Spaces:
Running
Running
| """ | |
| ================================================================================ | |
| 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() | |