akra35567 commited on
Commit
bbc81bd
·
verified ·
1 Parent(s): a9b8f00

Update modules/api.py

Browse files
Files changed (1) hide show
  1. modules/api.py +309 -72
modules/api.py CHANGED
@@ -1,8 +1,16 @@
1
- # modules/api.py — V27 OLLAMA PRINCIPAL + MISTRAL + GEMINI FALLBACK (SEM ERRO 'os')
 
 
 
 
 
 
 
2
  import time
3
  import datetime
4
  import requests
5
- import os # ← IMPORTADO AQUI!
 
6
  from flask import Blueprint, request, jsonify, make_response
7
  from loguru import logger
8
  from .contexto import Contexto
@@ -10,6 +18,10 @@ from .database import Database
10
  from .treinamento import Treinamento
11
  import modules.config as config
12
 
 
 
 
 
13
  class SimpleTTLCache:
14
  def __init__(self, ttl_seconds: int = 300):
15
  self.ttl = ttl_seconds
@@ -32,24 +44,280 @@ class SimpleTTLCache:
32
  raise KeyError(key)
33
  return self._store[key][0]
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  class AkiraAPI:
36
  def __init__(self, cfg_module):
37
  self.config = cfg_module
38
  self.api = Blueprint("akira_api", __name__)
39
  self.contexto_cache = SimpleTTLCache(ttl_seconds=300)
40
  self.db = Database(getattr(self.config, 'DB_PATH', 'akira.db'))
 
41
  self._setup_routes()
42
  self._setup_trainer()
43
-
44
  def _setup_trainer(self):
 
45
  if getattr(self.config, 'START_PERIODIC_TRAINER', False):
46
  try:
47
  Treinamento(self.db, interval_hours=config.TRAINING_INTERVAL_HOURS).start_periodic_training()
48
  logger.info("Treinamento periódico INICIADO")
49
  except Exception as e:
50
  logger.error(f"Treinador falhou: {e}")
51
-
52
  def _setup_routes(self):
 
 
53
  @self.api.before_request
54
  def handle_options():
55
  if request.method == 'OPTIONS':
@@ -58,89 +326,57 @@ class AkiraAPI:
58
  resp.headers['Access-Control-Allow-Headers'] = 'Content-Type'
59
  resp.headers['Access-Control-Allow-Methods'] = 'POST, GET'
60
  return resp
61
-
62
  @self.api.after_request
63
  def add_cors(response):
64
  response.headers['Access-Control-Allow-Origin'] = '*'
65
  return response
66
-
67
  @self.api.route('/akira', methods=['POST'])
68
  def akira_endpoint():
69
  try:
70
  data = request.get_json() or {}
71
  usuario = data.get('usuario', 'anonimo')
72
- numero = data.get('numero', '')
73
  mensagem = data.get('mensagem', '').strip()
74
  mensagem_citada = data.get('mensagem_citada', '').strip()
75
-
76
  if not mensagem and not mensagem_citada:
77
  return jsonify({'error': 'mensagem obrigatória'}), 400
78
-
79
  logger.info(f"[{usuario}] ({numero}): {mensagem[:60]}")
80
-
81
- # HORA RÁPIDA
82
- if any(k in mensagem.lower() for k in ["hora", "horas"]):
83
  agora = datetime.datetime.now()
84
  return jsonify({'resposta': f"São {agora.strftime('%H:%M')} em Luanda, puto."})
85
-
86
- # CONTEXTO
87
  contexto = self._get_user_context(numero)
