Spaces:
Running
Running
Update modules/api.py
Browse files- modules/api.py +46 -23
modules/api.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# modules/api.py — VERSÃO FINAL OFICIAL: Contexto Fixo, Web Search Ativo, Akira Viva!
|
| 2 |
"""
|
| 3 |
API wrapper Akira IA.
|
| 4 |
Prioridade: Mistral API (Phi-3 Mini) → Gemini → Fallback
|
|
@@ -19,7 +18,7 @@ import google.generativeai as genai
|
|
| 19 |
from mistralai import Mistral
|
| 20 |
|
| 21 |
# LOCAL MODULES
|
| 22 |
-
# from .local_llm import HermesLLM
|
| 23 |
from .contexto import Contexto
|
| 24 |
from .database import Database
|
| 25 |
from .treinamento import Treinamento
|
|
@@ -64,11 +63,6 @@ class LLMManager:
|
|
| 64 |
self.providers = []
|
| 65 |
|
| 66 |
# PRIORIDADE MÁXIMA AGORA É O MISTRAL (PHI-3 MINI)
|
| 67 |
-
# if HermesLLM.is_available(): # REMOVIDO PELA CARGA DE CPU
|
| 68 |
-
# self.hermes_available = True
|
| 69 |
-
# self.providers.append('hermes')
|
| 70 |
-
# logger.info("HERMES 7B LOCAL (GGUF + LoRA ANGOLANO) ATIVO → PRIORIDADE MÁXIMA → 8-12s RESPOSTA!")
|
| 71 |
-
|
| 72 |
if self.mistral_client:
|
| 73 |
self.providers.append('mistral') # Mistral (usando Phi-3) é o novo principal
|
| 74 |
if self.gemini_model:
|
|
@@ -127,11 +121,16 @@ class LLMManager:
|
|
| 127 |
messages.append({"role": role, "content": turn["content"]})
|
| 128 |
|
| 129 |
# Extrai a mensagem limpa do prompt (necessário para APIs)
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
| 131 |
if user_message_clean_match:
|
| 132 |
-
|
|
|
|
| 133 |
else:
|
| 134 |
-
|
|
|
|
| 135 |
|
| 136 |
messages.append({"role": "user", "content": user_message_clean})
|
| 137 |
|
|
@@ -140,10 +139,6 @@ class LLMManager:
|
|
| 140 |
|
| 141 |
|
| 142 |
for provider in self.providers:
|
| 143 |
-
# 1. HERMES LOCAL → PULADO (REMOVIDO DO __init__)
|
| 144 |
-
# if provider == 'hermes' and self.hermes_available:
|
| 145 |
-
# ...
|
| 146 |
-
|
| 147 |
# 1. MISTRAL API (AGORA PRIORIDADE MÁXIMA)
|
| 148 |
if provider == 'mistral' and self.mistral_client:
|
| 149 |
try:
|
|
@@ -171,6 +166,7 @@ class LLMManager:
|
|
| 171 |
if getattr(self.config, 'GEMINI_API_KEY', '').startswith('AIza'):
|
| 172 |
genai.configure(api_key=self.config.GEMINI_API_KEY)
|
| 173 |
|
|
|
|
| 174 |
gemini_hist = []
|
| 175 |
for msg in messages[1:]:
|
| 176 |
role = "user" if msg["role"] == "user" else "model"
|
|
@@ -245,15 +241,38 @@ class AkiraAPI:
|
|
| 245 |
|
| 246 |
# CORREÇÃO: Verifica se o método existe antes de chamar
|
| 247 |
if hasattr(trainer, 'start_periodic_training'):
|
| 248 |
-
|
| 249 |
-
|
| 250 |
else:
|
| 251 |
-
|
| 252 |
-
|
| 253 |
except Exception as e:
|
| 254 |
self.logger.exception(f"Treinador periódico falhou ao iniciar: {e}")
|
| 255 |
|
| 256 |
def _setup_routes(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
@self.api.route('/akira', methods=['POST'])
|
| 258 |
def akira_endpoint():
|
| 259 |
try:
|
|
@@ -272,14 +291,17 @@ class AkiraAPI:
|
|
| 272 |
|
| 273 |
# --- CORREÇÃO: Resposta rápida para "Que dia é hoje?" ---
|
| 274 |
prompt_lower = mensagem.lower().strip()
|
| 275 |
-
if any(keyword in prompt_lower for keyword in ["que dia é hoje", "qual é a data", "dia da semana"]):
|
| 276 |
hoje = datetime.datetime.now()
|
| 277 |
dia_semana = hoje.strftime("%A")
|
| 278 |
dia_mes = hoje.day
|
| 279 |
mes = hoje.strftime("%B")
|
| 280 |
ano = hoje.year
|
|
|
|
| 281 |
|
| 282 |
-
if
|
|
|
|
|
|
|
| 283 |
resposta = f"Hoje é {dia_semana.capitalize()}, {dia_mes}, meu."
|
| 284 |
else:
|
| 285 |
resposta = f"Hoje é {dia_semana.capitalize()}, {dia_mes} de {mes.capitalize()} de {ano}, meu."
|
|
@@ -373,7 +395,7 @@ class AkiraAPI:
|
|
| 373 |
web_search_context = ""
|
| 374 |
|
| 375 |
# Palavras-chave que sugerem necessidade de informação em tempo real ou muito específica
|
| 376 |
-
trigger_keywords = ['hoje', 'agora', 'recente', 'notícias', 'busca na web', 'pesquisa', 'investiga']
|
| 377 |
|
| 378 |
search_query = f"{mensagem} {mensagem_citada}".strip().lower()
|
| 379 |
|
|
@@ -443,9 +465,10 @@ class AkiraAPI:
|
|
| 443 |
temperature = getattr(self.config, 'TOP_P', 0.8)
|
| 444 |
|
| 445 |
# Extrai a mensagem limpa do prompt (necessário para APIs)
|
| 446 |
-
|
|
|
|
| 447 |
if user_prompt_clean_match:
|
| 448 |
-
user_prompt_clean = user_prompt_clean_match.group(
|
| 449 |
else:
|
| 450 |
user_prompt_clean = prompt # Fallback
|
| 451 |
|
|
|
|
|
|
|
| 1 |
"""
|
| 2 |
API wrapper Akira IA.
|
| 3 |
Prioridade: Mistral API (Phi-3 Mini) → Gemini → Fallback
|
|
|
|
| 18 |
from mistralai import Mistral
|
| 19 |
|
| 20 |
# LOCAL MODULES
|
| 21 |
+
# from .local_llm import HermesLLM # ← REMOVIDO: Era o modelo que causava a carga de 101% CPU
|
| 22 |
from .contexto import Contexto
|
| 23 |
from .database import Database
|
| 24 |
from .treinamento import Treinamento
|
|
|
|
| 63 |
self.providers = []
|
| 64 |
|
| 65 |
# PRIORIDADE MÁXIMA AGORA É O MISTRAL (PHI-3 MINI)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
if self.mistral_client:
|
| 67 |
self.providers.append('mistral') # Mistral (usando Phi-3) é o novo principal
|
| 68 |
if self.gemini_model:
|
|
|
|
| 121 |
messages.append({"role": role, "content": turn["content"]})
|
| 122 |
|
| 123 |
# Extrai a mensagem limpa do prompt (necessário para APIs)
|
| 124 |
+
# O prompt completo é formatado em _build_prompt, mas as APIs usam o formato de messages.
|
| 125 |
+
# Precisamos extrair apenas a última mensagem do usuário do prompt longo para garantir que
|
| 126 |
+
# o histórico (que já está em context_history) não seja duplicado.
|
| 127 |
+
user_message_clean_match = re.search(r'(### Mensagem Atual ###|### USUÁRIO RESPONDEU A ESSA MENSAGEM: ###)\n(.*?)\n\n(Akira:|$)', user_prompt, re.DOTALL)
|
| 128 |
if user_message_clean_match:
|
| 129 |
+
# Captura o grupo 2 (o conteúdo da mensagem)
|
| 130 |
+
user_message_clean = user_message_clean_match.group(2).strip()
|
| 131 |
else:
|
| 132 |
+
# Fallback (caso o formato do prompt mude)
|
| 133 |
+
user_message_clean = user_prompt
|
| 134 |
|
| 135 |
messages.append({"role": "user", "content": user_message_clean})
|
| 136 |
|
|
|
|
| 139 |
|
| 140 |
|
| 141 |
for provider in self.providers:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
# 1. MISTRAL API (AGORA PRIORIDADE MÁXIMA)
|
| 143 |
if provider == 'mistral' and self.mistral_client:
|
| 144 |
try:
|
|
|
|
| 166 |
if getattr(self.config, 'GEMINI_API_KEY', '').startswith('AIza'):
|
| 167 |
genai.configure(api_key=self.config.GEMINI_API_KEY)
|
| 168 |
|
| 169 |
+
# Cria o histórico no formato esperado pelo Gemini (list of Content)
|
| 170 |
gemini_hist = []
|
| 171 |
for msg in messages[1:]:
|
| 172 |
role = "user" if msg["role"] == "user" else "model"
|
|
|
|
| 241 |
|
| 242 |
# CORREÇÃO: Verifica se o método existe antes de chamar
|
| 243 |
if hasattr(trainer, 'start_periodic_training'):
|
| 244 |
+
trainer.start_periodic_training()
|
| 245 |
+
self.logger.info("Treinamento periódico (start_periodic_training) iniciado com sucesso.")
|
| 246 |
else:
|
| 247 |
+
self.logger.info("Treinamento periódico (via __init__) iniciado.")
|
| 248 |
+
|
| 249 |
except Exception as e:
|
| 250 |
self.logger.exception(f"Treinador periódico falhou ao iniciar: {e}")
|
| 251 |
|
| 252 |
def _setup_routes(self):
|
| 253 |
+
"""
|
| 254 |
+
Configura as rotas da API, incluindo o tratamento de CORS.
|
| 255 |
+
"""
|
| 256 |
+
# --- CORREÇÃO: Adiciona suporte manual a CORS ---
|
| 257 |
+
# 1. CORS Preflight Handler (Responde a requests OPTIONS)
|
| 258 |
+
@self.api.before_request
|
| 259 |
+
def handle_options():
|
| 260 |
+
if request.method == 'OPTIONS':
|
| 261 |
+
response = self.app.make_response('')
|
| 262 |
+
response.headers.add('Access-Control-Allow-Origin', '*')
|
| 263 |
+
response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
| 264 |
+
response.headers.add('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
|
| 265 |
+
return response
|
| 266 |
+
|
| 267 |
+
# 2. CORS Post-Request Header Addition (Adiciona headers em toda resposta)
|
| 268 |
+
@self.api.after_request
|
| 269 |
+
def add_cors_headers(response):
|
| 270 |
+
response.headers.add('Access-Control-Allow-Origin', '*')
|
| 271 |
+
response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
| 272 |
+
response.headers.add('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
|
| 273 |
+
return response
|
| 274 |
+
# ------------------------------------------------
|
| 275 |
+
|
| 276 |
@self.api.route('/akira', methods=['POST'])
|
| 277 |
def akira_endpoint():
|
| 278 |
try:
|
|
|
|
| 291 |
|
| 292 |
# --- CORREÇÃO: Resposta rápida para "Que dia é hoje?" ---
|
| 293 |
prompt_lower = mensagem.lower().strip()
|
| 294 |
+
if any(keyword in prompt_lower for keyword in ["que dia é hoje", "qual é a data", "dia da semana", "que horas"]):
|
| 295 |
hoje = datetime.datetime.now()
|
| 296 |
dia_semana = hoje.strftime("%A")
|
| 297 |
dia_mes = hoje.day
|
| 298 |
mes = hoje.strftime("%B")
|
| 299 |
ano = hoje.year
|
| 300 |
+
hora_minuto = hoje.strftime("%H:%M")
|
| 301 |
|
| 302 |
+
if "que horas" in prompt_lower:
|
| 303 |
+
resposta = f"São {hora_minuto} agora, meu."
|
| 304 |
+
elif any(k in prompt_lower for k in ["que dia", "hoje é que dia", "dia da semana"]) and not any(k in prompt_lower for k in ["mês", "ano", "data", "completa"]):
|
| 305 |
resposta = f"Hoje é {dia_semana.capitalize()}, {dia_mes}, meu."
|
| 306 |
else:
|
| 307 |
resposta = f"Hoje é {dia_semana.capitalize()}, {dia_mes} de {mes.capitalize()} de {ano}, meu."
|
|
|
|
| 395 |
web_search_context = ""
|
| 396 |
|
| 397 |
# Palavras-chave que sugerem necessidade de informação em tempo real ou muito específica
|
| 398 |
+
trigger_keywords = ['hoje', 'agora', 'recente', 'notícias', 'busca na web', 'pesquisa', 'investiga', 'último']
|
| 399 |
|
| 400 |
search_query = f"{mensagem} {mensagem_citada}".strip().lower()
|
| 401 |
|
|
|
|
| 465 |
temperature = getattr(self.config, 'TOP_P', 0.8)
|
| 466 |
|
| 467 |
# Extrai a mensagem limpa do prompt (necessário para APIs)
|
| 468 |
+
# Usa o mesmo regex do LLMManager.generate para manter a consistência
|
| 469 |
+
user_prompt_clean_match = re.search(r'(### Mensagem Atual ###|### USUÁRIO RESPONDEU A ESSA MENSAGEM: ###)\n(.*?)\n\n(Akira:|$)', prompt, re.DOTALL)
|
| 470 |
if user_prompt_clean_match:
|
| 471 |
+
user_prompt_clean = user_prompt_clean_match.group(2).strip()
|
| 472 |
else:
|
| 473 |
user_prompt_clean = prompt # Fallback
|
| 474 |
|