akra35567 commited on
Commit
fcc5c82
·
verified ·
1 Parent(s): bb82bba

Update modules/api.py

Browse files
Files changed (1) hide show
  1. modules/api.py +94 -112
modules/api.py CHANGED
@@ -1,6 +1,5 @@
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
@@ -52,37 +51,33 @@ class SimpleTTLCache:
52
 
53
  class MultiAPIManager:
54
  """Gerencia chamadas para 6 APIs com fallback automático"""
55
-
56
  def __init__(self):
57
  self.timeout = config.API_TIMEOUT
58
  self.apis_disponiveis = self._verificar_apis()
59
  logger.info(f"APIs disponíveis: {', '.join(self.apis_disponiveis)}")
60
-
61
  def _verificar_apis(self):
62
- """Verifica quais APIs estão configuradas"""
63
  apis = []
64
- if config.MISTRAL_API_KEY:
65
  apis.append("mistral")
66
- if config.GEMINI_API_KEY:
67
  apis.append("gemini")
68
- if config.GROQ_API_KEY:
69
  apis.append("groq")
70
- if config.COHERE_API_KEY:
71
  apis.append("cohere")
72
- if config.TOGETHER_API_KEY:
73
  apis.append("together")
74
- if config.HF_API_KEY:
75
  apis.append("huggingface")
76
  return apis
77
-
78
- def _construir_prompt(self, mensagem: str, historico: list, mensagem_citada: str,
79
- humor: str, tom_usuario: str) -> str:
80
  """Constrói prompt otimizado com contexto"""
81
-
82
  # === INFORMAÇÕES DA EMPRESA (SOFTEDGE) ===
83
  empresa_info = EmpresaInfo()
84
  info_context = ""
85
-
86
  # Detecta se usuário pergunta sobre criador/empresa
87
  msg_lower = mensagem.lower()
88
  if any(palavra in msg_lower for palavra in ["criou", "criador", "quem fez", "desenvolveu", "softedge", "isaac"]):
@@ -91,26 +86,22 @@ class MultiAPIManager:
91
  info_context = f"\n[INFO IMPORTANTE]: {empresa_info.get_resposta_sobre_empresa(mensagem, tom_formal)}\n"
92
  elif "softedge" in msg_lower or "empresa" in msg_lower:
93
  info_context = f"\n[INFO IMPORTANTE]: Softedge é empresa angolana de IA fundada por Isaac Quarenta em 2024. WhatsApp: {empresa_info.get_canal_whatsapp()}\n"
94
-
95
  # === DATA E HORA ATUAL ===
96
  from datetime import datetime
97
  agora = datetime.now()
98
  data_hora_atual = agora.strftime("%d de %B de %Y, %H:%M")
99
  # Traduz mês para português
100
  meses = {
101
- "January": "janeiro", "February": "fevereiro", "March": "março",
102
- "April": "abril", "May": "maio", "June": "junho",
103
- "July": "julho", "August": "agosto", "September": "setembro",
104
- "October": "outubro", "November": "novembro", "December": "dezembro"
105
  }
106
  for en, pt in meses.items():
107
  data_hora_atual = data_hora_atual.replace(en, pt)
108
-
109
  # Contexto de reply (se existir)
110
  reply_context = ""
111
  if mensagem_citada:
112
  reply_context = f"\n[USUÁRIO ESTÁ RESPONDENDO A]: \"{mensagem_citada[:100]}...\"\n"
113
-
114
  # Histórico formatado
115
  historico_texto = ""
116
  if historico:
@@ -119,25 +110,10 @@ class MultiAPIManager:
119
  role = msg.get("role", "user")
120
  content = msg.get("content", "")
121
  historico_texto += f"{role.upper()}: {content}\n"
122
-
123
  # Prompt final
