Spaces:
Running
Running
File size: 7,544 Bytes
ba70b93 | 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 | """
================================================================================
AKIRA V21 ULTIMATE - USER PROFILER (DOSSIÊ PSICOLÓGICO)
================================================================================
Módulo responsável pela coleta agressiva (mas silenciosa) de dados dos usuários.
Analisa conversas e extrai: Nomes, Endereços, Gostos, Gatilhos Emocionais,
Estilo de fala e outras preferências. Armazena tudo no banco de dados para
compor a resposta da Akira.
"""
import json
import logging
import threading
import time
from typing import Dict, Any, Optional
try:
from .database import Database
from . import config
except ImportError:
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from modules.database import Database
from modules import config
logger = logging.getLogger(__name__)
class UserProfiler:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self.db = Database()
self._initialized = True
logger.info("🟢 UserProfiler (Dossiê) inicializado.")
def _get_profile_key(self, user_id: str) -> str:
return f"dossie_psicologico_{user_id}"
def get_user_profile(self, user_id: str) -> Dict[str, Any]:
"""Retorna o dossiê completo do usuário."""
try:
dados = self.db.recuperar_aprendizado_detalhado(user_id, self._get_profile_key(user_id))
if dados:
if isinstance(dados, str):
return json.loads(dados)
return dados
except Exception as e:
logger.warning(f"Erro ao recuperar dossiê de {user_id}: {e}")
# Estrutura padrão de perfil vazio
return {
"nome_conhecido": "",
"estilo_comunicacao": "Desconhecido",
"gatilhos_emocionais": [],
"preferencias": [],
"dados_pessoais": [],
"ultima_analise": 0
}
def _save_user_profile(self, user_id: str, profile: Dict[str, Any]) -> None:
"""Salva o dossiê no banco de dados."""
try:
profile["ultima_analise"] = time.time()
self.db.salvar_aprendizado_detalhado(
user_id,
self._get_profile_key(user_id),
json.dumps(profile, ensure_ascii=False)
)
except Exception as e:
logger.error(f"Erro ao salvar dossiê de {user_id}: {e}")
def extrair_dados_assincrono(self, user_id: str, mensagem_usuario: str, resposta_bot: str, llm_manager=None):
"""Dispara a extração de dados em background usando a thread pool ou thread simples."""
thread = threading.Thread(
target=self.analisar_e_atualizar_perfil,
args=(user_id, mensagem_usuario, resposta_bot, llm_manager),
daemon=True
)
thread.start()
def analisar_e_atualizar_perfil(self, user_id: str, mensagem: str, resposta: str, llm_manager=None) -> None:
"""
Analisa a última interação para atualizar o dossiê.
Usa o LLM (se disponível) para extração silenciosa ou heurísticas avançadas.
"""
if not mensagem or len(mensagem.strip()) < 3:
return
perfil_atual = self.get_user_profile(user_id)
# Limite de processamento para não onerar APIs (1 vez a cada 30 mensagens aprox)
# Vamos fazer inferência simples para coletar nomes
mens_lower = mensagem.lower()
atualizou = False
# 1. Extração Hardcoded Básica (Fallback rápido)
# "me chamo X", "o meu nome é Y"
import re
nome_match = re.search(r'(me chamo|meu nome é|sou o|sou a) ([A-Za-zÀ-ÿ]+)', mens_lower)
if nome_match and not perfil_atual["nome_conhecido"]:
perfil_atual["nome_conhecido"] = nome_match.group(2).capitalize()
atualizou = True
# 2. Uso do LLM para Extração Agressiva Profunda (Dossiê)
# Limite de frequência: Apenas 1 a cada 10 mensagens (ou se for muito longa > 150 chars)
import random
deve_usar_llm = (random.random() < 0.1) or (len(mensagem) > 150)
if llm_manager is not None and deve_usar_llm:
# Monta prompt apenas para sumarizar a pessoa
prompt_extracao = f"""
Você é um analista comportamental silencioso. Analise a seguinte mensagem enviada por um usuário.
Extraia quaisquer informações relevantes (preferências, gostos, forma de se expressar, estado emocional implícito).
Responda APENAS com um JSON simples com chaves: "novas_preferencias" (lista), "estilo" (string), "emocional" (string).
Mensagem do usuário: "{mensagem}"
"""
try:
# Usa método síncrono da API configurada no projeto (ex: mistral)
# Como é background, pedimos via providers mais rápidos
provider = llm_manager.providers[0] if llm_manager.providers else None
if provider:
# Este try/except assume a estrutura do LLMManager de api.py
# Em caso de falha, ignora e segue a vida.
# 🔧 CORREÇÃO: Usando 'generate' em vez de 'generate_response'
resp_analise, _ = llm_manager.generate(
prompt_extracao,
context_history=[],
is_privileged=True
)
if resp_analise and resp_analise.strip().startswith('{'):
try:
dados_extraidos = json.loads(resp_analise)
if "novas_preferencias" in dados_extraidos and isinstance(dados_extraidos["novas_preferencias"], list):
for pref in dados_extraidos["novas_preferencias"]:
if pref not in perfil_atual["preferencias"]:
perfil_atual["preferencias"].append(pref)
atualizou = True
if "estilo" in dados_extraidos and len(dados_extraidos["estilo"]) > 4:
perfil_atual["estilo_comunicacao"] = dados_extraidos["estilo"]
atualizou = True
except json.JSONDecodeError:
pass
except Exception as e:
logger.debug(f"Falha na extração LLM para dossiê: {e}")
# Mantém listas em tamanho saudável
if len(perfil_atual["preferencias"]) > 20:
perfil_atual["preferencias"] = perfil_atual["preferencias"][-20:]
if atualizou:
self._save_user_profile(user_id, perfil_atual)
def get_user_profiler() -> UserProfiler:
"""Factory para instanciar o Profiler."""
return UserProfiler()
|