Spaces:
Sleeping
Sleeping
| # modules/api.py — AKIRA V21 FINAL INTEGRADO (Dezembro 2025) - COM TRANSIÇÃO GRADUAL | |
| """ | |
| ✅ TOTALMENTE INTEGRADO com database.py corrigido (message_id sem UNIQUE) | |
| ✅ CORREÇÃO: Usa métodos corretos do Database atualizado | |
| ✅ COMPATÍVEL com index.js e reply_metadata | |
| ✅ Sistema multi-API com fallback | |
| ✅ Cache de contexto otimizado | |
| ✅ Treinamento automático integrado | |
| ✅ ADAPTADO: Sistema de transição gradual para usuários privilegiados | |
| ✅ SIMPLIFICADO: Usa apenas config.py para toda lógica de transição | |
| ✅ TRANSIÇÃO: 3 níveis para privilegiados seguindo tom do usuário | |
| ✅ INSTABILIDADE: Não mantém formal se conversa mudar para descontraído | |
| """ | |
| import time | |
| import datetime | |
| import requests | |
| import os | |
| import json | |
| import random | |
| import re | |
| from typing import Dict, Any, List, Optional | |
| from flask import Blueprint, request, jsonify, make_response | |
| from loguru import logger | |
| # Importa módulos locais - CORRETAMENTE | |
| from .contexto import Contexto, criar_contexto | |
| from .database import Database | |
| from .treinamento import Treinamento | |
| import modules.config as config | |
| # ============================================================================ | |
| # 🔥 CACHE SIMPLES COM TRANSIÇÃO | |
| # ============================================================================ | |
| class SimpleTTLCache: | |
| def __init__(self, ttl_seconds: int = 300): | |
| self.ttl = ttl_seconds | |
| self._store = {} | |
| def __contains__(self, key): | |
| if key not in self._store: | |
| return False | |
| _, expires = self._store[key] | |
| if time.time() > expires: | |
| del self._store[key] | |
| return False | |
| return True | |
| def __setitem__(self, key, value): | |
| self._store[key] = (value, time.time() + self.ttl) | |
| def __getitem__(self, key): | |
| if key not in self: | |
| raise KeyError(key) | |
| return self._store[key][0] | |
| def get(self, key, default=None): | |
| try: | |
| return self[key] | |
| except KeyError: | |
| return default | |
| # ============================================================================ | |
| # 🧠 GERENCIADOR MULTI-API (OTIMIZADO PARA CONFIG.PY) | |
| # ============================================================================ | |
| class MultiAPIManager: | |
| def __init__(self): | |
| self.timeout = config.API_TIMEOUT | |
| self.apis_disponiveis = self._verificar_apis() | |
| logger.info(f"✅ APIs disponíveis: {', '.join(self.apis_disponiveis)}") | |
| def _verificar_apis(self): | |
| """Verifica quais APIs estão disponíveis""" | |
| apis = [] | |
| if config.MISTRAL_API_KEY and len(config.MISTRAL_API_KEY) > 10: | |
| apis.append("mistral") | |
| if config.GEMINI_API_KEY and config.GEMINI_API_KEY.startswith('AIza'): | |
| apis.append("gemini") | |
| if config.GROQ_API_KEY and len(config.GROQ_API_KEY) > 10: | |
| apis.append("groq") | |
| if config.COHERE_API_KEY and len(config.COHERE_API_KEY) > 10: | |
| apis.append("cohere") | |
| return apis | |
| def _construir_prompt( | |
| self, | |
| mensagem: str, | |
| historico: List[Dict[str, str]], | |
| mensagem_citada: str, | |
| analise: Dict[str, Any], | |
| usuario: str, | |
| tipo_conversa: str, | |
| reply_info: Optional[Dict] = None | |
| ) -> str: | |
| """ | |
| Constrói prompt usando config.py - COM TRANSIÇÃO GRADUAL | |
| """ | |
| try: | |
| logger.debug(f"📝 Construindo prompt para: {usuario}") | |
| logger.debug(f"📝 Mensagem citada: {mensagem_citada[:80] if mensagem_citada else '[Nenhuma]'}...") | |
| # Usa a função do config.py | |
| prompt = config.construir_prompt_api( | |
| mensagem=mensagem, | |
| historico=historico, | |
| mensagem_citada=mensagem_citada, | |
| analise=analise, | |
| usuario=usuario, | |
| tipo_conversa=tipo_conversa, | |
| reply_info=reply_info | |
| ) | |
| logger.debug(f"✅ Prompt construído: {len(prompt)} caracteres") | |
| return prompt | |
| except Exception as e: | |
| logger.error(f"❌ Erro ao construir prompt: {e}") | |
| # Fallback simples | |
| return f"""AKIRA V21 — PERSONALIDADE REAL SEM FILTRO | |
| Você é Akira, angolana de 18 anos de Luanda. Angolana pura e patritota | |
| USUÁRIO: {usuario} | |
| MENSAGEM: {mensagem} | |
| MENSAGEM CITADA: {mensagem_citada if mensagem_citada else 'Nenhuma'} : entenda quem citou a messagem cidada, quem escreveu originalmete para vc sabe se foi vc ou outro caso seja de outro por exemplo: messagem citada de dani texto: kkk olha isso akira a messagem a ser citada de tiago: importante é que todos se fodam. nese casso vc deve entender que a messagem aser citada é a de tiago, e o dani citou essa messgem e também te mencionou ouseja que sua popinião sobre essa messagwem citada como ele dissse "olha isso " nesse caso vc deve encarar que não foii vc que disse, por isso não pode assumeir que disse nenhuma dessas coisas, no entanto pode dar sua opinião | |
| RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS, mas dependo do tom que tá, não delire, tenha uma conversa focada, e racional):""" | |
| def _obter_parametros_api(self, analise: Dict[str, Any]) -> Dict[str, Any]: | |
| """Obtém parâmetros usando config.py""" | |
| emocao = analise.get("emocao_primaria", "neutral") | |
| modo = analise.get("modo_resposta", "normal_ironico") | |
| return config.obter_parametros_api(emocao, modo) | |
| def gerar_resposta( | |
| self, | |
| mensagem: str, | |
| historico: List[Dict[str, str]], | |
| mensagem_citada: str, | |
| analise: Dict[str, Any], | |
| usuario: str, | |
| tipo_conversa: str, | |
| reply_info: Optional[Dict] = None | |
| ) -> str: | |
| """Gera resposta usando multi-API fallback""" | |
| logger.info(f"🎯 Gerando resposta para {usuario}") | |
| logger.info(f"📤 Mensagem: {mensagem[:80]}...") | |
| # Construir prompt | |
| prompt = self._construir_prompt( | |
| mensagem=mensagem, | |
| historico=historico, | |
| mensagem_citada=mensagem_citada, | |
| analise=analise, | |
| usuario=usuario, | |
| tipo_conversa=tipo_conversa, | |
| reply_info=reply_info | |
| ) | |
| # Obter parâmetros | |
| parametros = self._obter_parametros_api(analise) | |
| logger.debug(f"🔧 Parâmetros: {parametros}") | |
| # Tentar APIs em ordem | |
| for api_name in config.API_FALLBACK_ORDER: | |
| if api_name not in self.apis_disponiveis: | |
| continue | |
| try: | |
| logger.debug(f"🔄 Tentando API: {api_name}") | |
| resposta = self._chamar_api(api_name, prompt, parametros) | |
| if resposta: | |
| resposta_limpa = self._limpar_resposta(resposta) | |
| logger.info(f"✅ API {api_name} respondeu: {resposta_limpa[:80]}...") | |
| return resposta_limpa | |
| except Exception as e: | |
| logger.warning(f"❌ API {api_name} falhou: {str(e)[:100]}") | |
| continue | |
| # Fallback contextual | |
| fallback = self._gerar_fallback_contextual(mensagem, mensagem_citada, reply_info) | |
| logger.info(f"🔄 Usando fallback: {fallback}") | |
| return fallback | |
| def _chamar_api(self, api_name: str, prompt: str, params: Dict[str, Any]) -> str: | |
| """Chama API específica""" | |
| try: | |
| if api_name == "mistral": | |
| return self._chamar_mistral(prompt, params) | |
| elif api_name == "gemini": | |
| return self._chamar_gemini(prompt, params) | |
| elif api_name == "groq": | |
| return self._chamar_groq(prompt, params) | |
| elif api_name == "cohere": | |
| return self._chamar_cohere(prompt, params) | |
| except Exception as e: | |
| logger.error(f"Erro ao chamar {api_name}: {e}") | |
| return "" | |
| def _chamar_mistral(self, prompt: str, params: Dict[str, Any]) -> str: | |
| """Chama Mistral API""" | |
| try: | |
| response = requests.post( | |
| "https://api.mistral.ai/v1/chat/completions", | |
| headers={"Authorization": f"Bearer {config.MISTRAL_API_KEY}"}, | |
| json={ | |
| "model": config.MISTRAL_MODEL, | |
| "messages": [{"role": "user", "content": prompt}], | |
| "max_tokens": params.get("max_tokens", config.MAX_TOKENS), | |
| "temperature": params.get("temperature", config.TEMPERATURE), | |
| "top_p": params.get("top_p", config.TOP_P), | |
| "frequency_penalty": params.get("frequency_penalty", config.FREQUENCY_PENALTY), | |
| "presence_penalty": params.get("presence_penalty", config.PRESENCE_PENALTY) | |
| }, | |
| timeout=self.timeout | |
| ) | |
| response.raise_for_status() | |
| return response.json()["choices"][0]["message"]["content"].strip() | |
| except Exception as e: | |
| logger.error(f"Mistral falhou: {e}") | |
| return "" | |
| def _chamar_gemini(self, prompt: str, params: Dict[str, Any]) -> str: | |
| """Chama Gemini API""" | |
| try: | |
| response = requests.post( | |
| f"https://generativelanguage.googleapis.com/v1beta/models/{config.GEMINI_MODEL}:generateContent?key={config.GEMINI_API_KEY}", | |
| json={ | |
| "contents": [{"parts": [{"text": prompt}]}], | |
| "generationConfig": { | |
| "temperature": params.get("temperature", config.TEMPERATURE), | |
| "maxOutputTokens": params.get("max_tokens", config.MAX_TOKENS) | |
| } | |
| }, | |
| timeout=self.timeout | |
| ) | |
| response.raise_for_status() | |
| return response.json()["candidates"][0]["content"]["parts"][0]["text"].strip() | |
| except Exception as e: | |
| logger.error(f"Gemini falhou: {e}") | |
| return "" | |
| def _chamar_groq(self, prompt: str, params: Dict[str, Any]) -> str: | |
| """Chama Groq API""" | |
| try: | |
| response = requests.post( | |
| "https://api.groq.com/openai/v1/chat/completions", | |
| headers={"Authorization": f"Bearer {config.GROQ_API_KEY}"}, | |
| json={ | |
| "model": config.GROQ_MODEL, | |
| "messages": [{"role": "user", "content": prompt}], | |
| "max_tokens": params.get("max_tokens", config.MAX_TOKENS), | |
| "temperature": params.get("temperature", config.TEMPERATURE) | |
| }, | |
| timeout=self.timeout | |
| ) | |
| response.raise_for_status() | |
| return response.json()["choices"][0]["message"]["content"].strip() | |
| except Exception as e: | |
| logger.error(f"Groq falhou: {e}") | |
| return "" | |
| def _chamar_cohere(self, prompt: str, params: Dict[str, Any]) -> str: | |
| """Chama Cohere API""" | |
| try: | |
| response = requests.post( | |
| "https://api.cohere.ai/v1/generate", | |
| headers={"Authorization": f"Bearer {config.COHERE_API_KEY}"}, | |
| json={ | |
| "prompt": prompt, | |
| "max_tokens": params.get("max_tokens", config.MAX_TOKENS), | |
| "temperature": params.get("temperature", config.TEMPERATURE) | |
| }, | |
| timeout=self.timeout | |
| ) | |
| response.raise_for_status() | |
| return response.json()["generations"][0]["text"].strip() | |
| except Exception as e: | |
| logger.error(f"Cohere falhou: {e}") | |
| return "" | |
| def _limpar_resposta(self, texto: str) -> str: | |
| """Limpa a resposta""" | |
| if not texto: | |
| return "…" | |
| # Remove markdown | |
| texto = re.sub(r'[\*`_]+', '', texto) | |
| # Remove aspas | |
| texto = texto.strip('"\'') | |
| # Remove prefixos | |
| texto = re.sub(r'^(Akira|AKIRA)[:\s\-]+', '', texto, flags=re.IGNORECASE) | |
| # Remove espaços extras | |
| texto = re.sub(r'\s+', ' ', texto) | |
| # Limita tamanho | |
| if len(texto) > 400: | |
| last_period = texto[:397].rfind('.') | |
| if last_period > 300: | |
| texto = texto[:last_period + 1] | |
| else: | |
| texto = texto[:397] + "..." | |
| return texto.strip() | |
| # ============================================================================ | |
| # 🎯 CLASSE PRINCIPAL AKIRA API (COM TRANSIÇÃO GRADUAL) | |
| # ============================================================================ | |
| class AkiraAPI: | |
| def __init__(self): | |
| """Inicializa API totalmente integrada""" | |
| self.api = Blueprint("akira_api", __name__) | |
| self.contexto_cache = SimpleTTLCache(ttl_seconds=300) | |
| # Inicializa Database CORRETAMENTE | |
| self.db = Database(config.DB_PATH) | |
| self.llm_manager = MultiAPIManager() | |
| # Configura treinamento se habilitado | |
| if config.START_PERIODIC_TRAINER: | |
| try: | |
| self.treinador = Treinamento( | |
| self.db, | |
| interval_hours=config.TRAINING_INTERVAL_HOURS | |
| ) | |
| self.treinador.start_periodic_training() | |
| logger.info("✅ Treinamento periódico iniciado") | |
| except Exception as e: | |
| logger.error(f"❌ Erro no treinador: {e}") | |
| self.treinador = None | |
| self._setup_routes() | |
| logger.success("🚀 AKIRA V21 FINAL inicializada (com transição gradual)") | |
| def _get_user_context(self, numero: str, tipo_conversa: str, grupo_id: str = "") -> Contexto: | |
| """Obtém contexto isolado""" | |
| if tipo_conversa == "grupo" and grupo_id: | |
| key = f"grupo_{grupo_id}" | |
| else: | |
| key = f"pv_{numero}" | |
| # Cache | |
| if key in self.contexto_cache: | |
| return self.contexto_cache[key] | |
| # Cria novo contexto usando a função correta | |
| ctx = criar_contexto(self.db, key, tipo_conversa) | |
| self.contexto_cache[key] = ctx | |
| return ctx | |
| def _processar_reset(self, numero: str, usuario: str, confirmacao: bool = False) -> Dict[str, Any]: | |
| """Processa comando /reset""" | |
| # Verifica permissão usando método CORRETO | |
| if not self.db.pode_usar_reset(numero): | |
| return { | |
| "error": "COMANDO RESTRITO", | |
| "resposta": "Só o boss pode usar /reset, puto." | |
| } | |
| if not confirmacao: | |
| return { | |
| "resposta": "Quer mesmo apagar tudo? Manda /reset de novo." | |
| } | |
| # Limpa cache | |
| self.contexto_cache._store.clear() | |
| # Reseta no banco | |
| resultado = self.db.resetar_contexto_usuario(numero, "completo") | |
| return { | |
| "resposta": f"Reset feito! {resultado.get('itens_apagados', 0)} itens apagados." | |
| } | |
| def _extrair_payload_indexjs(self, data: Dict[str, Any]) -> Dict[str, Any]: | |
| """Extrai dados do payload do index.js - ATUALIZADO""" | |
| payload = { | |
| 'numero': str(data.get('numero', '')).strip(), | |
| 'usuario': data.get('usuario', 'Anônimo').strip(), | |
| 'mensagem': data.get('mensagem', '').strip(), | |
| 'tipo_conversa': data.get('tipo_conversa', 'pv'), | |
| 'tipo_mensagem': data.get('tipo_mensagem', 'texto'), | |
| 'grupo_id': data.get('grupo_id', ''), | |
| 'grupo_nome': data.get('grupo_nome', ''), | |
| 'is_reset': False, | |
| 'reply_metadata': data.get('reply_metadata', {}), | |
| 'mensagem_citada': data.get('mensagem_citada', '') | |
| } | |
| # Log básico | |
| logger.debug(f"📦 Payload recebido de {payload['usuario']}") | |
| # Detecta /reset | |
| if payload['mensagem'].strip().lower() == '/reset': | |
| payload['is_reset'] = True | |
| return payload | |
| def _setup_routes(self): | |
| """Configura rotas da API""" | |
| def handle_options(): | |
| """Lida com CORS preflight""" | |
| if request.method == 'OPTIONS': | |
| resp = make_response() | |
| resp.headers['Access-Control-Allow-Origin'] = '*' | |
| resp.headers['Access-Control-Allow-Headers'] = 'Content-Type' | |
| resp.headers['Access-Control-Allow-Methods'] = 'POST,GET' | |
| return resp | |
| def add_cors(resp): | |
| """Adiciona headers CORS""" | |
| resp.headers['Access-Control-Allow-Origin'] = '*' | |
| return resp | |
| def akira_endpoint(): | |
| """Endpoint principal - COM TRANSIÇÃO GRADUAL""" | |
| try: | |
| # Recebe payload | |
| data = request.get_json() or {} | |
| payload = self._extrair_payload_indexjs(data) | |
| logger.info( | |
| f"📨 [{payload['usuario']}] ({payload['numero']}): " | |
| f"{payload['mensagem'][:80]}..." | |
| ) | |
| # Validação | |
| if not payload['mensagem']: | |
| return jsonify({'error': 'Mensagem obrigatória'}), 400 | |
| # Comando /reset | |
| if payload['is_reset']: | |
| resultado = self._processar_reset( | |
| payload['numero'], | |
| payload['usuario'], | |
| confirmacao=False | |
| ) | |
| if 'error' in resultado: | |
| return jsonify(resultado), 403 | |
| return jsonify(resultado) | |
| # Obtém contexto | |
| contexto = self._get_user_context( | |
| numero=payload['numero'], | |
| tipo_conversa=payload['tipo_conversa'], | |
| grupo_id=payload['grupo_id'] | |
| ) | |
| # Atualiza informações do usuário | |
| contexto.atualizar_informacoes_usuario( | |
| nome=payload['usuario'], | |
| numero=payload['numero'], | |
| grupo_id=payload['grupo_id'], | |
| grupo_nome=payload['grupo_nome'], | |
| tipo_conversa=payload['tipo_conversa'] | |
| ) | |
| # Obtém histórico | |
| historico = contexto.obter_historico_para_llm() | |
| # VERIFICA USUÁRIO PRIVILEGIADO E TRANSIÇÃO | |
| usuario_privilegiado = config.eh_usuario_privilegiado(payload['numero']) | |
| modo_inicial = config.forcar_modo_inicial_privilegiado(payload['numero']) | |
| # Analisa a mensagem | |
| analise = contexto.analisar_intencao_e_normalizar( | |
| mensagem=payload['mensagem'], | |
| historico=historico, | |
| mensagem_citada=payload['mensagem_citada'], | |
| reply_metadata=payload['reply_metadata'] | |
| ) | |
| # ANALISA TOM DO USUÁRIO PARA TRANSIÇÃO | |
| analise_tom = config.analisar_tom_usuario(payload['mensagem'], historico) | |
| # Obtém nível atual de transição | |
| nivel_transicao_atual = analise.get('nivel_transicao', 1 if usuario_privilegiado else 0) | |
| # Histórico recente para análise de transição | |
| historico_recente = historico[-10:] if len(historico) >= 10 else historico | |
| # DETERMINA TRANSIÇÃO SE FOR PRIVILEGIADO | |
| if usuario_privilegiado: | |
| info_transicao = config.determinar_nivel_transicao( | |
| payload['numero'], | |
| analise_tom, | |
| nivel_transicao_atual, | |
| historico_recente | |
| ) | |
| # Atualiza modo baseado na transição | |
| analise['modo_resposta'] = info_transicao['modo'] | |
| analise['nivel_transicao'] = info_transicao['nivel'] | |
| analise['info_transicao'] = info_transicao | |
| logger.info(f"👑 Usuário privilegiado {payload['numero']} - Nível: {info_transicao['nivel']} ({info_transicao['desc']})") | |
| # Adiciona informações extras | |
| analise.update({ | |
| 'usuario_privilegiado': usuario_privilegiado, | |
| 'numero': payload['numero'], | |
| 'tipo_mensagem': payload['tipo_mensagem'], | |
| 'reply_metadata': payload['reply_metadata'] | |
| }) | |
| # Prepara reply_info para config.py | |
| reply_info_for_config = payload['reply_metadata'] | |
| # Gera resposta | |
| resposta = self.llm_manager.gerar_resposta( | |
| mensagem=payload['mensagem'], | |
| historico=historico, | |
| mensagem_citada=payload['mensagem_citada'], | |
| analise=analise, | |
| usuario=payload['usuario'], | |
| tipo_conversa=payload['tipo_conversa'], | |
| reply_info=reply_info_for_config | |
| ) | |
| # Determina se é reply | |
| is_reply = bool(payload['mensagem_citada']) or ( | |
| payload['reply_metadata'] and payload['reply_metadata'].get('is_reply', False) | |
| ) | |
| reply_to_bot = False | |
| if payload['reply_metadata']: | |
| reply_to_bot = payload['reply_metadata'].get('reply_to_bot', False) | |
| # Mede tempo de resposta | |
| tempo_resposta_ms = int((time.time() - request.start_time) * 1000) if hasattr(request, 'start_time') else 0 | |
| # CORREÇÃO: Prepara reply_info_json para o Database | |
| reply_info_json = None | |
| if payload['reply_metadata']: | |
| reply_info_json = json.dumps(payload['reply_metadata'], ensure_ascii=False) | |
| # Atualiza contexto usando o Database CORRIGIDO | |
| contexto.atualizar_contexto( | |
| mensagem=payload['mensagem'], | |
| resposta=resposta, | |
| numero=payload['numero'], | |
| is_reply=is_reply, | |
| mensagem_original=payload['mensagem_citada'], | |
| reply_to_bot=reply_to_bot | |
| ) | |
| # Salva mensagem diretamente no banco (backup) | |
| try: | |
| # Gera message_id único com timestamp e random | |
| timestamp = int(time.time() * 1000) | |
| random_suffix = random.randint(1000, 9999) | |
| message_id = f"{payload['numero']}_{timestamp}_{random_suffix}" | |
| # Salva nível de transição se for privilegiado | |
| nivel_salvar = analise.get('nivel_transicao', 0) | |
| desc_transicao = analise.get('info_transicao', {}).get('desc', 'N/A') | |
| self.db.salvar_mensagem( | |
| usuario=payload['usuario'], | |
| mensagem=payload['mensagem'], | |
| resposta=resposta, | |
| numero=payload['numero'], | |
| is_reply=is_reply, | |
| mensagem_original=payload['mensagem_citada'], | |
| reply_to_bot=reply_to_bot, | |
| humor=analise.get('humor_atualizado', 'normal_ironico'), | |
| modo_resposta=analise.get('modo_resposta', 'normal_ironico'), | |
| emocao_detectada=analise.get('emocao_primaria', 'neutral'), | |
| confianca_emocao=analise.get('confianca_emocao', 0.5), | |
| tipo_mensagem=payload['tipo_mensagem'], | |
| reply_info_json=reply_info_json, | |
| usuario_nome=payload['usuario'], | |
| grupo_id=payload['grupo_id'], | |
| grupo_nome=payload['grupo_nome'], | |
| tipo_conversa=payload['tipo_conversa'], | |
| message_id=message_id, | |
| bot_response_time_ms=tempo_resposta_ms, | |
| # Campos extras para transição | |
| nivel_transicao=nivel_salvar, | |
| desc_transicao=desc_transicao, | |
| usuario_privilegiado=usuario_privilegiado | |
| ) | |
| logger.debug(f"✅ Mensagem salva no banco com message_id: {message_id}") | |
| except Exception as db_error: | |
| logger.warning(f"⚠️ Erro ao salvar mensagem no banco: {db_error}") | |
| # Registra interação para treinamento | |
| if hasattr(self, 'treinador') and self.treinador: | |
| try: | |
| self.treinador.registrar_interacao( | |
| usuario=payload['usuario'], | |
| mensagem=payload['mensagem'], | |
| resposta=resposta, | |
| numero=payload['numero'], | |
| is_reply=is_reply, | |
| mensagem_original=payload['mensagem_citada'], | |
| contexto=analise, | |
| tipo_conversa=payload['tipo_conversa'], | |
| tipo_mensagem=payload['tipo_mensagem'], | |
| reply_to_bot=reply_to_bot, | |
| reply_metadata=payload['reply_metadata'], | |
| nivel_transicao=analise.get('nivel_transicao', 0) | |
| ) | |
| logger.debug("✅ Interação registrada para treinamento") | |
| except Exception as e: | |
| logger.warning(f"⚠️ Erro ao registrar interação: {e}") | |
| logger.info(f"📤 Resposta: {resposta[:80]}...") | |
| # Log da transição se for privilegiado | |
| if usuario_privilegiado: | |
| info = analise.get('info_transicao', {}) | |
| logger.info(f"🔄 Transição: Nível {info.get('nivel', 1)} - {info.get('desc', 'N/A')}") | |
| return jsonify({"resposta": resposta}) | |
| except Exception as e: | |
| logger.error(f"❌ Erro em /akira: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return jsonify({ | |
| "error": "Erro interno", | |
| "resposta": "Erro interno, puto. Tenta de novo." | |
| }), 500 | |
| def health(): | |
| """Health check""" | |
| agora = datetime.datetime.now() + datetime.timedelta(hours=config.TIMEZONE_OFFSET_HOURS) | |
| # Conta usuários privilegiados | |
| privilegiados_count = len(config.USUARIOS_PRIVILEGIADOS) | |
| # Verifica APIs | |
| apis_ok = self.llm_manager.apis_disponiveis | |
| return jsonify({ | |
| "status": "✅ AKIRA V21 ONLINE COM TRANSIÇÃO GRADUAL", | |
| "hora_luanda": agora.strftime("%H:%M"), | |
| "versao": config.VERSAO, | |
| "database": "Corrigido (message_id sem UNIQUE)", | |
| "apis_disponiveis": apis_ok, | |
| "cache_size": len(self.contexto_cache._store), | |
| "treinamento_ativo": hasattr(self, 'treinador') and self.treinador is not None, | |
| "transicao_gradual": { | |
| "usuarios_privilegiados": privilegiados_count, | |
| "modo_inicial_privilegiados": "filosofico_ironico", | |
| "niveis_transicao": 3, | |
| "descricao": "Privilegiados começam formal, adaptam-se gradualmente" | |
| } | |
| }) | |
| def reset_endpoint(): | |
| """Endpoint de reset dedicado""" | |
| try: | |
| data = request.get_json() or {} | |
| numero = str(data.get('numero', '')).strip() | |
| if not numero: | |
| return jsonify({"error": "Número obrigatório"}), 400 | |
| resultado = self._processar_reset(numero, "admin", confirmacao=True) | |
| if 'error' in resultado: | |
| return jsonify(resultado), 403 | |
| return jsonify(resultado) | |
| except Exception as e: | |
| logger.error(f"Erro em /reset: {e}") | |
| return jsonify({"error": "Erro interno"}), 500 | |
| def info(): | |
| """Informações da API""" | |
| # Lista usuários privilegiados | |
| privilegiados_info = [] | |
| for numero, dados in config.USUARIOS_PRIVILEGIADOS.items(): | |
| privilegiados_info.append({ | |
| "numero": numero, | |
| "nome": dados.get("nome", "Desconhecido"), | |
| "modo_inicial": dados.get("modo_inicial", "filosofico_ironico"), | |
| "transicao_permitida": dados.get("transicao_permitida", True) | |
| }) | |
| # Informações de transição | |
| transicao_info = { | |
| "niveis": 3, | |
| "descricao_niveis": { | |
| 1: "Nível 1 - Formal Completo (filosofico_ironico)", | |
| 2: "Nível 2 - Formal Relaxado (tecnico_formal) esse tom deve ser usado por padrão para usarios priveleigiados, e para topicos academicos", | |
| 3: "Nível 3 - Normal (normal_ironico)" | |
| }, | |
| "regras": [ | |
| "Privilegiados começam no Nível 1", | |
| "Transição gradual baseada no tom do usuário", | |
| "Não mantém formal se conversa mudar para descontraída, mas isso deve ser lento e gradual", | |
| "Adaptação natural seguindo ritmo da conversa" | |
| ] | |
| } | |
| return jsonify({ | |
| "nome": "Akira V21", | |
| "descricao": "IA com personalidade brutal e irônica", | |
| "desenvolvedor": "Isaac Quarenta", | |
| "empresa": "Softedge", | |
| "versao": config.VERSAO, | |
| "database_status": "Corrigido - message_id sem UNIQUE constraint", | |
| "usuarios_privilegiados": privilegiados_info, | |
| "sistema_transicao": transicao_info, | |
| "endpoints": ["/akira", "/health", "/reset", "/info", "/teste/privilegiado", "/transicao/info"], | |
| "configuracoes": { | |
| "temperatura_padrao": config.TEMPERATURE, | |
| "max_tokens": config.MAX_TOKENS, | |
| "timezone_offset": config.TIMEZONE_OFFSET_HOURS, | |
| "treinamento_auto": config.START_PERIODIC_TRAINER, | |
| "modo_inicial_privilegiados": "filosofico_ironico", | |
| "transicao_gradual": "ativada" | |
| } | |
| }) | |
| def teste_privilegiado(): | |
| """Endpoint para testar usuário privilegiado""" | |
| try: | |
| data = request.get_json() or {} | |
| numero = str(data.get('numero', '')).strip() | |
| if not numero: | |
| return jsonify({"error": "Número obrigatório"}), 400 | |
| # Testa usando config.py | |
| eh_privilegiado = config.eh_usuario_privilegiado(numero) | |
| dados_privilegiado = config.verificar_usuario_privilegiado(numero) | |
| modo_inicial = config.forcar_modo_inicial_privilegiado(numero) | |
| permite_transicao = config.transicao_permitida_privilegiado(numero) | |
| return jsonify({ | |
| "numero": numero, | |
| "eh_privilegiado": eh_privilegiado, | |
| "dados_privilegiado": dados_privilegiado, | |
| "modo_inicial": modo_inicial, | |
| "permite_transicao": permite_transicao, | |
| "instrucao": "Usuário privilegiado começa formal, adapta-se gradualmente" if eh_privilegiado else "Usuário normal usa modo padrão" | |
| }) | |
| except Exception as e: | |
| logger.error(f"Erro em /teste/privilegiado: {e}") | |
| return jsonify({"error": "Erro interno"}), 500 | |
| def transicao_info(): | |
| """Informações sobre o sistema de transição""" | |
| return jsonify({ | |
| "sistema": "Transição Gradual para Usuários Privilegiados", | |
| "descricao": "Sistema que permite a Akira adaptar-se gradualmente ao tom da conversa", | |
| "niveis": [ | |
| { | |
| "nivel": 1, | |
| "nome": "Formal Completo", | |
| "modo": "filosofico_ironico", | |
| "descricao": "Tom respeitoso, sem gírias, formal", | |
| "exemplo": "A existência é absurda por natureza." | |
| }, | |
| { | |
| "nivel": 2, | |
| "nome": "Formal Relaxado", | |
| "modo": "tecnico_formal", | |
| "descricao": "Respeitoso mas com leve ironia, algumas gírias", | |
| "exemplo": "Ya, isso faz sentido." | |
| }, | |
| { | |
| "nivel": 3, | |
| "nome": "Normal", | |
| "modo": "normal_ironico", | |
| "descricao": "Gírias normais, ironia normal (sem xingar)", | |
| "exemplo": "Puto, tá certo." | |
| } | |
| ], | |
| "regras_transicao": [ | |
| "Privilegiados sempre começam no Nível 1", | |
| "Analisa tom do usuário a cada mensagem", | |
| "2-3 mensagens descontraídas → avança um nível", | |
| "Volta a sério → retorna gradualmente", | |
| "NÃO mantém formal se conversa mudou para descontraída" | |
| ], | |
| "usuarios_privilegiados": list(config.USUARIOS_PRIVILEGIADOS.keys()) | |
| }) | |
| def transicao_simular(): | |
| """Simula transição com histórico de mensagens""" | |
| try: | |
| data = request.get_json() or {} | |
| numero = str(data.get('numero', '')).strip() | |
| mensagens = data.get('mensagens', []) | |
| if not numero: | |
| return jsonify({"error": "Número obrigatório"}), 400 | |
| if not mensagens: | |
| return jsonify({"error": "Lista de mensagens obrigatória"}), 400 | |
| eh_privilegiado = config.eh_usuario_privilegiado(numero) | |
| if not eh_privilegiado: | |
| return jsonify({ | |
| "resultado": "Usuário não é privilegiado", | |
| "modo_constante": "normal_ironico" | |
| }) | |
| # Simula transição | |
| historico_simulado = [] | |
| nivel_atual = 1 | |
| resultados = [] | |
| for i, mensagem in enumerate(mensagens): | |
| # Analisa tom | |
| analise_tom = config.analisar_tom_usuario(mensagem, historico_simulado) | |
| # Determina transição | |
| info_transicao = config.determinar_nivel_transicao( | |
| numero, | |
| analise_tom, | |
| nivel_atual, | |
| historico_simulado[-5:] if len(historico_simulado) >= 5 else historico_simulado | |
| ) | |
| # Atualiza nível | |
| nivel_atual = info_transicao['nivel'] | |
| # Adiciona ao histórico simulado | |
| historico_simulado.append({"mensagem": mensagem}) | |
| resultados.append({ | |
| "mensagem_numero": i + 1, | |
| "mensagem": mensagem, | |
| "tom_detectado": analise_tom.get("tom"), | |
| "nivel_anterior": info_transicao.get("nivel_anterior", nivel_atual), | |
| "nivel_atual": nivel_atual, | |
| "modo": info_transicao["modo"], | |
| "deve_transicionar": info_transicao["deve_transicionar"], | |
| "direcao": info_transicao["direcao"] | |
| }) | |
| return jsonify({ | |
| "usuario": numero, | |
| "privilegiado": True, | |
| "simulacao": resultados, | |
| "nivel_final": nivel_atual, | |
| "modo_final": resultados[-1]["modo"] if resultados else "filosofico_ironico" | |
| }) | |
| except Exception as e: | |
| logger.error(f"Erro em /transicao/simular: {e}") | |
| return jsonify({"error": "Erro interno"}), 500 | |
| def get_blueprint(self): | |
| """Retorna o blueprint para Flask""" | |
| return self.api | |
| # ============================================================================ | |
| # 🎯 FUNÇÃO PARA USO DIRETO (COM TRANSIÇÃO) | |
| # ============================================================================ | |
| def gerar_resposta_direta( | |
| mensagem: str, | |
| usuario: str = "Anônimo", | |
| numero: str = "000000000", | |
| tipo_conversa: str = "pv", | |
| tipo_mensagem: str = "texto", | |
| mensagem_citada: str = "", | |
| reply_metadata: Optional[Dict] = None, | |
| historico: Optional[List[Dict]] = None, | |
| nivel_transicao_atual: int = 1 | |
| ) -> str: | |
| """ | |
| Função para uso direto (sem HTTP) - COM TRANSIÇÃO | |
| Args: | |
| mensagem: Mensagem do usuário | |
| usuario: Nome do usuário | |
| numero: Número do usuário | |
| tipo_conversa: Tipo da conversa | |
| tipo_mensagem: Tipo da mensagem | |
| mensagem_citada: Mensagem citada | |
| reply_metadata: Metadata do reply | |
| historico: Histórico da conversa | |
| nivel_transicao_atual: Nível atual de transição | |
| Returns: | |
| Resposta gerada | |
| """ | |
| try: | |
| # Cria instância simplificada | |
| llm_manager = MultiAPIManager() | |
| # Verifica usuário privilegiado | |
| usuario_privilegiado = config.eh_usuario_privilegiado(numero) | |
| # Histórico padrão se não fornecido | |
| historico = historico or [] | |
| # ANALISA TOM E TRANSIÇÃO SE FOR PRIVILEGIADO | |
| analise_tom = config.analisar_tom_usuario(mensagem, historico) | |
| if usuario_privilegiado: | |
| info_transicao = config.determinar_nivel_transicao( | |
| numero, | |
| analise_tom, | |
| nivel_transicao_atual, | |
| historico[-5:] if len(historico) >= 5 else historico | |
| ) | |
| modo_resposta = info_transicao['modo'] | |
| novo_nivel = info_transicao['nivel'] | |
| else: | |
| modo_resposta = 'normal_ironico' | |
| novo_nivel = 0 | |
| # Cria análise básica | |
| analise = { | |
| 'numero': numero, | |
| 'usuario_privilegiado': usuario_privilegiado, | |
| 'emocao_primaria': 'neutral', | |
| 'tipo_mensagem': tipo_mensagem, | |
| 'reply_metadata': reply_metadata, | |
| 'modo_resposta': modo_resposta, | |
| 'nivel_transicao': novo_nivel, | |
| 'info_transicao': info_transicao if usuario_privilegiado else {} | |
| } | |
| # Gera resposta | |
| resposta = llm_manager.gerar_resposta( | |
| mensagem=mensagem, | |
| historico=historico, | |
| mensagem_citada=mensagem_citada, | |
| analise=analise, | |
| usuario=usuario, | |
| tipo_conversa=tipo_conversa, | |
| reply_info=reply_metadata | |
| ) | |
| logger.info(f"✅ Resposta direta para {usuario}: {resposta[:80]}...") | |
| if usuario_privilegiado: | |
| logger.info(f"🔄 Nível transição: {novo_nivel} ({info_transicao.get('desc', 'N/A')})") | |
| return resposta | |
| except Exception as e: | |
| logger.error(f"❌ Erro na resposta direta: {e}") | |
| return "Puto, erro. Tenta de novo." | |
| # ============================================================================ | |
| # 🎯 TESTE | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| print("=" * 80) | |
| print("TESTANDO API.PY - COM SISTEMA DE TRANSIÇÃO GRADUAL") | |
| print("=" * 80) | |
| # Testes de transição | |
| test_cases = [ | |
| { | |
| "nome": "Isaac Quarenta - Início Formal", | |
| "numero": "244978787009", | |
| "mensagens": [ | |
| "Precisamos revisar o código do projeto.", | |
| "Analise a arquitetura atual.", | |
| "O sistema precisa de otimização." | |
| ], | |
| "expectativa": "Nível 1 (Formal) mantido" | |
| }, | |
| { | |
| "nome": "Isaac Quarenta - Transição para Descontraído", | |
| "numero": "244978787009", | |
| "mensagens": [ | |
| "Tá tudo fixe?", | |
| "Ya, tás a brincar hoje?", | |
| "kkk, relaxa mano", | |
| "De boa, tás tranquilo?" | |
| ], | |
| "expectativa": "Nível 1 → 2 → 3 (gradual)" | |
| }, | |
| { | |
| "nome": "Isaac Quarenta - Volta a Sério", | |
| "numero": "244937035662", | |
| "mensagens": [ | |
| "kkk, tás louco", | |
| "Brincadeira à parte...", | |
| "Precisamos falar sério do projeto.", | |
| "Analise este código: def func(): pass" | |
| ], | |
| "expectativa": "Nível 3 → 2 → 1 (gradual)" | |
| }, | |
| { | |
| "nome": "Usuário Normal", | |
| "numero": "123456789", | |
| "mensagens": [ | |
| "Ei, tudo bem?", | |
| "Você é um bot?", | |
| "Vai à merda!" | |
| ], | |
| "expectativa": "Modo normal sempre" | |
| } | |
| ] | |
| for i, test_case in enumerate(test_cases, 1): | |
| print(f"\n🔍 TESTE {i}: {test_case['nome']}") | |
| print(f" Número: {test_case['numero']}") | |
| # Verifica se é privilegiado | |
| eh_privilegiado = config.eh_usuario_privilegiado(test_case['numero']) | |
| modo_inicial = config.forcar_modo_inicial_privilegiado(test_case['numero']) | |
| permite_transicao = config.transicao_permitida_privilegiado(test_case['numero']) | |
| print(f" É privilegiado? {eh_privilegiado}") | |
| print(f" Modo inicial: {modo_inicial}") | |
| print(f" Permite transição? {permite_transicao}") | |
| # Simula conversa | |
| historico_simulado = [] | |
| nivel_atual = 1 | |
| for j, mensagem in enumerate(test_case['mensagens']): | |
| resposta = gerar_resposta_direta( | |
| mensagem=mensagem, | |
| usuario=test_case['nome'], | |
| numero=test_case['numero'], | |
| historico=historico_simulado, | |
| nivel_transicao_atual=nivel_atual | |
| ) | |
| # Atualiza nível se for privilegiado | |
| if eh_privilegiado: | |
| analise_tom = config.analisar_tom_usuario(mensagem, historico_simulado) | |
| info = config.determinar_nivel_transicao( | |
| test_case['numero'], | |
| analise_tom, | |
| nivel_atual, | |
| historico_simulado[-5:] if len(historico_simulado) >= 5 else historico_simulado | |
| ) | |
| nivel_atual = info['nivel'] | |
| historico_simulado.append({"mensagem": mensagem, "resposta": resposta}) | |
| print(f" Msg {j+1}: '{mensagem[:30]}...'") | |
| print(f" Resposta: {resposta[:50]}...") | |
| if eh_privilegiado: | |
| print(f" Nível: {nivel_atual}") | |
| print(f" Expectativa: {test_case['expectativa']}") | |
| print("\n" + "=" * 80) | |
| print("✅ API.PY - SISTEMA DE TRANSIÇÃO GRADUAL IMPLEMENTADO") | |
| print("✅ Usuários privilegiados: Formal → Relaxado → Normal") | |
| print("✅ Transição gradual baseada no tom do usuário") | |
| print("✅ Não mantém formal se conversa mudou") | |
| print("✅ Endpoints: /akira, /health, /info, /transicao/info, /transicao/simular") | |
| print("=" * 80) |