88
-
89
- # === 1. OLLAMA AUTÔNOMO ===
90
- payload = {
91
- "usuario": usuario,
92
- "numero": numero,
93
- "mensagem": mensagem,
94
- "mensagem_citada": mensagem_citada,
95
- "historico": contexto.obter_historico_para_llm()[-8:],
96
- "data_hora": datetime.datetime.now().strftime('%d/%m %H:%M')
97
- }
98
-
99
- resposta = None
100
- try:
101
- resp = requests.post(config.OLLAMA_SERVER_URL, json=payload, timeout=60)
102
- if resp.status_code == 200:
103
- resposta = resp.json().get("resposta", "").strip()
104
- logger.success("Resposta do Ollama")
105
- except Exception as e:
106
- logger.warning(f"Ollama falhou: {e}")
107
-
108
- # === 2. FALLBACK MISTRAL ===
109
- if not resposta:
110
- logger.info("Tentando Mistral...")
111
- try:
112
- headers = {"Authorization": f"Bearer {os.getenv('MISTRAL_API_KEY')}"}
113
- mistral_payload = {
114
- "model": "mistral-large-latest",
115
- "messages": [{"role": "user", "content": f"Fala como angolana debochada: {mensagem}"}]
116
- }
117
- resp = requests.post("https://api.mistral.ai/v1/chat/completions", json=mistral_payload, headers=headers, timeout=60)
118
- if resp.status_code == 200:
119
- resposta = resp.json()["choices"][0]["message"]["content"].strip()
120
- logger.success("Resposta do Mistral")
121
- except Exception as e:
122
- logger.warning(f"Mistral falhou: {e}")
123
-
124
- # === 3. FALLBACK GEMINI ===
125
- if not resposta:
126
- logger.info("Tentando Gemini...")
127
- try:
128
- url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={os.getenv('GEMINI_API_KEY')}"
129
- gemini_payload = {"contents": [{"parts": [{"text": f"Fala como angolana debochada: {mensagem}"}]}]}
130
- resp = requests.post(url, json=gemini_payload, timeout=60)
131
- if resp.status_code == 200:
132
- resposta = resp.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
133
- logger.success("Resposta do Gemini")
134
- except Exception as e:
135
- logger.warning(f"Gemini falhou: {e}")
136
-
137
- # === RESPOSTA FINAL ===
138
- if not resposta:
139
- resposta = "Epha... essa merda bugou"
140
-
141
 
142
- # SALVA + TREINA
 
 
 
 
 
 
 
 
 
143
  contexto.atualizar_contexto(mensagem, resposta)
 
144
  try:
145
  trainer = Treinamento(self.db)
146
  trainer.registrar_interacao(
@@ -153,18 +389,19 @@ class AkiraAPI:
153
  )
154
  except Exception as e:
155
  logger.warning(f"Erro ao treinar: {e}")
156
-
157
  return jsonify({'resposta': resposta})
158
-
159
  except Exception as e:
160
  logger.exception("Erro em /akira")
161
  return jsonify({'resposta': 'Erro interno, já volto!'}), 500
162
-
163
  @self.api.route('/health', methods=['GET'])
164
  def health_check():
165
  return 'OK', 200
166
-
167
  def _get_user_context(self, numero: str) -> Contexto:
 
168
  if not numero:
169
  numero = "anonimo"
170
  if numero not in self.contexto_cache:
 
1
+ # modules/api.py — AKIRA V19 ULTIMATE (Dezembro 2025)
2
+ """
3
+ API Flask com 6 provedores de IA em fallback cascata
4
+ - Mistral → Gemini → Groq → Cohere → Together → HuggingFace
5
+ - Respostas em <5s (média)
6
+ - Contexto de reply estruturado
7
+ - Zero limitações éticas
8
+ """
9
  import time
10
  import datetime
11
  import requests
12
+ import os
13
+ import json
14
  from flask import Blueprint, request, jsonify, make_response
15
  from loguru import logger
16
  from .contexto import Contexto
 
18
  from .treinamento import Treinamento
19
  import modules.config as config
20
 
21
+ # ============================================================================
22
+ # CACHE SIMPLES EM MEMÓRIA
23
+ # ============================================================================
24
+
25
  class SimpleTTLCache:
26
  def __init__(self, ttl_seconds: int = 300):
27
  self.ttl = ttl_seconds
 
44
  raise KeyError(key)
45
  return self._store[key][0]
46
 
