""" ═══════════════════════════════════════════════════════════════════════ INTEGRATION GUIDE — COMO INTEGRAR NO api.py EXISTENTE ═══════════════════════════════════════════════════════════════════════ Este arquivo mostra: 1. Como modificar o endpoint POST /akira existente 2. Como usar ContextManagerV2 3. Como usar ListenStreamProcessor 4. Exemplos práticos de fluxo IMPORTANTE: Isso SUBSTITUI o sistema antigo de context, mantendo compatibilidade com code existente. ═══════════════════════════════════════════════════════════════════════ """ # ═══════════════════════════════════════════════════════════════════════ # 📦 IMPORTS NECESSÁRIOS # ═══════════════════════════════════════════════════════════════════════ from modules.context_manager_v2 import ( ContextManagerV2, get_context_manager, MessageType ) from modules.listen_stream_processor import ( ListenStreamProcessor, get_listen_processor ) from typing import Dict, Any import logging logger = logging.getLogger(__name__) ctx_manager = get_context_manager() listen_processor = get_listen_processor() # ═══════════════════════════════════════════════════════════════════════ # 🔄 NOVO FLUXO DO ENDPOINT /akira # ═══════════════════════════════════════════════════════════════════════ """ ANTES (buggy): ──────────────────────────────────────────────────────────────── POST /akira { "usuario": "Isaac", "numero": "202391978787009", "texto": "@AKIRA qual é a capital?" } → api.py chama obter_historico() → PROBLEMA: Retorna ALL mensagens, misturando grupos → AKIRA responde sem isolação DEPOIS (robusto): ──────────────────────────────────────────────────────────────── POST /akira { "usuario": "Isaac", "numero": "202391978787009", "texto": "@AKIRA qual é a capital?", "tipo_conversa": "grupo" | "pv", "grupo_id": "g120363392399993499" (opcional), "referenced_message_author": "AKIRA", (opcional) } → listen_processor.processar_mensagem_chegando() → Classifica como DIRECT ou CONTEXTUAL → Adiciona à context_manager isolado por conversation_id → Se DIRECT (true): → ctx_manager.obter_historico_direto() → APENAS mensagens direcionadas a AKIRA → Isolado por conversation_id → AKIRA responde → Se CONTEXTUAL (false): → Registra mas NÃO processa → AKIRA entende fluxo via obter_contexto_grupo_amplificado() """ # ═══════════════════════════════════════════════════════════════════════ # 🔧 MODIFICAÇÃO NO ENDPOINT /akira # ═══════════════════════════════════════════════════════════════════════ def novo_akira_endpoint_handler(request_data: Dict[str, Any]) -> Dict[str, Any]: """ Novo handler para POST /akira com isolação de contexto. Substitui a lógica antiga em api.py:akira_endpoint() """ # ───────────────────────────────────────────────────────────── # PASSO 1: EXTRAI DADOS CHEGANDO # ───────────────────────────────────────────────────────────── usuario = request_data.get('usuario', 'desconhecido') numero = request_data.get('numero', '') texto = request_data.get('texto', '') tipo_conversa = request_data.get('tipo_conversa', 'pv') grupo_id = request_data.get('grupo_id') # Referência (se é resposta a outra mensagem) referenced_author = request_data.get('referenced_message_author') referenced_texto = request_data.get('referenced_message_texto') referenced_id = request_data.get('referenced_message_id') logger.info( f"📨 Mensagem chegando: {usuario} ({numero}) em {tipo_conversa} | " f"Texto: {texto[:50]}..." ) # ───────────────────────────────────────────────────────────── # PASSO 2: PROCESSA VIA LISTEN STREAM # ───────────────────────────────────────────────────────────── evento = { 'usuario': usuario, 'numero': numero, 'texto': texto, 'tipo_conversa': tipo_conversa, 'grupo_id': grupo_id, 'referenced_message_author': referenced_author, 'referenced_message_texto': referenced_texto, 'referenced_message_id': referenced_id, } resultado_processamento = listen_processor.processar_mensagem_chegando(evento) logger.info( f"🔍 Classificação: {resultado_processamento['tipo_message']} | " f"Deve processar: {resultado_processamento['deve_processar']}" ) # ───────────────────────────────────────────────────────────── # PASSO 3: DECIDE SE AKIRA RESPONDE # ───────────────────────────────────────────────────────────── if not resultado_processamento['deve_processar']: # Mensagem CONTEXTUAL → apenas registra, não responde logger.info(f"📚 Mensagem contextual (escuta). Conversão_id: {resultado_processamento['conversation_id']}") return { 'status': 'context_registered', 'tipo': 'contextual', 'message': f"Entendi. {usuario} falou sobre isso no grupo.", 'deve_responder': False, 'conversation_id': resultado_processamento['conversation_id'], } # ───────────────────────────────────────────────────────────── # PASSO 4: AKIRA RESPONDE (mensagem é DIRETA) # ───────────────────────────────────────────────────────────── conversation_id = resultado_processamento['conversation_id'] # Obtém contexto ISOLADO contexto = listen_processor.obter_contexto_para_resposta( numero=numero, tipo_conversa=tipo_conversa, grupo_id=grupo_id, include_contextual=True ) logger.info( f"📖 Contexto obtido: {contexto['total_direct']} mensagens diretas | " f"Participantes: {contexto.get('participants', [])}" ) # ───────────────────────────────────────────────────────────── # PASSO 5: ENRIQUECER COM CONTEXTO DE GRUPO # ───────────────────────────────────────────────────────────── contexto_grupo_info = contexto.get('grupo_flow', {}) participants = contexto.get('participants', []) # Monta prompt para AKIRA prompt_system = f""" Você é AKIRA, assistente IA em um grupo. Contexto: - Participantes do grupo: {', '.join(participants) if participants else 'Desconhecido'} - Tópicos recentes: {', '.join(contexto.get('topics', [])) if contexto.get('topics') else 'Vários'} - Sua conversa com {usuario}: {len(contexto['direct_messages'])} mensagens anteriores IMPORTANTE: Responda APENAS sobre o que foi perguntado a você. NÃO comente sobre outras conversas do grupo (escuta sem falar). """ # Histórico ISOLADO mensagens_anteriores = [ { 'usuario': m['usuario'], 'texto': m['texto'], 'timestamp': m['timestamp'] } for m in contexto['direct_messages'] ] # ───────────────────────────────────────────────────────────── # PASSO 6: CHAMA LLM (seu código existente) # ───────────────────────────────────────────────────────────── # Isso seria integrado com seu chain LLM existente: # resposta = gerar_resposta_llm( # sistema=prompt_system, # historico=mensagens_anteriores, # mensagem_atual=texto, # usuario=usuario # ) resposta = f"[RESPOSTA DE AKIRA] Você perguntou: {texto[:100]}" # Placeholder # ───────────────────────────────────────────────────────────── # PASSO 7: RETORNA # ───────────────────────────────────────────────────────────── return { 'status': 'success', 'tipo': 'direct', 'resposta': resposta, 'deve_responder': True, 'conversation_id': conversation_id, 'usuario': usuario, 'contexto_info': { 'num_direct_messages': contexto['total_direct'], 'participants': participants, 'topics': contexto.get('topics', []), } } # ═══════════════════════════════════════════════════════════════════════ # 📝 ALTERAÇÕES NECESSÁRIAS NO api.py # ═══════════════════════════════════════════════════════════════════════ """ LOCAIS ESPECÍFICOS A MODIFICAR EM api.py: 1️⃣ IMPORT SECTION (top do arquivo) ──────────────────────────────────────────────────────────────── Adicionar: from modules.context_manager_v2 import get_context_manager from modules.listen_stream_processor import get_listen_processor ctx_manager = get_context_manager() listen_processor = get_listen_processor() 2️⃣ FUNÇÃO akira_endpoint() - SUBSTITUIR INTEIRA ──────────────────────────────────────────────────────────────── Localização: api.py::akira_endpoint() (aproximadamente linha 950) Ação: SUBSTITUIR todo o corpo da função pelo code do novo_akira_endpoint_handler() acima. O que muda: ✅ Remove: data = contexto.obter_historico() (BUGGY - sem filtro) ✅ Adiciona: resultado_processamento = listen_processor.processar_mensagem_chegando() ✅ Adiciona: if not resultado_processamento['deve_processar']: return (skip contextual) ✅ Adiciona: contexto = listen_processor.obter_contexto_para_resposta() (ISOLADO) 3️⃣ FUNÇÃO _get_user_context() - MODIFICAR ──────────────────────────────────────────────────────────────── Localização: api.py::_get_user_context() (aproximadamente linha 1000) ANTES: def _get_user_context(numero, usuario): historico = contexto.obter_historico() # ❌ SEM FILTRO return { 'historico': historico, 'usuario': usuario } DEPOIS: def _get_user_context(numero, usuario, tipo_conversa='pv', grupo_id=None): # Usa novo context manager ISOLADO historico = ctx_manager.obter_historico_direto( numero=numero, tipo_conversa=tipo_conversa, grupo_id=grupo_id, limit=50 ) contexto_grupo = ctx_manager.obter_contexto_grupo_amplificado( numero=numero, tipo_conversa=tipo_conversa, grupo_id=grupo_id ) if tipo_conversa == "grupo" else None return { 'historico': [m.to_dict() for m in historico], 'usuario': usuario, 'contexto_grupo': contexto_grupo, 'conversation_id': historico[0].conversation_id if historico else None } 4️⃣ FUNÇÃO obter_historico() - DEPRECAR OU SUBSTITUIR ──────────────────────────────────────────────────────────────── Localização: database.py ou modules/database.py ANTES: def obter_historico(): return db.query(Mensagem).all() # ❌ RETORNA TUDO DEPOIS: def obter_historico(conversation_id: str, msg_type: str = 'all'): # msg_type: 'direct', 'contextual', 'all' query = db.query(Mensagem).filter( Mensagem.conversation_id == conversation_id ) if msg_type == 'direct': query = query.filter(Mensagem.tipo == 'direct') elif msg_type == 'contextual': query = query.filter(Mensagem.tipo == 'contextual') return query.order_by(Mensagem.timestamp.desc()).limit(100).all() 5️⃣ SUPORTE AO PAYLOAD NOVO ──────────────────────────────────────────────────────────────── O endpoint POST /akira AGORA ACEITA: { "usuario": "Isaac", "numero": "202391978787009", "texto": "@AKIRA qual é a capital?", "tipo_conversa": "grupo", ← NOVO "grupo_id": "g120363392399993499", ← NOVO (opcional) "referenced_message_author": "Stefânio", ← NOVO (opcional) "referenced_message_texto": "Oi", ← NOVO (opcional) "referenced_message_id": "msg_123" ← NOVO (opcional) } Os campos 'tipo_conversa', 'grupo_id', 'referenced_*' são opcionais e vêm do discord-ts quando disponíveis. Se não vierem, valor default é 'pv' (conversa privada). """ # ═══════════════════════════════════════════════════════════════════════ # 📊 EXEMPLO DE USO COMPLETO # ═══════════════════════════════════════════════════════════════════════ """ CENÁRIO: Isaac e Stefânio em grupo 1️⃣ ISAAC envia: "@AKIRA qual é a capital de Portugal?" grupo: "g120363392399993499" → listen_processor classifica como DIRECT (menciona @AKIRA) → ctx_manager.adicionar_message_direta() com conversation_id único → AKIRA responde: "A capital é Lisboa" 2️⃣ STEFÂNIO responde (sem @AKIRA): "Bacano, eu não sabia" → listen_processor classifica como CONTEXTUAL → ctx_manager.adicionar_message_contextual() → AKIRA NÃO responde (não foi direcionada a ela) → Mas AKIRA "entende" que Stefânio achou legal 3️⃣ STEFÂNIO envia: "Qual é a capital da França?" (sem @AKIRA) → listen_processor classifica como CONTEXTUAL → AKIRA não responde 4️⃣ ISAAC responde: "@AKIRA? Já que perguntou, qual é?" → listen_processor classifica como DIRECT (menciona @AKIRA) → Obtém contexto ISOLADO (só conversas diretas com Isaac) → AKIRA NÃO vê a pergunta de Stefânio (contextual) → AKIRA responde baseada só no que Isaac perguntou RESULTADO: ✅ AKIRA entende quem fala o quê ✅ Não mistura contextos ✅ Responde apenas quando direcionada ✅ Escuta o fluxo sem contaminar """ # ═══════════════════════════════════════════════════════════════════════ # 🎯 PRÓXIMOS PASSOS # ═══════════════════════════════════════════════════════════════════════ """ TAREFAS PARA IMPLEMENTAR: 1. ✅ Criar context_manager_v2.py (FEITO) 2. ✅ Criar listen_stream_processor.py (FEITO) 3. 🔄 Modificar api.py: - Importar novos módulos - Reescrever akira_endpoint() - Atualizar _get_user_context() 4. 🔄 Modificar database.py: - Adicionar suporte a conversation_id na query - Adicionar campo 'tipo' na tabela Mensagem 5. 🔄 Testar com fluxo real: - Isaac + Stefânio em grupo - Verificar isolamento 6. 🔄 Integrar com discord-ts: - discord-ts deve enviar 'tipo_conversa' e 'grupo_id' - Enviar 'referenced_message_author' quando é reply """ __all__ = [ 'novo_akira_endpoint_handler', 'ctx_manager', 'listen_processor', ]