# type: ignore """ ================================================================================ IMPROVED CONTEXT HANDLER - Melhor gerenciamento de contexto para Akira ================================================================================ IMPORTANTE: Este módulo NÃO modifica context_builder.py ou contexto.py! Ele adiciona uma camada INTELIGENTE de análise de contexto para perguntas curtas. Função: Resolver o problema de perguntas curtas ("Oq é isso?") perdendo contexto Preserva: Toda a arquitetura e lógica existente do sistema de contexto ================================================================================ """ import re from typing import Dict, List, Optional, Tuple, Any from dataclasses import dataclass try: from . import config except ImportError: import modules.config as config @dataclass class ContextWeights: """Pesos calculados para diferentes tipos de contexto.""" reply_context: float = 0.0 quoted_analysis: float = 0.0 short_term_memory: float = 1.0 vector_memory: float = 0.7 def to_dict(self) -> Dict[str, float]: """Converte para dicionário.""" return { "reply_context": self.reply_context, "quoted_analysis": self.quoted_analysis, "short_term_memory": self.short_term_memory, "vector_memory": self.vector_memory, } @dataclass class QuestionAnalysis: """Análise de uma pergunta.""" is_short:bool = False # <= 5 palavras is_very_short: bool = False # <= 2 palavras has_pronoun: bool = False # tem "isso", "aquilo", "ele", etc has_reply: bool = False needs_context: bool = False # precisa de contexto extra question_type: str = "general" # "what", "how", "where", "why", "general" class ImprovedContextHandler: """ Gerenciador inteligente de contexto para perguntas curtas. IMPORTANTE: - NÃO substitui o context_builder.py existente - Funciona como HELPER para calcular pesos de contexto - AUMENTA contexto para perguntas curtas com reply (contrário da lógica antiga) """ def __init__(self): # Pronomes que indicam necessidade de contexto self.context_pronouns = { "isso", "aquilo", "este", "esse", "aquele", "ele", "ela", "eles", "elas", "la", "lo", "las", "los", # "a la", "o lo" } # Palavras interrogativas self.question_words = { "what": ["oq", "o que", "oque", "que é"], "how": ["como"], "where": ["onde", "aonde"], "when": ["quando", "que horas"], "why": ["porque", "porquê", "por que", "pq"], "who": ["quem"], } # Limites de palavras self.very_short_threshold = 2 # "Oq é?" self.short_threshold = 5 # "Como funciona isso?" def analyze_question( self, message: str, reply_metadata: Optional[Dict[str, Any]] = None ) -> QuestionAnalysis: """ Analisa uma mensagem para determinar necessidade de contexto. Args: message: Mensagem do usuário reply_metadata: Metadados de reply (se for reply) Returns: QuestionAnalysis com detalhes da análise """ message_lower = message.lower().strip() words = message_lower.split() word_count = len(words) analysis = QuestionAnalysis() # Classifica tamanho analysis.is_very_short = word_count <= self.very_short_threshold analysis.is_short = word_count <= self.short_threshold # Detecta pronomes contextuais analysis.has_pronoun = any( pronoun in message_lower for pronoun in self.context_pronouns ) # Verifica se tem reply if reply_metadata: analysis.has_reply = reply_metadata.get("is_reply", False) # Detecta tipo de pergunta for q_type, patterns in self.question_words.items(): if any(pattern in message_lower for pattern in patterns): analysis.question_type = q_type break # Determina se precisa de contexto extra analysis.needs_context = ( analysis.is_short and (analysis.has_pronoun or analysis.has_reply) ) return analysis def calculate_context_weights( self, message: str, reply_metadata: Optional[Dict[str, Any]] = None ) -> ContextWeights: """ Calcula pesos de contexto de forma inteligente. LÓGICA INVERTIDA da original: - Perguntas curtas COM reply = MAIS contexto de reply - Perguntas normais = balanço - Sem reply = contexto geral Args: message: Mensagem do usuário reply_metadata: Metadados de reply Returns: ContextWeights com pesos calculados """ analysis = self.analyze_question(message, reply_metadata) weights = ContextWeights() # CASO 1: Pergunta MUITO curta COM reply # Exemplo: "Oq é isso?" (reply a mensagem sobre Radiohead) if analysis.is_very_short and analysis.has_reply: weights.reply_context = 1.0 # ✅ MÁXIMO para reply weights.quoted_analysis = 0.95 # Analisa profundamente a citação weights.short_term_memory = 0.8 # ✅ MANTÉM texto curto + contexto weights.vector_memory = 0.3 # Fatos gerais baixo # CASO 2: Pergunta curta COM reply # Exemplo: "Como funciona isso?" (reply a explicação técnica) elif analysis.is_short and analysis.has_reply: weights.reply_context = 0.9 # Alto para reply weights.quoted_analysis = 0.85 weights.short_term_memory = 0.85 # ✅ MANTÉM texto curto no contexto weights.vector_memory = 0.4 # CASO 3: Pergunta curta COM pronome mas SEM reply # Exemplo: "Oq é isso?" (sem reply - contexto ambíguo) elif analysis.is_short and analysis.has_pronoun: weights.reply_context = 0.0 # Sem reply weights.quoted_analysis = 0.0 weights.short_term_memory = 1.0 # Usa histórico recente completo weights.vector_memory = 0.8 # Busca memória de fatos # CASO 4: Pergunta normal COM reply # Exemplo: "Você pode explicar melhor esse conceito?" (reply a explicação) elif analysis.has_reply: weights.reply_context = 0.8 weights.quoted_analysis = 0.7 weights.short_term_memory = 0.8 weights.vector_memory = 0.5 # CASO 5: Pergunta normal SEM reply # Exemplo: "Como funciona inteligência artificial?" else: weights.reply_context = 0.0 weights.quoted_analysis = 0.0 weights.short_term_memory = 1.0 weights.vector_memory = 0.7 return weights def extract_quoted_content_deep( self, reply_metadata: Dict[str, Any] ) -> str: """ Extrai conteúdo citado de forma profunda. Prioriza campos mais completos. Args: reply_metadata: Metadados do reply Returns: Conteúdo completo citado """ # Ordem de prioridade (do mais completo para o menos) priority_fields = [ "mensagem_citada", "full_message", "quoted_text_original", "quoted_text", "reply_content", "context_hint", ] for field in priority_fields: if field in reply_metadata and reply_metadata[field]: content = str(reply_metadata[field]).strip() if len(content) > 5: # Ignora conteúdos muito curtos return content # Fallback: tenta extrair de qualquer campo que pareça mensagem for key, value in reply_metadata.items(): if isinstance(value, str) and len(value) > 10: # Verifica se tem palavras comuns de mensagem if any(word in value.lower() for word in ["eu", "você", "tu", "ele"]): return value.strip() return "" def analyze_quoted_content( self, quoted_content: str, current_message: str ) -> Dict[str, Any]: """ Analisa conteúdo citado para entender o contexto. Args: quoted_content: Conteúdo da mensagem citada current_message: Mensagem atual do usuário Returns: Análise do conteúdo citado """ if not quoted_content: return {"empty": True} quoted_lower = quoted_content.lower() current_lower = current_message.lower() # Detecta tipo de conteúdo content_type = "general" if any(w in quoted_lower for w in ["?", "qual", "quando", "onde", "como", "por que"]): content_type = "question" elif any(w in quoted_lower for w in ["eu", "mim", "meu", "minha"]): content_type = "personal" elif any(w in quoted_lower for w in ["akira", "bot", "você", "vc"]): content_type = "about_bot" # Extrai keywords principais keywords = self._extract_keywords(quoted_content) # Detecta tom tone = "neutral" if any(w in quoted_lower for w in ["kkk", "haha", "😂", "🤣"]): tone = "humorous" elif any(w in quoted_lower for w in ["!!!", "???", "nossa", "eita"]): tone = "excited" # Detecta se há informação técnica/específica has_specific_info = any( word in quoted_lower for word in ["Estudo", "Academica", "Programação", "Ciência", "política", "País"] ) return { "content_type": content_type, "keywords": keywords, "tone": tone, "length": len(quoted_content), "has_question": "?" in quoted_content, "has_specific_info": has_specific_info, } def _extract_keywords(self, text: str, max_keywords: int = 5) -> List[str]: """Extrai keywords principais do texto.""" # Remove stopwords comuns stopwords = { "o", "a", "de", "da", "do", "em", "para", "com", "por", "que", "é", "um", "uma", "os", "as", "dos", "das", "e", "ou", "mas", "se", "não", "sim", } words = re.findall(r'\w+', text.lower()) keywords = [w for w in words if w not in stopwords and len(w) > 3] # Retorna os primeiros N return keywords[:max_keywords] # ============================================================ # FUNÇÕES DE CONVENIÊNCIA # ============================================================ _handler_instance: Optional[ImprovedContextHandler] = None def get_context_handler() -> ImprovedContextHandler: """Retorna instância singleton do handler.""" global _handler_instance if _handler_instance is None: _handler_instance = ImprovedContextHandler() return _handler_instance def calculate_smart_context_weights( message: str, reply_metadata: Optional[Dict[str, Any]] = None ) -> Dict[str, float]: """ Função helper para calcular pesos de contexto inteligentemente. Args: message: Mensagem do usuário reply_metadata: Metadados de reply Returns: Dict com pesos de contexto """ handler = get_context_handler() weights = handler.calculate_context_weights(message, reply_metadata) return weights.to_dict() # ============================================================ # EXEMPLO DE USO # ============================================================ if __name__ == "__main__": # Teste básico handler = ImprovedContextHandler() test_cases = [ # (mensagem, tem_reply, descrição) ("Oq é isso?", True, "Pergunta muito curta com reply"), ("Como funciona isso?", True, "Pergunta curta com reply"), ("Oq é isso?", False, "Pergunta curta SEM reply (ambígua)"), ("Você pode explicar melhor esse conceito?", True, "Pergunta normal com reply"), ("Como funciona inteligência artificial?", False, "Pergunta normal sem reply"), ] print("=== TESTE DE PESOS DE CONTEXTO ===\n") for message, has_reply, description in test_cases: print(f"Caso: {description}") print(f"Mensagem: \"{message}\"") print(f"Tem reply: {has_reply}") reply_meta = {"is_reply": has_reply} if has_reply else None weights = handler.calculate_context_weights(message, reply_meta) print(f"Pesos calculados:") print(f" - Reply context: {weights.reply_context:.2f}") print(f" - Quoted analysis: {weights.quoted_analysis:.2f}") print(f" - Short-term memory: {weights.short_term_memory:.2f}") print(f" - Vector memory: {weights.vector_memory:.2f}") print()