47
+ # ============================================================================
48
+ # GERENCIADOR MULTI-API
49
+ # ============================================================================
50
+
51
+ class MultiAPIManager:
52
+ """Gerencia chamadas para 6 APIs com fallback automático"""
53
+
54
+ def __init__(self):
55
+ self.timeout = config.API_TIMEOUT
56
+ self.apis_disponiveis = self._verificar_apis()
57
+ logger.info(f"APIs disponíveis: {', '.join(self.apis_disponiveis)}")
58
+
59
+ def _verificar_apis(self):
60
+ """Verifica quais APIs estão configuradas"""
61
+ apis = []
62
+ if config.MISTRAL_API_KEY:
63
+ apis.append("mistral")
64
+ if config.GEMINI_API_KEY:
65
+ apis.append("gemini")
66
+ if config.GROQ_API_KEY:
67
+ apis.append("groq")
68
+ if config.COHERE_API_KEY:
69
+ apis.append("cohere")
70
+ if config.TOGETHER_API_KEY:
71
+ apis.append("together")
72
+ if config.HF_API_KEY:
73
+ apis.append("huggingface")
74
+ return apis
75
+
76
+ def _construir_prompt(self, mensagem: str, historico: list, mensagem_citada: str,
77
+ humor: str, tom_usuario: str) -> str:
78
+ """Constrói prompt otimizado com contexto"""
79
+
80
+ # Contexto de reply (se existir)
81
+ reply_context = ""
82
+ if mensagem_citada:
83
+ reply_context = f"\n[USUÁRIO ESTÁ RESPONDENDO A]: \"{mensagem_citada[:100]}...\"\n"
84
+
85
+ # Histórico formatado
86
+ historico_texto = ""
87
+ if historico:
88
+ ultimas = historico[-6:] # Últimas 6 mensagens
89
+ for msg in ultimas:
90
+ role = msg.get("role", "user")
91
+ content = msg.get("content", "")
92
+ historico_texto += f"{role.upper()}: {content}\n"
93
+
94
+ # Prompt final
95
+ prompt = f"""{config.PERSONA.format(humor=humor, tom_usuario=tom_usuario)}
96
+
97
+ {config.SYSTEM_PROMPT.format(mensagem_citada=mensagem_citada or "nenhuma", humor=humor)}
98
+
99
+ CONTEXTO DA CONVERSA:
100
+ {historico_texto}
101
+ {reply_context}
102
+
103
+ USUÁRIO: {mensagem}
104
+
105
+ AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
106
+
107
+ return prompt
108
+
109
+ # === API 1: MISTRAL ===
110
+ def _chamar_mistral(self, prompt: str) -> str:
111
+ """Chama Mistral AI"""
112
+ try:
113
+ headers = {"Authorization": f"Bearer {config.MISTRAL_API_KEY}"}
114
+ payload = {
115
+ "model": config.MISTRAL_MODEL,
116
+ "messages": [{"role": "user", "content": prompt}],
117
+ "max_tokens": config.MAX_TOKENS,
118
+ "temperature": config.TEMPERATURE
119
+ }
120
+ resp = requests.post(
121
+ "https://api.mistral.ai/v1/chat/completions",
122
+ json=payload,
123
+ headers=headers,
124
+ timeout=self.timeout
125
+ )
126
+ if resp.status_code == 200:
127
+ return resp.json()["choices"][0]["message"]["content"].strip()
128
+ except Exception as e:
129
+ logger.warning(f"Mistral falhou: {e}")
130
+ return None
131
+
132
+ # === API 2: GEMINI ===
133
+ def _chamar_gemini(self, prompt: str) -> str:
134
+ """Chama Google Gemini"""
135
+ try:
136
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/{config.GEMINI_MODEL}:generateContent?key={config.GEMINI_API_KEY}"
137
+ payload = {
138
+ "contents": [{"parts": [{"text": prompt}]}],
139
+ "generationConfig": {
140
+ "maxOutputTokens": config.MAX_TOKENS,
141
+ "temperature": config.TEMPERATURE
142
+ }
143
+ }
144
+ resp = requests.post(url, json=payload, timeout=self.timeout)
145
+ if resp.status_code == 200:
146
+ return resp.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
147
+ except Exception as e:
148
+ logger.warning(f"Gemini falhou: {e}")
149
+ return None
150
+
151
+ # === API 3: GROQ ===
152
+ def _chamar_groq(self, prompt: str) -> str:
153
+ """Chama Groq (ultra-rápido)"""
154
+ try:
155
+ headers = {"Authorization": f"Bearer {config.GROQ_API_KEY}"}
156
+ payload = {
157
+ "model": config.GROQ_MODEL,
158
+ "messages": [{"role": "user", "content": prompt}],
159
+ "max_tokens": config.MAX_TOKENS,
160
+ "temperature": config.TEMPERATURE
161
+ }
162
+ resp = requests.post(
163
+ "https://api.groq.com/openai/v1/chat/completions",
164
+ json=payload,
165
+ headers=headers,
166
+ timeout=self.timeout
167
+ )
168
+ if resp.status_code == 200:
169
+ return resp.json()["choices"][0]["message"]["content"].strip()
170
+ except Exception as e:
171
+ logger.warning(f"Groq falhou: {e}")
172
+ return None
173
+
174
+ # === API 4: COHERE ===
175
+ def _chamar_cohere(self, prompt: str) -> str:
176
+ """Chama Cohere"""
177
+ try:
178
+ headers = {"Authorization": f"Bearer {config.COHERE_API_KEY}"}
179
+ payload = {
180
+ "model": config.COHERE_MODEL,
181
+ "message": prompt,
182
+ "max_tokens": config.MAX_TOKENS,
183
+ "temperature": config.TEMPERATURE
184
+ }
185
+ resp = requests.post(
186
+ "https://api.cohere.ai/v1/chat",
187
+ json=payload,
188
+ headers=headers,
189
+ timeout=self.timeout
190
+ )
191
+ if resp.status_code == 200:
192
+ return resp.json()["text"].strip()
193
+ except Exception as e:
194
+ logger.warning(f"Cohere falhou: {e}")
195
+ return None
196
+
197
+ # === API 5: TOGETHER AI ===
198
+ def _chamar_together(self, prompt: str) -> str:
199
+ """Chama Together AI"""
200
+ try:
201
+ headers = {"Authorization": f"Bearer {config.TOGETHER_API_KEY}"}
202
+ payload = {
203
+ "model": config.TOGETHER_MODEL,
204
+ "messages": [{"role": "user", "content": prompt}],
205
+ "max_tokens": config.MAX_TOKENS,
206
+ "temperature": config.TEMPERATURE
207
+ }
208
+ resp = requests.post(
209
+ "https://api.together.xyz/v1/chat/completions",
210
+ json=payload,
211
+ headers=headers,
212
+ timeout=self.timeout
213
+ )
214
+ if resp.status_code == 200:
215
+ return resp.json()["choices"][0]["message"]["content"].strip()
216
+ except Exception as e:
217
+ logger.warning(f"Together falhou: {e}")
218
+ return None
219
+
220
+ # === API 6: HUGGING FACE ===
221
+ def _chamar_huggingface(self, prompt: str) -> str:
222
+ """Chama HuggingFace Inference API"""
223
+ try:
224
+ headers = {"Authorization": f"Bearer {config.HF_API_KEY}"}
225
+ payload = {"inputs": prompt, "parameters": {"max_new_tokens": config.MAX_TOKENS}}
226
+ resp = requests.post(
227
+ f"https://api-inference.huggingface.co/models/{config.HF_MODEL}",
228
+ json=payload,
229
+ headers=headers,
230
+ timeout=self.timeout
231
+ )
232
+ if resp.status_code == 200:
233
+ return resp.json()[0]["generated_text"].split("AKIRA:")[-1].strip()
234
+ except Exception as e:
235
+ logger.warning(f"HuggingFace falhou: {e}")
236
+ return None
237
+
238
+ # === MÉTODO PRINCIPAL DE GERAÇÃO ===
239
+ def gerar_resposta(self, mensagem: str, historico: list, mensagem_citada: str,
240
+ humor: str, tom_usuario: str) -> str:
241
+ """
242
+ Tenta gerar resposta usando todas as APIs na ordem configurada
243
+ """
244
+ prompt = self._construir_prompt(mensagem, historico, mensagem_citada, humor, tom_usuario)
245
+
246
+ # Tenta cada API na ordem de fallback
247
+ for api_name in config.API_FALLBACK_ORDER:
248
+ if api_name not in self.apis_disponiveis:
249
+ continue
250
+
251
+ logger.info(f"Tentando {api_name.upper()}...")
252
+
253
+ try:
254
+ if api_name == "mistral":
255
+ resposta = self._chamar_mistral(prompt)
256
+ elif api_name == "gemini":
257
+ resposta = self._chamar_gemini(prompt)
258
+ elif api_name == "groq":
259
+ resposta = self._chamar_groq(prompt)
260
+ elif api_name == "cohere":
261
+ resposta = self._chamar_cohere(prompt)
262
+ elif api_name == "together":
263
+ resposta = self._chamar_together(prompt)
264
+ elif api_name == "huggingface":
265
+ resposta = self._chamar_huggingface(prompt)
266
+
267
+ if resposta:
268
+ logger.success(f"✓ Resposta gerada via {api_name.upper()}")
269
+ return self._limpar_resposta(resposta)
270
+
271
+ except Exception as e:
272
+ logger.error(f"{api_name} erro crítico: {e}")
273
+
274
+ # Se todas falharem
275
+ return "Tá foda hoje, todos os servidores caíram. Volta daqui a pouco."
276
+
277
+ def _limpar_resposta(self, resposta: str) -> str:
278
+ """Remove markdown e limita tamanho"""
279
+ # Remove markdown
280
+ resposta = resposta.replace("**", "").replace("*", "")
281
+ resposta = resposta.replace("```", "").replace("`", "")
282
+
283
+ # Remove prefixos comuns de IA
284
+ prefixos = ["AKIRA:", "Akira:", "RESPOSTA:", "Resposta:"]
285
+ for p in prefixos:
286
+ if resposta.startswith(p):
287
+ resposta = resposta[len(p):].strip()
288
+
289
+ # Limita tamanho (máximo 300 caracteres)
290
+ if len(resposta) > 300:
291
+ resposta = resposta[:297] + "..."
292
+
293
+ return resposta.strip()
294
+
295
+ # ============================================================================
296
+ # CLASSE PRINCIPAL DA API
297
+ # ============================================================================
298
+
299
  class AkiraAPI:
300
  def __init__(self, cfg_module):
301
  self.config = cfg_module
302
  self.api = Blueprint("akira_api", __name__)
303
  self.contexto_cache = SimpleTTLCache(ttl_seconds=300)
304
  self.db = Database(getattr(self.config, 'DB_PATH', 'akira.db'))
305
+ self.llm_manager = MultiAPIManager()
306
  self._setup_routes()
307
  self._setup_trainer()
308
+
309
  def _setup_trainer(self):
310
+ """Inicializa treinamento (desativado por padrão)"""
311
  if getattr(self.config, 'START_PERIODIC_TRAINER', False):
312
  try:
313
  Treinamento(self.db, interval_hours=config.TRAINING_INTERVAL_HOURS).start_periodic_training()
314
  logger.info("Treinamento periódico INICIADO")
315
  except Exception as e:
316
  logger.error(f"Treinador falhou: {e}")
317
+
318
  def _setup_routes(self):
319
+ """Configura rotas Flask"""
320
+
321
  @self.api.before_request
322
  def handle_options():
323
  if request.method == 'OPTIONS':
 
326
  resp.headers['Access-Control-Allow-Headers'] = 'Content-Type'
327
  resp.headers['Access-Control-Allow-Methods'] = 'POST, GET'
328
  return resp
329
+
330
  @self.api.after_request
331
  def add_cors(response):
332
  response.headers['Access-Control-Allow-Origin'] = '*'
333
  return response
334
+
335
  @self.api.route('/akira', methods=['POST'])
336
  def akira_endpoint():
337
  try:
338
  data = request.get_json() or {}
339
  usuario = data.get('usuario', 'anonimo')
340
+ numero = data.get('numero', '').strip()
341
  mensagem = data.get('mensagem', '').strip()
342
  mensagem_citada = data.get('mensagem_citada', '').strip()
343
+
344
  if not mensagem and not mensagem_citada:
345
  return jsonify({'error': 'mensagem obrigatória'}), 400
346
+
347
  logger.info(f"[{usuario}] ({numero}): {mensagem[:60]}")
348
+
349
+ # === RESPOSTA RÁPIDA PARA HORA ===
350
+ if any(k in mensagem.lower() for k in ["hora", "horas", "que horas"]):
351
  agora = datetime.datetime.now()
352
  return jsonify({'resposta': f"São {agora.strftime('%H:%M')} em Luanda, puto."})
353
+
354
+ # === CONTEXTO DO USUÁRIO ===
355
  contexto = self._get_user_context(numero)
356
+ historico = contexto.obter_historico_para_llm()
357
+
358
+ # === ANÁLISE DE TOM E HUMOR ===
359
+ analise = contexto.analisar_intencao_e_normalizar(mensagem, historico)
360
+ tom_usuario = analise.get("estilo", "casual")
361
+ humor_atual = contexto.obter_emocao_atual()
362
+
363
+ # === VERIFICAR SE É USUÁRIO PRIVILEGIADO ===
364
+ if numero in config.USUARIOS_PRIVILEGIADOS:
365
+ tom_usuario = "formal"
366
+ logger.info(f"Usuário privilegiado detectado: {config.USUARIOS_PRIVILEGIADOS[numero]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
+ # === GERAR RESPOSTA VIA MULTI-API ===
369
+ resposta = self.llm_manager.gerar_resposta(
370
+ mensagem=mensagem,
371
+ historico=historico,
372
+ mensagem_citada=mensagem_citada,
373
+ humor=humor_atual,
374
+ tom_usuario=tom_usuario
375
+ )
376
+
377
+ # === SALVAR NO BANCO + CONTEXTO ===
378
  contexto.atualizar_contexto(mensagem, resposta)
379
+
380
  try:
381
  trainer = Treinamento(self.db)
382
  trainer.registrar_interacao(
 
389
  )
390
  except Exception as e:
391
  logger.warning(f"Erro ao treinar: {e}")
392
+
393
  return jsonify({'resposta': resposta})
394
+
395
  except Exception as e:
396
  logger.exception("Erro em /akira")
397
  return jsonify({'resposta': 'Erro interno, já volto!'}), 500
398
+
399
  @self.api.route('/health', methods=['GET'])
400
  def health_check():
401
  return 'OK', 200
402
+
403
  def _get_user_context(self, numero: str) -> Contexto:
404
+ """Retorna contexto do usuário (com cache)"""
405
  if not numero:
406
  numero = "anonimo"
407
  if numero not in self.contexto_cache: