Spaces:
Running
Running
File size: 15,833 Bytes
c2e2750 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 | """
================================================================================
THINKING ENGINE - Sistema de Pensamento Profundo Pré-Processamento
================================================================================
Similar a modelos com "thinking tokens" - analisa o que foi perguntado
ANTES de gerar resposta, resultando em respostas mais acertivas.
Features:
- Análise multi-camada da pergunta/contexto
- Embeddings especializados para pensamento
- Detecção de intent implícito
- Complexidade da pergunta
- Relacionamentos com LSTM context
- Cache de pensamentos
================================================================================
"""
import json
from typing import Dict, Any, Optional, List
from loguru import logger
from sentence_transformers import SentenceTransformer, util
import numpy as np
class ThinkingEngine:
"""Processa pensamento profundo antes de responder."""
def __init__(self, db=None):
"""Inicializa com modelo de embedding para análise profunda."""
self.db = db
self.thinking_cache = {}
self.model_thinking = None
self._load_thinking_model()
def _load_thinking_model(self):
"""Carrega modelo especializado para pensamento."""
try:
# Usa o modelo centralizado do config (com fallback embutido)
from . import config
self.model_thinking = config.get_embedding_model("all-MiniLM-L6-v2")
if self.model_thinking:
logger.success("✅ ThinkingEngine: Modelo de pensamento carregado via config")
else:
logger.warning("⚠️ ThinkingEngine: Config retornou None para o modelo")
except Exception as e:
logger.warning(f"⚠️ ThinkingEngine: Erro ao carregar modelo: {e}")
self.model_thinking = None
def think(
self,
mensagem: str,
contexto_lstm: Optional[Dict[str, Any]] = None,
historico_recente: Optional[List[str]] = None,
is_group: bool = False,
usuario: str = None,
llm_manager: Any = None
) -> Dict[str, Any]:
"""
Processa pensamento profundo sobre a pergunta/contexto.
Args:
mensagem: Mensagem do usuário
contexto_lstm: Contexto LSTM (longo prazo)
historico_recente: Últimas mensagens
is_group: Se é em grupo
usuario: Nome do usuário
llm_manager: Instância de LLMManager para CoT Dinâmico (OpenRouter)
Returns:
Dict com análise profunda
"""
if not self.model_thinking:
return self._thinking_fallback(mensagem)
cache_key = f"{usuario}:{mensagem[:50]}"
if cache_key in self.thinking_cache:
logger.debug(f"🧠 ThinkingEngine: Pensamento recuperado do cache")
return self.thinking_cache[cache_key]
try:
thinking_result = {
"depth": self._analyze_question_complexity(mensagem),
"intent": self._detect_intent(mensagem),
"entities": self._extract_entities(mensagem),
"context_relevance": self._analyze_context_relevance(mensagem, contexto_lstm),
"related_topics": self._find_related_topics(mensagem, contexto_lstm),
"assumptions": self._detect_assumptions(mensagem),
"required_sources": self._identify_sources(mensagem),
"response_strategy": self._plan_response_strategy(mensagem, is_group),
"quality_markers": self._identify_quality_markers(mensagem),
}
# 🧠 CoT Dinâmico: Chama o OpenRouter para raciocínio estruturado
dynamic_thought = self._generate_dynamic_thought(
mensagem, contexto_lstm, historico_recente, is_group, llm_manager, usuario
)
if dynamic_thought:
thinking_result["dynamic_thought_trace"] = dynamic_thought
# Cache por 30 minutos (300 chamadas)
if len(self.thinking_cache) > 1000:
self.thinking_cache.clear()
self.thinking_cache[cache_key] = thinking_result
logger.debug(f"🧠 ThinkingEngine: Pensamento realizado (depth={thinking_result['depth']})")
return thinking_result
except Exception as e:
logger.warning(f"⚠️ ThinkingEngine erro: {e}")
return self._thinking_fallback(mensagem)
def _generate_dynamic_thought(
self,
mensagem: str,
contexto_lstm: Optional[Dict[str, Any]],
historico_recente: Optional[List[str]],
is_group: bool,
llm_manager: Any,
usuario: str = "desconhecido"
) -> Optional[str]:
"""Usa o OpenRouter para gerar um plano de raciocínio passo a passo."""
if not llm_manager:
logger.warning("⚠️ CoT Dinâmico abortado: llm_manager é None")
return None
if not hasattr(llm_manager, '_call_openrouter'):
logger.warning(f"⚠️ CoT Dinâmico abortado: llm_manager ({type(llm_manager)}) não tem o método '_call_openrouter'")
return None
try:
# Constrói um contexto enxuto para não gastar muitos tokens
sys_prompt = (
"Atuas como o Motor Analítico Interno da Akira V21.\n"
"A tua ÚNICA tarefa é gerar um rascunho de raciocínio (plano lógico) sobre como a Akira deve responder a esta mensagem, deixa sempre claro akira que sua resposta deve ser curta e direta e séria.\n"
"Reflete sobre:\n"
f"1. A emoção e intenção oculta de {usuario}.\n"
"2. Que factos devem ser procurados no histórico.\n"
"3. Qual o tom (direto, empático, sério) a usar.\n"
f"NOTA: A pessoa a falar contigo chama-se '{usuario}'. Usa o nome real na tua análise em vez de 'o utilizador'.\n"
"NÃO dês a resposta final. Apenas planeia a estratégia de resposta em menos de 80 palavras ed deia sugestões de resposta pra akira usar, lembrando ela não mandar emojis. GERA O TEU PENSAMENTO EXCLUSIVAMENTE EM PORTUGUÊS."
)
if is_group:
sys_prompt += "\nNOTA: Isto é um ambiente de GRUPO. Sê muito conciso e evita intervir desnecessariamente."
if contexto_lstm:
sys_prompt += "\n\n[MEMÓRIA LONGO PRAZO (LSTM)]"
if 'topic_principal' in contexto_lstm:
sys_prompt += f"\n- Tópico Principal: {contexto_lstm['topic_principal']}"
if 'unanswered_questions' in contexto_lstm and contexto_lstm['unanswered_questions']:
sys_prompt += f"\n- Perguntas Pendentes: {', '.join(contexto_lstm['unanswered_questions'][:2])}"
if 'interaction_pattern' in contexto_lstm:
sys_prompt += f"\n- Padrão do Utilizador: {contexto_lstm['interaction_pattern']}"
if historico_recente:
sys_prompt += "\n\n[MEMÓRIA CURTO PRAZO (LISTEN)]\nÚltimas mensagens da conversa:\n"
# Pega mais mensagens para entender conversas paralelas
for msg in historico_recente[-15:]:
if isinstance(msg, dict) and "content" in msg:
sys_prompt += f"{msg['content']}\n"
else:
sys_prompt += f"{msg}\n"
logger.info("🧠 Gerando CoT Dinâmico via OpenRouter...")
# Chamada ultrarrápida usando o modelo setado no config
thought = llm_manager._call_openrouter(
system_prompt=sys_prompt,
context_history=[], # não passamos o histórico todo para ser super rápido
user_prompt=mensagem,
max_tokens=150
)
return thought
except Exception as e:
logger.warning(f"⚠️ Erro no CoT Dinâmico (Fallback ativado): {e}")
return None
def _analyze_question_complexity(self, mensagem: str) -> str:
"""Analisa complexidade da pergunta."""
msg_lower = mensagem.lower()
# Sinais de complexidade
complex_markers = {
"muito": 0.3, "profundo": 0.4, "explique": 0.35, "detalhe": 0.35,
"por quê": 0.4, "como": 0.3, "quando": 0.25, "onde": 0.2,
"comparação": 0.5, "diferença": 0.4, "relação": 0.4,
"múltiplo": 0.45, "vários": 0.4, "tanto": 0.35,
}
score = 0.1 # Base
for marker, weight in complex_markers.items():
if marker in msg_lower:
score += weight
# Pontuação
if "?" in mensagem:
score += 0.1
if "!" in mensagem:
score -= 0.1
score = min(1.0, score)
if score < 0.2:
return "simples"
elif score < 0.5:
return "moderada"
elif score < 0.75:
return "complexa"
else:
return "muito_complexa"
def _detect_intent(self, mensagem: str) -> List[str]:
"""Detecta intent(s) implícito(s)."""
intents = []
msg_lower = mensagem.lower()
intent_markers = {
"informação": ["o que", "como", "por quê", "sabe sobre", "fala sobre", "explica"],
"ação": ["faz", "cria", "envia", "modifica", "deleta", "inicia"],
"opinião": ["acha", "gosta", "prefere", "ache", "pense", "achei"],
"confirmação": ["certo", "verdade", "é mesmo", "sério", "confirma"],
"contexto": ["em relação", "sobre isso", "quanto a", "nisso"],
"humor": ["kkk", "haha", "ué", "lol", ":)", "rsrs"],
}
for intent, markers in intent_markers.items():
if any(m in msg_lower for m in markers):
intents.append(intent)
return intents or ["indefinido"]
def _extract_entities(self, mensagem: str) -> List[str]:
"""Extrai entidades mencionadas."""
# Simples: palavras maiúsculas ou nomes comuns
palavras = mensagem.split()
entities = [p.strip(".,!?;:") for p in palavras if len(p) > 3 and p[0].isupper()]
return entities[:5] # Top 5
def _analyze_context_relevance(
self,
mensagem: str,
contexto_lstm: Optional[Dict[str, Any]]
) -> float:
"""Quanto a mensagem se relaciona com contexto de longo prazo."""
if not contexto_lstm or not self.model_thinking:
return 0.0
try:
topic_lstm = contexto_lstm.get("topic_principal", "")
if not topic_lstm:
return 0.0
# Embedding similarity
emb_msg = self.model_thinking.encode(mensagem, convert_to_tensor=False)
emb_topic = self.model_thinking.encode(topic_lstm, convert_to_tensor=False)
relevance = float(util.cos_sim(emb_msg, emb_topic)[0][0])
return max(0.0, min(1.0, relevance))
except:
return 0.0
def _find_related_topics(
self,
mensagem: str,
contexto_lstm: Optional[Dict[str, Any]]
) -> List[str]:
"""Encontra tópicos relacionados no LSTM."""
if not contexto_lstm:
return []
topics = []
# Topics do LSTM (se houver)
if contexto_lstm.get("subtopicas"):
topics.extend(contexto_lstm["subtopicas"][:3])
if contexto_lstm.get("conversation_path"):
topics.extend(contexto_lstm["conversation_path"][-3:])
return topics[:5]
def _detect_assumptions(self, mensagem: str) -> List[str]:
"""Detecta assumptions que o usuário faz."""
assumptions = []
msg_lower = mensagem.lower()
# Palavras que indicam assumption
if "já" in msg_lower or "não sabe" in msg_lower:
assumptions.append("assume_conhecimento_anterior")
if "deve" in msg_lower or "deveria" in msg_lower:
assumptions.append("expectativa_de_comportamento")
if "sempre" in msg_lower or "nunca" in msg_lower:
assumptions.append("generalização")
return assumptions
def _identify_sources(self, mensagem: str) -> List[str]:
"""Identifica que fontes seriam úteis."""
sources = []
msg_lower = mensagem.lower()
if any(w in msg_lower for w in ["notícia", "última", "recente", "novo", "2024", "2025"]):
sources.append("web_search")
if any(w in msg_lower for w in ["wikipedia", "história", "quem foi", "quando"]):
sources.append("wikipedia")
if any(w in msg_lower for w in ["preço", "dólar", "bitcoin", "crypto", "cotação"]):
sources.append("market_data")
if any(w in msg_lower for w in ["clima", "tempo", "previsão", "chuva"]):
sources.append("weather")
return sources
def _plan_response_strategy(self, mensagem: str, is_group: bool) -> str:
"""Define estratégia de resposta."""
msg_lower = mensagem.lower()
# Contexto do grupo
if is_group:
if any(w in msg_lower for w in ["vocês", "vcs", "todos", "@all"]):
return "grupo_completo"
else:
return "grupo_individual"
else:
return "privado"
def _identify_quality_markers(self, mensagem: str) -> Dict[str, bool]:
"""Identifica marcadores de qualidade da resposta esperada."""
return {
"needs_brevity": len(mensagem) < 20,
"needs_detail": len(mensagem) > 100,
"needs_humor": any(m in mensagem for m in ["kk", "kkk", ":)", "rsrs"]),
"formal_tone": any(w in mensagem for w in ["sr.", "sra.", "prezado"]),
"technical": any(w in mensagem.lower() for w in ["código", "api", "script", "função"]),
}
def _thinking_fallback(self, mensagem: str) -> Dict[str, Any]:
"""Fallback simples quando modelo não está disponível."""
return {
"depth": "moderada",
"intent": ["indefinido"],
"entities": [],
"context_relevance": 0.5,
"related_topics": [],
"assumptions": [],
"required_sources": [],
"response_strategy": "padrão",
"quality_markers": {
"needs_brevity": False,
"needs_detail": False,
"needs_humor": False,
"formal_tone": False,
"technical": False,
},
}
# Singleton global
_thinking_engine_instance: Optional[ThinkingEngine] = None
def get_thinking_engine(db=None) -> ThinkingEngine:
"""Retorna instância singleton do ThinkingEngine."""
global _thinking_engine_instance
if _thinking_engine_instance is None:
_thinking_engine_instance = ThinkingEngine(db=db)
return _thinking_engine_instance
|