akira-index / modules /improved_context_handler.py
akra35567's picture
Upload 21 files
703e66d verified
# 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()