Spaces:
Running
Running
Update modules/contexto.py
Browse files- modules/contexto.py +95 -148
modules/contexto.py
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
|
|
| 1 |
import logging
|
| 2 |
import re
|
| 3 |
import random
|
| 4 |
import time
|
| 5 |
import sqlite3
|
| 6 |
-
import json
|
| 7 |
-
from typing import Optional, List, Dict, Tuple, Any
|
| 8 |
import modules.config as config
|
| 9 |
from .database import Database
|
| 10 |
-
from .treinamento import Treinamento
|
| 11 |
|
| 12 |
try:
|
| 13 |
-
from sentence_transformers import SentenceTransformer
|
| 14 |
except Exception as e:
|
| 15 |
logging.warning(f"sentence_transformers não disponível: {e}")
|
| 16 |
SentenceTransformer = None
|
| 17 |
-
|
| 18 |
try:
|
| 19 |
-
import psutil
|
| 20 |
except Exception:
|
| 21 |
psutil = None
|
| 22 |
-
|
| 23 |
try:
|
| 24 |
-
import structlog
|
| 25 |
except Exception:
|
| 26 |
structlog = None
|
| 27 |
|
| 28 |
logger = logging.getLogger(__name__)
|
| 29 |
-
|
| 30 |
-
# Configuração do logging (fallback se structlog ausente)
|
| 31 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
|
|
|
|
| 32 |
if structlog:
|
| 33 |
structlog.configure(
|
| 34 |
processors=[
|
|
@@ -45,9 +45,10 @@ if structlog:
|
|
| 45 |
PALAVRAS_POSITIVAS = ['bom', 'ótimo', 'incrível', 'feliz', 'adorei', 'top', 'fixe', 'bué', 'show', 'legal', 'bacana']
|
| 46 |
PALAVRAS_NEGATIVAS = ['ruim', 'péssimo', 'triste', 'ódio', 'raiva', 'chateado', 'merda', 'porra', 'odeio']
|
| 47 |
|
|
|
|
| 48 |
class Contexto:
|
| 49 |
"""
|
| 50 |
-
Classe para gerenciar o contexto da conversa, análise de intenções e aprendizado
|
| 51 |
dinâmico de termos regionais/gírias para cada usuário.
|
| 52 |
"""
|
| 53 |
def __init__(self, db: Database, usuario: Optional[str] = None):
|
|
@@ -56,30 +57,27 @@ class Contexto:
|
|
| 56 |
self.model: Optional[SentenceTransformer] = None
|
| 57 |
self.embeddings: Optional[Dict[str, Any]] = None
|
| 58 |
self._treinador: Optional[Treinamento] = None
|
| 59 |
-
|
| 60 |
# Estado de conversa
|
| 61 |
self.emocao_atual = "neutra"
|
| 62 |
self.espírito_crítico = False
|
| 63 |
self.base_conhecimento = {}
|
| 64 |
-
|
| 65 |
# Garante que termo_contexto seja sempre um dicionário
|
| 66 |
self.termo_contexto: Dict[str, Dict] = {}
|
| 67 |
self.atualizar_aprendizados_do_banco()
|
| 68 |
-
|
| 69 |
-
logger.info("
|
| 70 |
-
|
| 71 |
# Cache para termos regionais e gírias
|
| 72 |
self.cache_girias: Dict[str, Any] = {}
|
| 73 |
-
|
| 74 |
def atualizar_aprendizados_do_banco(self):
|
| 75 |
-
"""
|
| 76 |
-
Carrega todos os dados de aprendizado persistentes (termos, gírias, etc.)
|
| 77 |
-
do banco de dados para a instância do Contexto.
|
| 78 |
-
"""
|
| 79 |
try:
|
| 80 |
termos_aprendidos = self.db.recuperar_girias_usuario(self.usuario) if self.usuario else []
|
| 81 |
self.termo_contexto = {
|
| 82 |
-
termo['giria']: {"significado": termo['significado'], "frequencia": termo['frequencia']}
|
| 83 |
for termo in termos_aprendidos
|
| 84 |
}
|
| 85 |
except Exception as e:
|
|
@@ -91,88 +89,62 @@ class Contexto:
|
|
| 91 |
if emocao_salva:
|
| 92 |
emocao_dict = json.loads(emocao_salva)
|
| 93 |
if isinstance(emocao_dict, dict) and 'emocao' in emocao_dict:
|
| 94 |
-
|
| 95 |
elif isinstance(emocao_salva, str):
|
| 96 |
-
self.emocao_atual = emocao_salva
|
| 97 |
except Exception as e:
|
| 98 |
logger.warning(f"Falha ao carregar emoção do DB: {e}")
|
| 99 |
-
|
| 100 |
-
logger.info(f"Aprendizados
|
| 101 |
|
| 102 |
@property
|
| 103 |
def ton_predominante(self) -> Optional[str]:
|
| 104 |
-
"""
|
| 105 |
-
[CORREÇÃO DO BUG] Retorna o tom predominante do usuário, acessando o DB.
|
| 106 |
-
Adicionado para resolver o 'AttributeError: ton_predominante'.
|
| 107 |
-
"""
|
| 108 |
if self.usuario:
|
| 109 |
return self.db.obter_tom_predominante(self.usuario)
|
| 110 |
return None
|
| 111 |
|
| 112 |
def get_or_create_treinador(self, interval_hours: int = 24) -> Treinamento:
|
| 113 |
-
"""Retorna um treinador associado
|
| 114 |
if self._treinador is None:
|
| 115 |
self._treinador = Treinamento(self.db, contexto=self, interval_hours=interval_hours)
|
| 116 |
return self._treinador
|
| 117 |
|
| 118 |
def _load_model(self):
|
| 119 |
-
"""Carrega o modelo SentenceTransformer
|
| 120 |
if self.model is not None:
|
| 121 |
return
|
| 122 |
-
|
| 123 |
-
start_time = time.time()
|
| 124 |
-
|
| 125 |
if SentenceTransformer is None:
|
| 126 |
-
logger.warning(
|
| 127 |
return
|
| 128 |
-
|
| 129 |
try:
|
| 130 |
self.model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 131 |
-
logger.info(
|
| 132 |
except Exception as e:
|
| 133 |
-
logger.error(
|
| 134 |
self.model = None
|
| 135 |
-
|
| 136 |
self._check_embeddings()
|
| 137 |
-
duration = time.time() - start_time
|
| 138 |
-
logger.info({"event": "Modelo carregado", "duration_seconds": duration})
|
| 139 |
|
| 140 |
def _check_embeddings(self):
|
| 141 |
-
"""Verifica ou cria embeddings no banco
|
| 142 |
if self.model and not self.embeddings:
|
| 143 |
-
|
| 144 |
-
self.embeddings = {"conhecimento_base": "placeholder_embedding_data"}
|
| 145 |
-
except Exception as e:
|
| 146 |
-
logger.warning(f"Não foi possível carregar embeddings: {e}")
|
| 147 |
|
| 148 |
def analisar_emocoes_mensagem(self, mensagem: str) -> Dict[str, Any]:
|
| 149 |
-
"""
|
| 150 |
-
Analisa o sentimento e emoção da mensagem (Heurística simples).
|
| 151 |
-
MÉTODO ADICIONADO PARA RESOLVER O ERRO 'analisar_emocoes_mensagem'.
|
| 152 |
-
"""
|
| 153 |
mensagem_lower = mensagem.strip().lower()
|
| 154 |
-
|
| 155 |
-
# Análise de Sentimento (Tom Simples)
|
| 156 |
pos_count = sum(mensagem_lower.count(w) for w in PALAVRAS_POSITIVAS)
|
| 157 |
neg_count = sum(mensagem_lower.count(w) for w in PALAVRAS_NEGATIVAS)
|
| 158 |
-
|
| 159 |
sentimento = "neutro"
|
| 160 |
if pos_count > neg_count:
|
| 161 |
sentimento = "positivo"
|
| 162 |
elif neg_count > pos_count:
|
| 163 |
sentimento = "negativo"
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
if sentimento == "positivo":
|
| 167 |
-
emocao_predominante = "alegria"
|
| 168 |
-
elif sentimento == "negativo":
|
| 169 |
-
emocao_predominante = "frustração"
|
| 170 |
-
else:
|
| 171 |
-
emocao_predominante = "neutra"
|
| 172 |
-
|
| 173 |
-
# Atualiza o estado da classe
|
| 174 |
self.emocao_atual = emocao_predominante
|
| 175 |
-
|
| 176 |
return {
|
| 177 |
"sentimento_detectado": sentimento,
|
| 178 |
"emocao_predominante": emocao_predominante,
|
|
@@ -180,166 +152,141 @@ class Contexto:
|
|
| 180 |
"intensidade_negativa": neg_count,
|
| 181 |
"tom_sugerido": "casual" if sentimento != "neutro" else "neutro"
|
| 182 |
}
|
| 183 |
-
|
| 184 |
def analisar_intencao_e_normalizar(self, mensagem: str, historico: List[Tuple[str, str]]) -> Dict[str, Any]:
|
| 185 |
-
"""Analisa
|
| 186 |
self._load_model()
|
| 187 |
-
|
| 188 |
if not isinstance(mensagem, str):
|
| 189 |
mensagem = str(mensagem)
|
| 190 |
mensagem_lower = mensagem.strip().lower()
|
| 191 |
|
| 192 |
-
#
|
| 193 |
intencao = "pergunta"
|
| 194 |
-
if '?' not in mensagem_lower and
|
| 195 |
intencao = "afirmacao"
|
| 196 |
if any(w in mensagem_lower for w in ['ola', 'oi', 'bom dia', 'boa tarde', 'boa noite', 'como vai']):
|
| 197 |
intencao = "saudacao"
|
| 198 |
if any(w in mensagem_lower for w in ['tchau', 'ate mais', 'adeus', 'fim', 'parar']):
|
| 199 |
intencao = "despedida"
|
| 200 |
-
|
| 201 |
-
#
|
| 202 |
analise_emocional = self.analisar_emocoes_mensagem(mensagem_lower)
|
| 203 |
-
|
| 204 |
-
#
|
| 205 |
estilo = "informal"
|
| 206 |
if len(re.findall(r'[A-ZÀ-Ÿ]{3,}', mensagem)) >= 2 or re.search(r'\b(Senhor|Doutor|Atenciosamente)\b', mensagem, re.IGNORECASE):
|
| 207 |
estilo = "formal"
|
| 208 |
-
|
| 209 |
-
# 4. Outras bandeiras
|
| 210 |
-
ironia = False
|
| 211 |
-
meia_frase = False
|
| 212 |
usar_nome = random.random() < getattr(config, 'USAR_NOME_PROBABILIDADE', 0.7)
|
| 213 |
|
| 214 |
return {
|
| 215 |
"texto_normalizado": mensagem_lower,
|
| 216 |
"intencao": intencao,
|
| 217 |
-
"sentimento": analise_emocional['sentimento_detectado'],
|
| 218 |
"estilo": estilo,
|
| 219 |
"contexto_ajustado": self.substituir_termos_aprendidos(mensagem_lower),
|
| 220 |
-
"ironia":
|
| 221 |
-
"meia_frase":
|
| 222 |
"usar_nome": usar_nome,
|
| 223 |
-
"emocao": self.emocao_atual
|
| 224 |
}
|
| 225 |
|
| 226 |
def obter_historico(self, limite: int = 5) -> List[Tuple[str, str]]:
|
| 227 |
-
"""Recupera
|
| 228 |
if not self.usuario:
|
| 229 |
return []
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
|
| 235 |
def atualizar_contexto(self, mensagem: str, resposta: str, numero: Optional[str] = None):
|
| 236 |
-
"""Salva
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
usuario = self.usuario
|
| 241 |
-
|
| 242 |
-
final_numero = numero if numero else self.usuario
|
| 243 |
-
|
| 244 |
try:
|
| 245 |
self.db.salvar_mensagem(usuario, mensagem, resposta, numero=final_numero)
|
| 246 |
-
|
| 247 |
historico = self.obter_historico(limite=10)
|
| 248 |
-
self.aprender_do_historico(mensagem, resposta, historico)
|
| 249 |
-
|
| 250 |
self.salvar_estado_contexto_no_db(final_numero)
|
| 251 |
-
|
| 252 |
except Exception as e:
|
| 253 |
-
logger.warning(f'Falha ao salvar
|
| 254 |
|
| 255 |
def salvar_estado_contexto_no_db(self, user_key: str):
|
| 256 |
-
"""Persiste
|
| 257 |
termos_json = json.dumps(self.termo_contexto)
|
| 258 |
-
emocao_str = self.emocao_atual
|
| 259 |
-
|
| 260 |
try:
|
| 261 |
-
self.db.salvar_aprendizado_detalhado(user_key, "emocao_atual", json.dumps({"emocao":
|
| 262 |
-
|
| 263 |
self.db.salvar_contexto(
|
| 264 |
user_key=user_key,
|
| 265 |
-
historico="[]",
|
| 266 |
-
emocao_atual=
|
| 267 |
termos=termos_json,
|
| 268 |
girias=termos_json,
|
| 269 |
-
tom=
|
| 270 |
)
|
| 271 |
-
logger.debug(f"Contexto do usuário {user_key} salvo no DB.")
|
| 272 |
except Exception as e:
|
| 273 |
-
logger.error(f"Falha ao salvar
|
| 274 |
-
|
| 275 |
|
| 276 |
def aprender_do_historico(self, mensagem: str, resposta: str, historico: List[Tuple[str, str]]):
|
| 277 |
-
"""Aprende
|
| 278 |
if not self.usuario:
|
| 279 |
return
|
| 280 |
-
|
| 281 |
mensagem_lower = mensagem.lower()
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
girias_angolanas_simples = ['ya', 'bué', 'fixe', 'puto']
|
| 285 |
-
|
| 286 |
for giria in girias_angolanas_simples:
|
| 287 |
if giria in mensagem_lower:
|
| 288 |
try:
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
self.
|
| 292 |
-
|
| 293 |
-
giria,
|
| 294 |
-
|
| 295 |
-
mensagem[:50]
|
| 296 |
-
)
|
| 297 |
-
|
| 298 |
-
self.termo_contexto[giria] = {"significado": significado_placeholder, "frequencia": self.termo_contexto.get(giria, {}).get("frequencia", 0) + 1}
|
| 299 |
-
|
| 300 |
except Exception as e:
|
| 301 |
-
logger.warning(f"Erro ao salvar gíria
|
| 302 |
-
|
| 303 |
def substituir_termos_aprendidos(self, mensagem: str) -> str:
|
| 304 |
-
"""Substitui termos aprendidos
|
| 305 |
for termo, info in self.termo_contexto.items():
|
| 306 |
if isinstance(info, dict) and "significado" in info:
|
| 307 |
-
# Usa regex para substituir apenas a palavra inteira (case insensitive)
|
| 308 |
mensagem = re.sub(r'\b' + re.escape(termo) + r'\b', info["significado"], mensagem, flags=re.IGNORECASE)
|
| 309 |
return mensagem
|
| 310 |
-
|
| 311 |
def obter_aprendizado_detalhado(self, chave: str) -> Optional[Dict]:
|
| 312 |
-
"""Recupera
|
| 313 |
try:
|
| 314 |
-
|
| 315 |
-
if
|
| 316 |
-
return json.loads(raw_data)
|
| 317 |
-
return None
|
| 318 |
except Exception as e:
|
| 319 |
-
logger.warning(f"Erro ao obter aprendizado
|
| 320 |
return None
|
| 321 |
-
|
| 322 |
def obter_emocao_atual(self) -> str:
|
| 323 |
-
"""Recupera a emoção atual do usuário."""
|
| 324 |
return self.emocao_atual
|
| 325 |
|
| 326 |
def ativar_espírito_crítico(self):
|
| 327 |
-
"""Ativa o espírito crítico para respostas questionadoras."""
|
| 328 |
self.espírito_crítico = True
|
| 329 |
|
| 330 |
def obter_aprendizados(self) -> Dict[str, Any]:
|
| 331 |
-
"""Retorna os aprendizados
|
| 332 |
-
|
| 333 |
"termos": self.termo_contexto,
|
| 334 |
"emocao_preferida": self.emocao_atual,
|
| 335 |
-
"ton_predominante": self.ton_predominante
|
| 336 |
}
|
| 337 |
-
return aprendizados
|
| 338 |
|
| 339 |
def salvar_conhecimento_base(self, chave: str, valor: Any):
|
| 340 |
-
"""Salva uma informação na base de conhecimento."""
|
| 341 |
self.base_conhecimento[chave] = valor
|
| 342 |
|
| 343 |
def obter_conhecimento_base(self, chave: str) -> Optional[Any]:
|
| 344 |
-
"""Obtém uma informação da base de conhecimento."""
|
| 345 |
return self.base_conhecimento.get(chave)
|
|
|
|
| 1 |
+
# modules/contexto.py
|
| 2 |
import logging
|
| 3 |
import re
|
| 4 |
import random
|
| 5 |
import time
|
| 6 |
import sqlite3
|
| 7 |
+
import json
|
| 8 |
+
from typing import Optional, List, Dict, Tuple, Any
|
| 9 |
import modules.config as config
|
| 10 |
from .database import Database
|
| 11 |
+
from .treinamento import Treinamento
|
| 12 |
|
| 13 |
try:
|
| 14 |
+
from sentence_transformers import SentenceTransformer
|
| 15 |
except Exception as e:
|
| 16 |
logging.warning(f"sentence_transformers não disponível: {e}")
|
| 17 |
SentenceTransformer = None
|
| 18 |
+
|
| 19 |
try:
|
| 20 |
+
import psutil
|
| 21 |
except Exception:
|
| 22 |
psutil = None
|
| 23 |
+
|
| 24 |
try:
|
| 25 |
+
import structlog
|
| 26 |
except Exception:
|
| 27 |
structlog = None
|
| 28 |
|
| 29 |
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
| 30 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
|
| 31 |
+
|
| 32 |
if structlog:
|
| 33 |
structlog.configure(
|
| 34 |
processors=[
|
|
|
|
| 45 |
PALAVRAS_POSITIVAS = ['bom', 'ótimo', 'incrível', 'feliz', 'adorei', 'top', 'fixe', 'bué', 'show', 'legal', 'bacana']
|
| 46 |
PALAVRAS_NEGATIVAS = ['ruim', 'péssimo', 'triste', 'ódio', 'raiva', 'chateado', 'merda', 'porra', 'odeio']
|
| 47 |
|
| 48 |
+
|
| 49 |
class Contexto:
|
| 50 |
"""
|
| 51 |
+
Classe para gerenciar o contexto da conversa, análise de intenções e aprendizado
|
| 52 |
dinâmico de termos regionais/gírias para cada usuário.
|
| 53 |
"""
|
| 54 |
def __init__(self, db: Database, usuario: Optional[str] = None):
|
|
|
|
| 57 |
self.model: Optional[SentenceTransformer] = None
|
| 58 |
self.embeddings: Optional[Dict[str, Any]] = None
|
| 59 |
self._treinador: Optional[Treinamento] = None
|
| 60 |
+
|
| 61 |
# Estado de conversa
|
| 62 |
self.emocao_atual = "neutra"
|
| 63 |
self.espírito_crítico = False
|
| 64 |
self.base_conhecimento = {}
|
| 65 |
+
|
| 66 |
# Garante que termo_contexto seja sempre um dicionário
|
| 67 |
self.termo_contexto: Dict[str, Dict] = {}
|
| 68 |
self.atualizar_aprendizados_do_banco()
|
| 69 |
+
|
| 70 |
+
logger.info("Inicializando Contexto (com NLP avançado, aprendizado de gírias e emoções) ...")
|
| 71 |
+
|
| 72 |
# Cache para termos regionais e gírias
|
| 73 |
self.cache_girias: Dict[str, Any] = {}
|
| 74 |
+
|
| 75 |
def atualizar_aprendizados_do_banco(self):
|
| 76 |
+
"""Carrega todos os dados de aprendizado persistentes do banco."""
|
|
|
|
|
|
|
|
|
|
| 77 |
try:
|
| 78 |
termos_aprendidos = self.db.recuperar_girias_usuario(self.usuario) if self.usuario else []
|
| 79 |
self.termo_contexto = {
|
| 80 |
+
termo['giria']: {"significado": termo['significado'], "frequencia": termo['frequencia']}
|
| 81 |
for termo in termos_aprendidos
|
| 82 |
}
|
| 83 |
except Exception as e:
|
|
|
|
| 89 |
if emocao_salva:
|
| 90 |
emocao_dict = json.loads(emocao_salva)
|
| 91 |
if isinstance(emocao_dict, dict) and 'emocao' in emocao_dict:
|
| 92 |
+
self.emocao_atual = emocao_dict['emocao']
|
| 93 |
elif isinstance(emocao_salva, str):
|
| 94 |
+
self.emocao_atual = emocao_salva
|
| 95 |
except Exception as e:
|
| 96 |
logger.warning(f"Falha ao carregar emoção do DB: {e}")
|
| 97 |
+
|
| 98 |
+
logger.info(f"Aprendizados carregados para {self.usuario}.")
|
| 99 |
|
| 100 |
@property
|
| 101 |
def ton_predominante(self) -> Optional[str]:
|
| 102 |
+
"""Retorna o tom predominante do usuário (acessa o DB)."""
|
|
|
|
|
|
|
|
|
|
| 103 |
if self.usuario:
|
| 104 |
return self.db.obter_tom_predominante(self.usuario)
|
| 105 |
return None
|
| 106 |
|
| 107 |
def get_or_create_treinador(self, interval_hours: int = 24) -> Treinamento:
|
| 108 |
+
"""Retorna um treinador associado, criando se necessário."""
|
| 109 |
if self._treinador is None:
|
| 110 |
self._treinador = Treinamento(self.db, contexto=self, interval_hours=interval_hours)
|
| 111 |
return self._treinador
|
| 112 |
|
| 113 |
def _load_model(self):
|
| 114 |
+
"""Carrega o modelo SentenceTransformer sob demanda."""
|
| 115 |
if self.model is not None:
|
| 116 |
return
|
|
|
|
|
|
|
|
|
|
| 117 |
if SentenceTransformer is None:
|
| 118 |
+
logger.warning("SentenceTransformer não instalado")
|
| 119 |
return
|
|
|
|
| 120 |
try:
|
| 121 |
self.model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 122 |
+
logger.info("Modelo SentenceTransformer carregado")
|
| 123 |
except Exception as e:
|
| 124 |
+
logger.error(f"Erro ao carregar modelo: {e}")
|
| 125 |
self.model = None
|
|
|
|
| 126 |
self._check_embeddings()
|
|
|
|
|
|
|
| 127 |
|
| 128 |
def _check_embeddings(self):
|
| 129 |
+
"""Verifica ou cria embeddings no banco."""
|
| 130 |
if self.model and not self.embeddings:
|
| 131 |
+
self.embeddings = {"conhecimento_base": "placeholder"}
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
def analisar_emocoes_mensagem(self, mensagem: str) -> Dict[str, Any]:
|
| 134 |
+
"""Analisa sentimento e emoção da mensagem (heurística)."""
|
|
|
|
|
|
|
|
|
|
| 135 |
mensagem_lower = mensagem.strip().lower()
|
|
|
|
|
|
|
| 136 |
pos_count = sum(mensagem_lower.count(w) for w in PALAVRAS_POSITIVAS)
|
| 137 |
neg_count = sum(mensagem_lower.count(w) for w in PALAVRAS_NEGATIVAS)
|
| 138 |
+
|
| 139 |
sentimento = "neutro"
|
| 140 |
if pos_count > neg_count:
|
| 141 |
sentimento = "positivo"
|
| 142 |
elif neg_count > pos_count:
|
| 143 |
sentimento = "negativo"
|
| 144 |
+
|
| 145 |
+
emocao_predominante = "alegria" if sentimento == "positivo" else "frustração" if sentimento == "negativo" else "neutra"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
self.emocao_atual = emocao_predominante
|
| 147 |
+
|
| 148 |
return {
|
| 149 |
"sentimento_detectado": sentimento,
|
| 150 |
"emocao_predominante": emocao_predominante,
|
|
|
|
| 152 |
"intensidade_negativa": neg_count,
|
| 153 |
"tom_sugerido": "casual" if sentimento != "neutro" else "neutro"
|
| 154 |
}
|
| 155 |
+
|
| 156 |
def analisar_intencao_e_normalizar(self, mensagem: str, historico: List[Tuple[str, str]]) -> Dict[str, Any]:
|
| 157 |
+
"""Analisa intenção, normaliza e detecta estilo."""
|
| 158 |
self._load_model()
|
|
|
|
| 159 |
if not isinstance(mensagem, str):
|
| 160 |
mensagem = str(mensagem)
|
| 161 |
mensagem_lower = mensagem.strip().lower()
|
| 162 |
|
| 163 |
+
# Intenção
|
| 164 |
intencao = "pergunta"
|
| 165 |
+
if '?' not in mensagem_lower and 'porquê' not in mensagem_lower and 'porque' not in mensagem_lower:
|
| 166 |
intencao = "afirmacao"
|
| 167 |
if any(w in mensagem_lower for w in ['ola', 'oi', 'bom dia', 'boa tarde', 'boa noite', 'como vai']):
|
| 168 |
intencao = "saudacao"
|
| 169 |
if any(w in mensagem_lower for w in ['tchau', 'ate mais', 'adeus', 'fim', 'parar']):
|
| 170 |
intencao = "despedida"
|
| 171 |
+
|
| 172 |
+
# Sentimento
|
| 173 |
analise_emocional = self.analisar_emocoes_mensagem(mensagem_lower)
|
| 174 |
+
|
| 175 |
+
# Estilo
|
| 176 |
estilo = "informal"
|
| 177 |
if len(re.findall(r'[A-ZÀ-Ÿ]{3,}', mensagem)) >= 2 or re.search(r'\b(Senhor|Doutor|Atenciosamente)\b', mensagem, re.IGNORECASE):
|
| 178 |
estilo = "formal"
|
| 179 |
+
|
|
|
|
|
|
|
|
|
|
| 180 |
usar_nome = random.random() < getattr(config, 'USAR_NOME_PROBABILIDADE', 0.7)
|
| 181 |
|
| 182 |
return {
|
| 183 |
"texto_normalizado": mensagem_lower,
|
| 184 |
"intencao": intencao,
|
| 185 |
+
"sentimento": analise_emocional['sentimento_detectado'],
|
| 186 |
"estilo": estilo,
|
| 187 |
"contexto_ajustado": self.substituir_termos_aprendidos(mensagem_lower),
|
| 188 |
+
"ironia": False,
|
| 189 |
+
"meia_frase": False,
|
| 190 |
"usar_nome": usar_nome,
|
| 191 |
+
"emocao": self.emocao_atual
|
| 192 |
}
|
| 193 |
|
| 194 |
def obter_historico(self, limite: int = 5) -> List[Tuple[str, str]]:
|
| 195 |
+
"""Recupera histórico do banco."""
|
| 196 |
if not self.usuario:
|
| 197 |
return []
|
| 198 |
+
raw = self.db.recuperar_mensagens(self.usuario, limite=limite)
|
| 199 |
+
return raw if raw else []
|
| 200 |
+
|
| 201 |
+
def obter_historico_para_llm(self) -> List[Dict]:
|
| 202 |
+
"""Formato esperado pelo LLMManager.generate()"""
|
| 203 |
+
raw = self.obter_historico(limite=10)
|
| 204 |
+
history = []
|
| 205 |
+
for user_msg, bot_msg in raw:
|
| 206 |
+
history.append({"role": "user", "content": user_msg})
|
| 207 |
+
history.append({"role": "assistant", "content": bot_msg})
|
| 208 |
+
return history
|
| 209 |
|
| 210 |
def atualizar_contexto(self, mensagem: str, resposta: str, numero: Optional[str] = None):
|
| 211 |
+
"""Salva interação e aprende."""
|
| 212 |
+
usuario = self.usuario or 'anonimo'
|
| 213 |
+
final_numero = numero or self.usuario
|
| 214 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
try:
|
| 216 |
self.db.salvar_mensagem(usuario, mensagem, resposta, numero=final_numero)
|
|
|
|
| 217 |
historico = self.obter_historico(limite=10)
|
| 218 |
+
self.aprender_do_historico(mensagem, resposta, historico)
|
|
|
|
| 219 |
self.salvar_estado_contexto_no_db(final_numero)
|
|
|
|
| 220 |
except Exception as e:
|
| 221 |
+
logger.warning(f'Falha ao salvar: {e}')
|
| 222 |
|
| 223 |
def salvar_estado_contexto_no_db(self, user_key: str):
|
| 224 |
+
"""Persiste estado no DB."""
|
| 225 |
termos_json = json.dumps(self.termo_contexto)
|
|
|
|
|
|
|
| 226 |
try:
|
| 227 |
+
self.db.salvar_aprendizado_detalhado(user_key, "emocao_atual", json.dumps({"emocao": self.emocao_atual}))
|
|
|
|
| 228 |
self.db.salvar_contexto(
|
| 229 |
user_key=user_key,
|
| 230 |
+
historico="[]",
|
| 231 |
+
emocao_atual=self.emocao_atual,
|
| 232 |
termos=termos_json,
|
| 233 |
girias=termos_json,
|
| 234 |
+
tom=self.emocao_atual
|
| 235 |
)
|
|
|
|
| 236 |
except Exception as e:
|
| 237 |
+
logger.error(f"Falha ao salvar contexto: {e}")
|
|
|
|
| 238 |
|
| 239 |
def aprender_do_historico(self, mensagem: str, resposta: str, historico: List[Tuple[str, str]]):
|
| 240 |
+
"""Aprende gírias do histórico."""
|
| 241 |
if not self.usuario:
|
| 242 |
return
|
|
|
|
| 243 |
mensagem_lower = mensagem.lower()
|
| 244 |
+
girias_angolanas_simples = ['ya', 'bué', 'fixe', 'puto', 'kota', 'mwangolé']
|
| 245 |
+
|
|
|
|
|
|
|
| 246 |
for giria in girias_angolanas_simples:
|
| 247 |
if giria in mensagem_lower:
|
| 248 |
try:
|
| 249 |
+
significado = f'termo regional para {giria}'
|
| 250 |
+
self.db.salvar_giria_aprendida(self.usuario, giria, significado, mensagem[:50])
|
| 251 |
+
self.termo_contexto[giria] = {
|
| 252 |
+
"significado": significado,
|
| 253 |
+
"frequencia": self.termo_contexto.get(giria, {}).get("frequencia", 0) + 1
|
| 254 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
except Exception as e:
|
| 256 |
+
logger.warning(f"Erro ao salvar gíria: {e}")
|
| 257 |
+
|
| 258 |
def substituir_termos_aprendidos(self, mensagem: str) -> str:
|
| 259 |
+
"""Substitui termos aprendidos."""
|
| 260 |
for termo, info in self.termo_contexto.items():
|
| 261 |
if isinstance(info, dict) and "significado" in info:
|
|
|
|
| 262 |
mensagem = re.sub(r'\b' + re.escape(termo) + r'\b', info["significado"], mensagem, flags=re.IGNORECASE)
|
| 263 |
return mensagem
|
| 264 |
+
|
| 265 |
def obter_aprendizado_detalhado(self, chave: str) -> Optional[Dict]:
|
| 266 |
+
"""Recupera aprendizado detalhado."""
|
| 267 |
try:
|
| 268 |
+
raw = self.db.recuperar_aprendizado_detalhado(self.usuario, chave)
|
| 269 |
+
return json.loads(raw) if raw else None
|
|
|
|
|
|
|
| 270 |
except Exception as e:
|
| 271 |
+
logger.warning(f"Erro ao obter aprendizado: {e}")
|
| 272 |
return None
|
| 273 |
+
|
| 274 |
def obter_emocao_atual(self) -> str:
|
|
|
|
| 275 |
return self.emocao_atual
|
| 276 |
|
| 277 |
def ativar_espírito_crítico(self):
|
|
|
|
| 278 |
self.espírito_crítico = True
|
| 279 |
|
| 280 |
def obter_aprendizados(self) -> Dict[str, Any]:
|
| 281 |
+
"""Retorna todos os aprendizados."""
|
| 282 |
+
return {
|
| 283 |
"termos": self.termo_contexto,
|
| 284 |
"emocao_preferida": self.emocao_atual,
|
| 285 |
+
"ton_predominante": self.ton_predominante
|
| 286 |
}
|
|
|
|
| 287 |
|
| 288 |
def salvar_conhecimento_base(self, chave: str, valor: Any):
|
|
|
|
| 289 |
self.base_conhecimento[chave] = valor
|
| 290 |
|
| 291 |
def obter_conhecimento_base(self, chave: str) -> Optional[Any]:
|
|
|
|
| 292 |
return self.base_conhecimento.get(chave)
|