Spaces:
Running
Running
File size: 11,729 Bytes
410dc31 dce7dab 410dc31 65165a0 0c323cb 410dc31 0c323cb dce7dab 410dc31 dce7dab 410dc31 dce7dab 410dc31 dce7dab 410dc31 0c3fa43 410dc31 0c3fa43 dce7dab 0c3fa43 410dc31 0c3fa43 4b1ce16 4f32660 0c3fa43 410dc31 786df8a 0c3fa43 410dc31 0c3fa43 786df8a dce7dab 0c3fa43 dce7dab 410dc31 0c3fa43 dce7dab 410dc31 dce7dab 4f32660 410dc31 4b1ce16 0c3fa43 410dc31 9fb5b11 410dc31 4f32660 410dc31 4f32660 9fb5b11 4f32660 410dc31 4f32660 410dc31 4f32660 410dc31 9fb5b11 4b1ce16 410dc31 4b1ce16 dce7dab 410dc31 dce7dab 0c323cb dce7dab 410dc31 dce7dab 410dc31 dce7dab 410dc31 dce7dab 410dc31 dce7dab 0c323cb dce7dab 410dc31 0c3fa43 410dc31 4f32660 410dc31 4f32660 410dc31 4f32660 410dc31 4f32660 410dc31 4f32660 410dc31 0c3fa43 410dc31 dce7dab 0c3fa43 0c323cb 410dc31 0c3fa43 410dc31 0c3fa43 dce7dab 0c3fa43 dce7dab 410dc31 4f32660 410dc31 0c3fa43 410dc31 0c3fa43 4aeebe8 dce7dab 0c3fa43 dce7dab 410dc31 0c3fa43 410dc31 0c3fa43 410dc31 dce7dab 786df8a dce7dab 410dc31 0c3fa43 410dc31 786df8a 0c3fa43 410dc31 786df8a 0c3fa43 dce7dab 410dc31 4f32660 786df8a 410dc31 0c3fa43 4f32660 410dc31 4f32660 410dc31 4f32660 410dc31 4f32660 4b1ce16 410dc31 4f32660 410dc31 4f32660 0c3fa43 410dc31 0c3fa43 410dc31 0c3fa43 410dc31 4f32660 410dc31 0c3fa43 410dc31 0c3fa43 4f32660 0c3fa43 410dc31 0c3fa43 410dc31 0c3fa43 410dc31 4f32660 410dc31 0c3fa43 410dc31 0c3fa43 410dc31 0c3fa43 410dc31 0c3fa43 |
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 |
# modules/contexto.py
import logging
import re
import random
import time
import sqlite3
import json
from typing import Optional, List, Dict, Tuple, Any
import modules.config as config
from .database import Database
from .treinamento import Treinamento
try:
from sentence_transformers import SentenceTransformer
except Exception as e:
logging.warning(f"sentence_transformers não disponível: {e}")
SentenceTransformer = None
try:
import psutil
except Exception:
psutil = None
try:
import structlog
except Exception:
structlog = None
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
if structlog:
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_log_level,
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
)
# Palavras para análise de sentimento heurística
PALAVRAS_POSITIVAS = ['bom', 'ótimo', 'incrível', 'feliz', 'adorei', 'top', 'fixe', 'bué', 'show', 'legal', 'bacana']
PALAVRAS_NEGATIVAS = ['ruim', 'péssimo', 'triste', 'ódio', 'raiva', 'chateado', 'merda', 'porra', 'odeio']
class Contexto:
"""
Classe para gerenciar o contexto da conversa, análise de intenções e aprendizado
dinâmico de termos regionais/gírias para cada usuário.
"""
def __init__(self, db: Database, usuario: Optional[str] = None):
self.db = db
self.usuario = usuario
self.model: Optional[SentenceTransformer] = None
self.embeddings: Optional[Dict[str, Any]] = None
self._treinador: Optional[Treinamento] = None
# Estado de conversa
self.emocao_atual = "neutra"
self.espírito_crítico = False
self.base_conhecimento = {}
# Garante que termo_contexto seja sempre um dicionário
self.termo_contexto: Dict[str, Dict] = {}
self.atualizar_aprendizados_do_banco()
logger.info("Inicializando Contexto (com NLP avançado, aprendizado de gírias e emoções) ...")
# Cache para termos regionais e gírias
self.cache_girias: Dict[str, Any] = {}
def atualizar_aprendizados_do_banco(self):
"""Carrega todos os dados de aprendizado persistentes do banco."""
try:
termos_aprendidos = self.db.recuperar_girias_usuario(self.usuario) if self.usuario else []
self.termo_contexto = {
termo['giria']: {"significado": termo['significado'], "frequencia": termo['frequencia']}
for termo in termos_aprendidos
}
except Exception as e:
logger.warning(f"Falha ao carregar termos/gírias do DB: {e}")
self.termo_contexto = {}
try:
emocao_salva = self.db.recuperar_aprendizado_detalhado(self.usuario, "emocao_atual") if self.usuario else None
if emocao_salva:
emocao_dict = json.loads(emocao_salva)
if isinstance(emocao_dict, dict) and 'emocao' in emocao_dict:
self.emocao_atual = emocao_dict['emocao']
elif isinstance(emocao_salva, str):
self.emocao_atual = emocao_salva
except Exception as e:
logger.warning(f"Falha ao carregar emoção do DB: {e}")
logger.info(f"Aprendizados carregados para {self.usuario}.")
@property
def ton_predominante(self) -> Optional[str]:
"""Retorna o tom predominante do usuário (acessa o DB)."""
if self.usuario:
return self.db.obter_tom_predominante(self.usuario)
return None
def get_or_create_treinador(self, interval_hours: int = 24) -> Treinamento:
"""Retorna um treinador associado, criando se necessário."""
if self._treinador is None:
self._treinador = Treinamento(self.db, contexto=self, interval_hours=interval_hours)
return self._treinador
def _load_model(self):
"""Carrega o modelo SentenceTransformer sob demanda."""
if self.model is not None:
return
if SentenceTransformer is None:
logger.warning("SentenceTransformer não instalado")
return
try:
self.model = SentenceTransformer('all-MiniLM-L6-v2')
logger.info("Modelo SentenceTransformer carregado")
except Exception as e:
logger.error(f"Erro ao carregar modelo: {e}")
self.model = None
self._check_embeddings()
def _check_embeddings(self):
"""Verifica ou cria embeddings no banco."""
if self.model and not self.embeddings:
self.embeddings = {"conhecimento_base": "placeholder"}
def analisar_emocoes_mensagem(self, mensagem: str) -> Dict[str, Any]:
"""Analisa sentimento e emoção da mensagem (heurística)."""
mensagem_lower = mensagem.strip().lower()
pos_count = sum(mensagem_lower.count(w) for w in PALAVRAS_POSITIVAS)
neg_count = sum(mensagem_lower.count(w) for w in PALAVRAS_NEGATIVAS)
sentimento = "neutro"
if pos_count > neg_count:
sentimento = "positivo"
elif neg_count > pos_count:
sentimento = "negativo"
emocao_predominante = "alegria" if sentimento == "positivo" else "frustração" if sentimento == "negativo" else "neutra"
self.emocao_atual = emocao_predominante
return {
"sentimento_detectado": sentimento,
"emocao_predominante": emocao_predominante,
"intensidade_positiva": pos_count,
"intensidade_negativa": neg_count,
"tom_sugerido": "casual" if sentimento != "neutro" else "neutro"
}
def analisar_intencao_e_normalizar(self, mensagem: str, historico: List[Tuple[str, str]]) -> Dict[str, Any]:
"""Analisa intenção, normaliza e detecta estilo."""
self._load_model()
if not isinstance(mensagem, str):
mensagem = str(mensagem)
mensagem_lower = mensagem.strip().lower()
# Intenção
intencao = "pergunta"
if '?' not in mensagem_lower and 'porquê' not in mensagem_lower and 'porque' not in mensagem_lower:
intencao = "afirmacao"
if any(w in mensagem_lower for w in ['ola', 'oi', 'bom dia', 'boa tarde', 'boa noite', 'como vai']):
intencao = "saudacao"
if any(w in mensagem_lower for w in ['tchau', 'ate mais', 'adeus', 'fim', 'parar']):
intencao = "despedida"
# Sentimento
analise_emocional = self.analisar_emocoes_mensagem(mensagem_lower)
# Estilo
estilo = "informal"
if len(re.findall(r'[A-ZÀ-Ÿ]{3,}', mensagem)) >= 2 or re.search(r'\b(Senhor|Doutor|Atenciosamente)\b', mensagem, re.IGNORECASE):
estilo = "formal"
usar_nome = random.random() < getattr(config, 'USAR_NOME_PROBABILIDADE', 0.7)
return {
"texto_normalizado": mensagem_lower,
"intencao": intencao,
"sentimento": analise_emocional['sentimento_detectado'],
"estilo": estilo,
"contexto_ajustado": self.substituir_termos_aprendidos(mensagem_lower),
"ironia": False,
"meia_frase": False,
"usar_nome": usar_nome,
"emocao": self.emocao_atual
}
def obter_historico(self, limite: int = 5) -> List[Tuple[str, str]]:
"""Recupera histórico do banco."""
if not self.usuario:
return []
raw = self.db.recuperar_mensagens(self.usuario, limite=limite)
return raw if raw else []
def obter_historico_para_llm(self) -> List[Dict]:
"""Formato esperado pelo LLMManager.generate()"""
raw = self.obter_historico(limite=10)
history = []
for user_msg, bot_msg in raw:
history.append({"role": "user", "content": user_msg})
history.append({"role": "assistant", "content": bot_msg})
return history
def atualizar_contexto(self, mensagem: str, resposta: str, numero: Optional[str] = None):
"""Salva interação e aprende."""
usuario = self.usuario or 'anonimo'
final_numero = numero or self.usuario
try:
self.db.salvar_mensagem(usuario, mensagem, resposta, numero=final_numero)
historico = self.obter_historico(limite=10)
self.aprender_do_historico(mensagem, resposta, historico)
self.salvar_estado_contexto_no_db(final_numero)
except Exception as e:
logger.warning(f'Falha ao salvar: {e}')
def salvar_estado_contexto_no_db(self, user_key: str):
"""Persiste estado no DB."""
termos_json = json.dumps(self.termo_contexto)
try:
self.db.salvar_aprendizado_detalhado(user_key, "emocao_atual", json.dumps({"emocao": self.emocao_atual}))
self.db.salvar_contexto(
user_key=user_key,
historico="[]",
emocao_atual=self.emocao_atual,
termos=termos_json,
girias=termos_json,
tom=self.emocao_atual
)
except Exception as e:
logger.error(f"Falha ao salvar contexto: {e}")
def aprender_do_historico(self, mensagem: str, resposta: str, historico: List[Tuple[str, str]]):
"""Aprende gírias do histórico."""
if not self.usuario:
return
mensagem_lower = mensagem.lower()
girias_angolanas_simples = ['ya', 'bué', 'fixe', 'puto', 'kota', 'mwangolé']
for giria in girias_angolanas_simples:
if giria in mensagem_lower:
try:
significado = f'termo regional para {giria}'
self.db.salvar_giria_aprendida(self.usuario, giria, significado, mensagem[:50])
self.termo_contexto[giria] = {
"significado": significado,
"frequencia": self.termo_contexto.get(giria, {}).get("frequencia", 0) + 1
}
except Exception as e:
logger.warning(f"Erro ao salvar gíria: {e}")
def substituir_termos_aprendidos(self, mensagem: str) -> str:
"""Substitui termos aprendidos."""
for termo, info in self.termo_contexto.items():
if isinstance(info, dict) and "significado" in info:
mensagem = re.sub(r'\b' + re.escape(termo) + r'\b', info["significado"], mensagem, flags=re.IGNORECASE)
return mensagem
def obter_aprendizado_detalhado(self, chave: str) -> Optional[Dict]:
"""Recupera aprendizado detalhado."""
try:
raw = self.db.recuperar_aprendizado_detalhado(self.usuario, chave)
return json.loads(raw) if raw else None
except Exception as e:
logger.warning(f"Erro ao obter aprendizado: {e}")
return None
def obter_emocao_atual(self) -> str:
return self.emocao_atual
def ativar_espírito_crítico(self):
self.espírito_crítico = True
def obter_aprendizados(self) -> Dict[str, Any]:
"""Retorna todos os aprendizados."""
return {
"termos": self.termo_contexto,
"emocao_preferida": self.emocao_atual,
"ton_predominante": self.ton_predominante
}
def salvar_conhecimento_base(self, chave: str, valor: Any):
self.base_conhecimento[chave] = valor
def obter_conhecimento_base(self, chave: str) -> Optional[Any]:
return self.base_conhecimento.get(chave) |