124
- prompt = f"""{config.PERSONA.format(humor=humor, tom_usuario=tom_usuario)}
125
-
126
- {config.SYSTEM_PROMPT.format(mensagem_citada=mensagem_citada or "nenhuma", humor=humor)}
127
-
128
- DATA E HORA ATUAL: {data_hora_atual}
129
- {info_context}
130
-
131
- CONTEXTO DA CONVERSA:
132
- {historico_texto}
133
- {reply_context}
134
-
135
- USUÁRIO: {mensagem}
136
-
137
- AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
138
-
139
  return prompt
140
-
141
  # === API 1: MISTRAL ===
142
  def _chamar_mistral(self, prompt: str) -> str:
143
  """Chama Mistral AI"""
@@ -155,12 +131,16 @@ AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
155
  headers=headers,
156
  timeout=self.timeout
157
  )
 
158
  if resp.status_code == 200:
159
  return resp.json()["choices"][0]["message"]["content"].strip()
 
 
 
160
  except Exception as e:
161
  logger.warning(f"Mistral falhou: {e}")
162
- return None
163
-
164
  # === API 2: GEMINI ===
165
  def _chamar_gemini(self, prompt: str) -> str:
166
  """Chama Google Gemini"""
@@ -174,12 +154,16 @@ AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
174
  }
175
  }
176
  resp = requests.post(url, json=payload, timeout=self.timeout)
 
177
  if resp.status_code == 200:
178
  return resp.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
 
 
 
179
  except Exception as e:
180
  logger.warning(f"Gemini falhou: {e}")
181
- return None
182
-
183
  # === API 3: GROQ ===
184
  def _chamar_groq(self, prompt: str) -> str:
185
  """Chama Groq (ultra-rápido)"""
@@ -197,12 +181,16 @@ AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
197
  headers=headers,
198
  timeout=self.timeout
199
  )
 
200
  if resp.status_code == 200:
201
  return resp.json()["choices"][0]["message"]["content"].strip()
 
 
 
202
  except Exception as e:
203
  logger.warning(f"Groq falhou: {e}")
204
- return None
205
-
206
  # === API 4: COHERE ===
207
  def _chamar_cohere(self, prompt: str) -> str:
208
  """Chama Cohere"""
@@ -220,12 +208,16 @@ AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
220
  headers=headers,
221
  timeout=self.timeout
222
  )
 
223
  if resp.status_code == 200:
224
  return resp.json()["text"].strip()
 
 
 
225
  except Exception as e:
226
  logger.warning(f"Cohere falhou: {e}")
227
- return None
228
-
229
  # === API 5: TOGETHER AI ===
230
  def _chamar_together(self, prompt: str) -> str:
231
  """Chama Together AI"""
@@ -243,12 +235,16 @@ AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
243
  headers=headers,
244
  timeout=self.timeout
245
  )
 
246
  if resp.status_code == 200:
247
  return resp.json()["choices"][0]["message"]["content"].strip()
 
 
 
248
  except Exception as e:
249
  logger.warning(f"Together falhou: {e}")
250
- return None
251
-
252
  # === API 6: HUGGING FACE ===
253
  def _chamar_huggingface(self, prompt: str) -> str:
254
  """Chama HuggingFace Inference API"""
@@ -261,67 +257,68 @@ AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
261
  headers=headers,
262
  timeout=self.timeout
263
  )
 
264
  if resp.status_code == 200:
265
  return resp.json()[0]["generated_text"].split("AKIRA:")[-1].strip()
 
 
 
266
  except Exception as e:
267
  logger.warning(f"HuggingFace falhou: {e}")
268
- return None
269
-
270
  # === MÉTODO PRINCIPAL DE GERAÇÃO ===
271
- def gerar_resposta(self, mensagem: str, historico: list, mensagem_citada: str,
272
- humor: str, tom_usuario: str) -> str:
273
- """
274
- Tenta gerar resposta usando todas as APIs na ordem configurada
 
 
275
  """
276
  prompt = self._construir_prompt(mensagem, historico, mensagem_citada, humor, tom_usuario)
277
-
278
- # Tenta cada API na ordem de fallback
279
- for api_name in config.API_FALLBACK_ORDER:
280
- if api_name not in self.apis_disponiveis:
281
- continue
282
-
283
- logger.info(f"Tentando {api_name.upper()}...")
284
-
285
- try:
286
- if api_name == "mistral":
287
- resposta = self._chamar_mistral(prompt)
288
- elif api_name == "gemini":
289
- resposta = self._chamar_gemini(prompt)
290
- elif api_name == "groq":
291
- resposta = self._chamar_groq(prompt)
292
- elif api_name == "cohere":
293
- resposta = self._chamar_cohere(prompt)
294
- elif api_name == "together":
295
- resposta = self._chamar_together(prompt)
296
- elif api_name == "huggingface":
297
- resposta = self._chamar_huggingface(prompt)
298
-
299
- if resposta:
300
- logger.success(f"✓ Resposta gerada via {api_name.upper()}")
301
- return self._limpar_resposta(resposta)
302
-
303
- except Exception as e:
304
- logger.error(f"{api_name} erro crítico: {e}")
305
-
306
- # Se todas falharem
307
- return "Barra no bardeado."
308
-
309
  def _limpar_resposta(self, resposta: str) -> str:
310
  """Remove markdown e limita tamanho"""
311
  # Remove markdown
312
  resposta = resposta.replace("**", "").replace("*", "")
313
  resposta = resposta.replace("```", "").replace("`", "")
314
-
315
  # Remove prefixos comuns de IA
316
  prefixos = ["AKIRA:", "Akira:", "RESPOSTA:", "Resposta:"]
317
  for p in prefixos:
318
  if resposta.startswith(p):
319
  resposta = resposta[len(p):].strip()
320
-
321
  # Limita tamanho (máximo 300 caracteres)
322
  if len(resposta) > 300:
323
  resposta = resposta[:297] + "..."
324
-
325
  return resposta.strip()
326
 
327
  # ============================================================================
@@ -338,7 +335,7 @@ class AkiraAPI:
338
  self.web_search = get_web_search() # Instância de WebSearch
339
  self._setup_routes()
340
  self._setup_trainer()
341
-
342
  def _setup_trainer(self):
343
  """Inicializa treinamento (desativado por padrão)"""
344
  if getattr(self.config, 'START_PERIODIC_TRAINER', False):
@@ -347,10 +344,9 @@ class AkiraAPI:
347
  logger.info("Treinamento periódico INICIADO")
348
  except Exception as e:
349
  logger.error(f"Treinador falhou: {e}")
350
-
351
  def _setup_routes(self):
352
  """Configura rotas Flask"""
353
-
354
  @self.api.before_request
355
  def handle_options():
356
  if request.method == 'OPTIONS':
@@ -359,12 +355,12 @@ class AkiraAPI:
359
  resp.headers['Access-Control-Allow-Headers'] = 'Content-Type'
360
  resp.headers['Access-Control-Allow-Methods'] = 'POST, GET'
361
  return resp
362
-
363
  @self.api.after_request
364
  def add_cors(response):
365
  response.headers['Access-Control-Allow-Origin'] = '*'
366
  return response
367
-
368
  @self.api.route('/akira', methods=['POST'])
369
  def akira_endpoint():
370
  try:
@@ -373,21 +369,16 @@ class AkiraAPI:
373
  numero = data.get('numero', '').strip()
374
  mensagem = data.get('mensagem', '').strip()
375
  mensagem_citada = data.get('mensagem_citada', '').strip()
376
-
377
  if not mensagem and not mensagem_citada:
378
  return jsonify({'error': 'mensagem obrigatória'}), 400
379
-
380
  logger.info(f"[{usuario}] ({numero}): {mensagem[:60]}")
381
-
382
  # === RESPOSTA RÁPIDA PARA HORA ===
383
  if any(k in mensagem.lower() for k in ["hora", "horas", "que horas"]):
384
  agora = datetime.datetime.now()
385
  return jsonify({'resposta': f"São {agora.strftime('%H:%M')} em Luanda, puto."})
386
-
387
  # === DETECTAR INTENÇÃO DE BUSCA WEB ===
388
  intencao_busca = WebSearch.detectar_intencao_busca(mensagem)
389
  contexto_web = ""
390
-
391
  if intencao_busca == "noticias":
392
  logger.info("Buscando notícias de Angola...")
393
  contexto_web = self.web_search.pesquisar_noticias_angola()
@@ -404,21 +395,17 @@ class AkiraAPI:
404
  elif intencao_busca == "busca_geral":
405
  logger.info("Buscando informações gerais...")
406
  contexto_web = self.web_search.buscar_geral(mensagem)
407
-
408
  # === CONTEXTO DO USUÁRIO ===
409
  contexto = self._get_user_context(numero)
410
  historico = contexto.obter_historico_para_llm()
411
-
412
  # === ANÁLISE DE TOM E HUMOR ===
413
  analise = contexto.analisar_intencao_e_normalizar(mensagem, historico)
414
  tom_usuario = analise.get("estilo", "casual")
415
  humor_atual = contexto.obter_emocao_atual()
416
-
417
  # === VERIFICAR SE É USUÁRIO PRIVILEGIADO ===
418
  if numero in config.USUARIOS_PRIVILEGIADOS:
419
  tom_usuario = "formal"
420
  logger.info(f"Usuário privilegiado detectado: {config.USUARIOS_PRIVILEGIADOS[numero]}")
421
-
422
  # === GERAR RESPOSTA VIA MULTI-API ===
423
  # Adiciona contexto web ao histórico se disponível
424
  historico_com_web = historico.copy()
@@ -427,7 +414,6 @@ class AkiraAPI:
427
  "role": "system",
428
  "content": f"CONTEXTO ADICIONAL (busca web):\n{contexto_web}"
429
  })
430
-
431
  resposta = self.llm_manager.gerar_resposta(
432
  mensagem=mensagem,
433
  historico=historico_com_web,
@@ -435,10 +421,8 @@ class AkiraAPI:
435
  humor=humor_atual,
436
  tom_usuario=tom_usuario
437
  )
438
-
439
  # === SALVAR NO BANCO + CONTEXTO ===
440
  contexto.atualizar_contexto(mensagem, resposta)
441
-
442
  try:
443
  trainer = Treinamento(self.db)
444
  trainer.registrar_interacao(
@@ -451,17 +435,15 @@ class AkiraAPI:
451
  )
452
  except Exception as e:
453
  logger.warning(f"Erro ao treinar: {e}")
454
-
455
  return jsonify({'resposta': resposta})
456
-
457
  except Exception as e:
458
  logger.exception("Erro em /akira")
459
  return jsonify({'resposta': 'Erro interno, já volto!'}), 500
460
-
461
  @self.api.route('/health', methods=['GET'])
462
  def health_check():
463
  return 'OK', 200
464
-
465
  def _get_user_context(self, numero: str) -> Contexto:
466
  """Retorna contexto do usuário (com cache)"""
467
  if not numero:
 
1
  # modules/api.py — AKIRA V19 ULTIMATE (Dezembro 2025)
2
+ """API Flask com 6 provedores de IA em fallback cascata
 
3
  - Mistral → Gemini → Groq → Cohere → Together → HuggingFace
4
  - Respostas em <5s (média)
5
  - Contexto de reply estruturado
 
51
 
52
  class MultiAPIManager:
53
  """Gerencia chamadas para 6 APIs com fallback automático"""
 
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 (ignora vazias)"""
61
  apis = []
62
+ if config.MISTRAL_API_KEY and config.MISTRAL_API_KEY.strip():
63
  apis.append("mistral")
64
+ if config.GEMINI_API_KEY and config.GEMINI_API_KEY.strip():
65
  apis.append("gemini")
66
+ if config.GROQ_API_KEY and config.GROQ_API_KEY.strip():
67
  apis.append("groq")
68
+ if config.COHERE_API_KEY and config.COHERE_API_KEY.strip():
69
  apis.append("cohere")
70
+ if config.TOGETHER_API_KEY and config.TOGETHER_API_KEY.strip():
71
  apis.append("together")
72
+ if config.HF_API_KEY and config.HF_API_KEY.strip():
73
  apis.append("huggingface")
74
  return apis
75
+
76
+ def _construir_prompt(self, mensagem: str, historico: list, mensagem_citada: str, humor: str, tom_usuario: str) -> str:
 
77
  """Constrói prompt otimizado com contexto"""
 
78
  # === INFORMAÇÕES DA EMPRESA (SOFTEDGE) ===
79
  empresa_info = EmpresaInfo()
80
  info_context = ""
 
81
  # Detecta se usuário pergunta sobre criador/empresa
82
  msg_lower = mensagem.lower()
83
  if any(palavra in msg_lower for palavra in ["criou", "criador", "quem fez", "desenvolveu", "softedge", "isaac"]):
 
86
  info_context = f"\n[INFO IMPORTANTE]: {empresa_info.get_resposta_sobre_empresa(mensagem, tom_formal)}\n"
87
  elif "softedge" in msg_lower or "empresa" in msg_lower:
88
  info_context = f"\n[INFO IMPORTANTE]: Softedge é empresa angolana de IA fundada por Isaac Quarenta em 2024. WhatsApp: {empresa_info.get_canal_whatsapp()}\n"
 
89
  # === DATA E HORA ATUAL ===
90
  from datetime import datetime
91
  agora = datetime.now()
92
  data_hora_atual = agora.strftime("%d de %B de %Y, %H:%M")
93
  # Traduz mês para português
94
  meses = {
95
+ "January": "janeiro", "February": "fevereiro", "March": "março", "April": "abril",
96
+ "May": "maio", "June": "junho", "July": "julho", "August": "agosto",
97
+ "September": "setembro", "October": "outubro", "November": "novembro", "December": "dezembro"
 
98
  }
99
  for en, pt in meses.items():
100
  data_hora_atual = data_hora_atual.replace(en, pt)
 
101
  # Contexto de reply (se existir)
102
  reply_context = ""
103
  if mensagem_citada:
104
  reply_context = f"\n[USUÁRIO ESTÁ RESPONDENDO A]: \"{mensagem_citada[:100]}...\"\n"
 
105
  # Histórico formatado
106
  historico_texto = ""
107
  if historico:
 
110
  role = msg.get("role", "user")
111
  content = msg.get("content", "")
112
  historico_texto += f"{role.upper()}: {content}\n"
 
113
  # Prompt final
114
+ prompt = f"""{config.PERSONA.format(humor=humor, tom_usuario=tom_usuario)}{config.SYSTEM_PROMPT.format(mensagem_citada=mensagem_citada or "nenhuma", humor=humor)}DATA E HORA ATUAL: {data_hora_atual}{info_context}CONTEXTO DA CONVERSA:{historico_texto}{reply_context}USUÁRIO: {mensagem}AKIRA (responda EM 1-2 LINHAS curtas, sem markdown):"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  return prompt
116
+
117
  # === API 1: MISTRAL ===
118
  def _chamar_mistral(self, prompt: str) -> str:
119
  """Chama Mistral AI"""
 
131
  headers=headers,
132
  timeout=self.timeout
133
  )
134
+ logger.debug(f"Mistral response: {resp.status_code} - {resp.text[:100]}...")
135
  if resp.status_code == 200:
136
  return resp.json()["choices"][0]["message"]["content"].strip()
137
+ else:
138
+ logger.warning(f"Mistral erro: {resp.status_code} - {resp.text}")
139
+ return None
140
  except Exception as e:
141
  logger.warning(f"Mistral falhou: {e}")
142
+ return None
143
+
144
  # === API 2: GEMINI ===
145
  def _chamar_gemini(self, prompt: str) -> str:
146
  """Chama Google Gemini"""
 
154
  }
155
  }
156
  resp = requests.post(url, json=payload, timeout=self.timeout)
157
+ logger.debug(f"Gemini response: {resp.status_code} - {resp.text[:100]}...")
158
  if resp.status_code == 200:
159
  return resp.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
160
+ else:
161
+ logger.warning(f"Gemini erro: {resp.status_code} - {resp.text}")
162
+ return None
163
  except Exception as e:
164
  logger.warning(f"Gemini falhou: {e}")
165
+ return None
166
+
167
  # === API 3: GROQ ===
168
  def _chamar_groq(self, prompt: str) -> str:
169
  """Chama Groq (ultra-rápido)"""
 
181
  headers=headers,
182
  timeout=self.timeout
183
  )
184
+ logger.debug(f"Groq response: {resp.status_code} - {resp.text[:100]}...")
185
  if resp.status_code == 200:
186
  return resp.json()["choices"][0]["message"]["content"].strip()
187
+ else:
188
+ logger.warning(f"Groq erro: {resp.status_code} - {resp.text}")
189
+ return None
190
  except Exception as e:
191
  logger.warning(f"Groq falhou: {e}")
192
+ return None
193
+
194
  # === API 4: COHERE ===
195
  def _chamar_cohere(self, prompt: str) -> str:
196
  """Chama Cohere"""
 
208
  headers=headers,
209
  timeout=self.timeout
210
  )
211
+ logger.debug(f"Cohere response: {resp.status_code} - {resp.text[:100]}...")
212
  if resp.status_code == 200:
213
  return resp.json()["text"].strip()
214
+ else:
215
+ logger.warning(f"Cohere erro: {resp.status_code} - {resp.text}")
216
+ return None
217
  except Exception as e:
218
  logger.warning(f"Cohere falhou: {e}")
219
+ return None
220
+
221
  # === API 5: TOGETHER AI ===
222
  def _chamar_together(self, prompt: str) -> str:
223
  """Chama Together AI"""
 
235
  headers=headers,
236
  timeout=self.timeout
237
  )
238
+ logger.debug(f"Together response: {resp.status_code} - {resp.text[:100]}...")
239
  if resp.status_code == 200:
240
  return resp.json()["choices"][0]["message"]["content"].strip()
241
+ else:
242
+ logger.warning(f"Together erro: {resp.status_code} - {resp.text}")
243
+ return None
244
  except Exception as e:
245
  logger.warning(f"Together falhou: {e}")
246
+ return None
247
+
248
  # === API 6: HUGGING FACE ===
249
  def _chamar_huggingface(self, prompt: str) -> str:
250
  """Chama HuggingFace Inference API"""
 
257
  headers=headers,
258
  timeout=self.timeout
259
  )
260
+ logger.debug(f"HF response: {resp.status_code} - {resp.text[:100]}...")
261
  if resp.status_code == 200:
262
  return resp.json()[0]["generated_text"].split("AKIRA:")[-1].strip()
263
+ else:
264
+ logger.warning(f"HF erro: {resp.status_code} - {resp.text}")
265
+ return None
266
  except Exception as e:
267
  logger.warning(f"HuggingFace falhou: {e}")
268
+ return None
269
+
270
  # === MÉTODO PRINCIPAL DE GERAÇÃO ===
271
+ def gerar_resposta(self, mensagem: str, historico: list, mensagem_citada: str, humor: str, tom_usuario: str) -> str:
272
+ """
273
+ Tenta gerar resposta usando todas as APIs na ordem configurada.
274
+ - 2 retries por API
275
+ - Até 2 loops full se todas falharem
276
+ - Logs detalhados
277
  """
278
  prompt = self._construir_prompt(mensagem, historico, mensagem_citada, humor, tom_usuario)
279
+ max_loops = 2
280
+ for loop in range(max_loops):
281
+ logger.info(f"Fallback loop {loop+1}/{max_loops}")
282
+ for api_name in config.API_FALLBACK_ORDER:
283
+ if api_name not in self.apis_disponiveis:
284
+ continue
285
+ for retry in range(2): # 2 tentativas por API
286
+ logger.info(f"Tentando {api_name.upper()} (retry {retry+1}/2)...")
287
+ try:
288
+ if api_name == "mistral":
289
+ resposta = self._chamar_mistral(prompt)
290
+ elif api_name == "gemini":
291
+ resposta = self._chamar_gemini(prompt)
292
+ elif api_name == "groq":
293
+ resposta = self._chamar_groq(prompt)
294
+ elif api_name == "cohere":
295
+ resposta = self._chamar_cohere(prompt)
296
+ elif api_name == "together":
297
+ resposta = self._chamar_together(prompt)
298
+ elif api_name == "huggingface":
299
+ resposta = self._chamar_huggingface(prompt)
300
+ if resposta:
301
+ logger.success(f"✓ Resposta gerada via {api_name.upper()}")
302
+ return self._limpar_resposta(resposta)
303
+ time.sleep(1) # Sleep entre retries
304
+ except Exception as e:
305
+ logger.error(f"{api_name} erro crítico (retry {retry+1}): {e}")
306
+ time.sleep(2) # Sleep entre loops full
307
+ return "Barra no bardeado"
308
+
 
 
309
  def _limpar_resposta(self, resposta: str) -> str:
310
  """Remove markdown e limita tamanho"""
311
  # Remove markdown
312
  resposta = resposta.replace("**", "").replace("*", "")
313
  resposta = resposta.replace("```", "").replace("`", "")
 
314
  # Remove prefixos comuns de IA
315
  prefixos = ["AKIRA:", "Akira:", "RESPOSTA:", "Resposta:"]
316
  for p in prefixos:
317
  if resposta.startswith(p):
318
  resposta = resposta[len(p):].strip()
 
319
  # Limita tamanho (máximo 300 caracteres)
320
  if len(resposta) > 300:
321
  resposta = resposta[:297] + "..."
 
322
  return resposta.strip()
323
 
324
  # ============================================================================
 
335
  self.web_search = get_web_search() # Instância de WebSearch
336
  self._setup_routes()
337
  self._setup_trainer()
338
+
339
  def _setup_trainer(self):
340
  """Inicializa treinamento (desativado por padrão)"""
341
  if getattr(self.config, 'START_PERIODIC_TRAINER', False):
 
344
  logger.info("Treinamento periódico INICIADO")
345
  except Exception as e:
346
  logger.error(f"Treinador falhou: {e}")
347
+
348
  def _setup_routes(self):
349
  """Configura rotas Flask"""
 
350
  @self.api.before_request
351
  def handle_options():
352
  if request.method == 'OPTIONS':
 
355
  resp.headers['Access-Control-Allow-Headers'] = 'Content-Type'
356
  resp.headers['Access-Control-Allow-Methods'] = 'POST, GET'
357
  return resp
358
+
359
  @self.api.after_request
360
  def add_cors(response):
361
  response.headers['Access-Control-Allow-Origin'] = '*'
362
  return response
363
+
364
  @self.api.route('/akira', methods=['POST'])
365
  def akira_endpoint():
366
  try:
 
369
  numero = data.get('numero', '').strip()
370
  mensagem = data.get('mensagem', '').strip()
371
  mensagem_citada = data.get('mensagem_citada', '').strip()
 
372
  if not mensagem and not mensagem_citada:
373
  return jsonify({'error': 'mensagem obrigatória'}), 400
 
374
  logger.info(f"[{usuario}] ({numero}): {mensagem[:60]}")
 
375
  # === RESPOSTA RÁPIDA PARA HORA ===
376
  if any(k in mensagem.lower() for k in ["hora", "horas", "que horas"]):
377
  agora = datetime.datetime.now()
378
  return jsonify({'resposta': f"São {agora.strftime('%H:%M')} em Luanda, puto."})
 
379
  # === DETECTAR INTENÇÃO DE BUSCA WEB ===
380
  intencao_busca = WebSearch.detectar_intencao_busca(mensagem)
381
  contexto_web = ""
 
382
  if intencao_busca == "noticias":
383
  logger.info("Buscando notícias de Angola...")
384
  contexto_web = self.web_search.pesquisar_noticias_angola()
 
395
  elif intencao_busca == "busca_geral":
396
  logger.info("Buscando informações gerais...")
397
  contexto_web = self.web_search.buscar_geral(mensagem)
 
398
  # === CONTEXTO DO USUÁRIO ===
399
  contexto = self._get_user_context(numero)
400
  historico = contexto.obter_historico_para_llm()
 
401
  # === ANÁLISE DE TOM E HUMOR ===
402
  analise = contexto.analisar_intencao_e_normalizar(mensagem, historico)
403
  tom_usuario = analise.get("estilo", "casual")
404
  humor_atual = contexto.obter_emocao_atual()
 
405
  # === VERIFICAR SE É USUÁRIO PRIVILEGIADO ===
406
  if numero in config.USUARIOS_PRIVILEGIADOS:
407
  tom_usuario = "formal"
408
  logger.info(f"Usuário privilegiado detectado: {config.USUARIOS_PRIVILEGIADOS[numero]}")
 
409
  # === GERAR RESPOSTA VIA MULTI-API ===
410
  # Adiciona contexto web ao histórico se disponível
411
  historico_com_web = historico.copy()
 
414
  "role": "system",
415
  "content": f"CONTEXTO ADICIONAL (busca web):\n{contexto_web}"
416
  })
 
417
  resposta = self.llm_manager.gerar_resposta(
418
  mensagem=mensagem,
419
  historico=historico_com_web,
 
421
  humor=humor_atual,
422
  tom_usuario=tom_usuario
423
  )
 
424
  # === SALVAR NO BANCO + CONTEXTO ===
425
  contexto.atualizar_contexto(mensagem, resposta)
 
426
  try:
427
  trainer = Treinamento(self.db)
428
  trainer.registrar_interacao(
 
435
  )
436
  except Exception as e:
437
  logger.warning(f"Erro ao treinar: {e}")
 
438
  return jsonify({'resposta': resposta})
 
439
  except Exception as e:
440
  logger.exception("Erro em /akira")
441
  return jsonify({'resposta': 'Erro interno, já volto!'}), 500
442
+
443
  @self.api.route('/health', methods=['GET'])
444
  def health_check():
445
  return 'OK', 200
446
+
447
  def _get_user_context(self, numero: str) -> Contexto:
448
  """Retorna contexto do usuário (com cache)"""
449
  if not numero: