akra35567 commited on
Commit
cbb1a12
·
verified ·
1 Parent(s): 0f76c7a

Update modules/api.py

Browse files
Files changed (1) hide show
  1. modules/api.py +184 -285
modules/api.py CHANGED
@@ -1,13 +1,13 @@
1
- # modules/api.py — AKIRA V21 FINAL ADAPTADO (DEZEMBRO 2025)
2
  """
3
- ✅ TOTALMENTE ADAPTADO ao config.py BRUTAL ATUALIZADO
4
- TOTALMENTE COMPATÍVEL com index.js otimizado (mensagem_citada completa)
5
- Sistema emocional DistilBERT
6
- Xingamento automático integrado
7
- Usa prompts de ironia máxima com contexto completo
8
- Processa reply_metadata e mensagem_citada do index.js corretamente
9
- ✅ CORREÇÃO: Compatível com novo payload (reply_metadata + mensagem_citada)
10
  """
 
11
  import time
12
  import datetime
13
  import requests
@@ -19,12 +19,10 @@ from typing import Dict, Any, List, Optional
19
  from flask import Blueprint, request, jsonify, make_response
20
  from loguru import logger
21
 
22
- # Importa módulos locais
23
- from .contexto import Contexto
24
  from .database import Database
25
  from .treinamento import Treinamento
26
- from .web_search import get_web_search, WebSearch
27
- from .empresa_info import EmpresaInfo
28
  import modules.config as config
29
 
30
  # ============================================================================
@@ -59,7 +57,7 @@ class SimpleTTLCache:
59
  return default
60
 
61
  # ============================================================================
62
- # 🧠 GERENCIADOR MULTI-API (OTIMIZADO PARA NOVO CONTEXTO)
63
  # ============================================================================
64
  class MultiAPIManager:
65
  def __init__(self):
@@ -68,6 +66,7 @@ class MultiAPIManager:
68
  logger.info(f"✅ APIs disponíveis: {', '.join(self.apis_disponiveis)}")
69
 
70
  def _verificar_apis(self):
 
71
  apis = []
72
  if config.MISTRAL_API_KEY and len(config.MISTRAL_API_KEY) > 10:
73
  apis.append("mistral")
@@ -90,17 +89,12 @@ class MultiAPIManager:
90
  reply_info: Optional[Dict] = None
91
  ) -> str:
92
  """
93
- Constrói prompt usando a função do config.py ATUALIZADO
94
- TOTALMENTE COMPATÍVEL com o novo index.js (mensagem_citada completa)
95
  """
96
  try:
97
- # Log do que estamos recebendo
98
  logger.debug(f"📝 Construindo prompt para: {usuario}")
99
- logger.debug(f"📝 Mensagem: {mensagem[:100]}...")
100
- logger.debug(f"📝 Mensagem citada: {mensagem_citada[:100] if mensagem_citada else '[Nenhuma]'}...")
101
- logger.debug(f"📝 Reply info disponível: {bool(reply_info)}")
102
 
103
- # Usa a função principal do config.py atualizado
104
  prompt = config.construir_prompt_api(
105
  mensagem=mensagem,
106
  historico=historico,
@@ -112,31 +106,25 @@ class MultiAPIManager:
112
  )
113
 
114
  logger.debug(f"✅ Prompt construído: {len(prompt)} caracteres")
115
- logger.debug(f"📄 Primeiras 3 linhas do prompt:")
116
- for i, line in enumerate(prompt.split('\n')[:3]):
117
- logger.debug(f" {i}: {line[:100]}...")
118
-
119
  return prompt
120
 
121
  except Exception as e:
122
  logger.error(f"❌ Erro ao construir prompt: {e}")
123
- # Fallback para prompt simples (com mensagem citada)
124
  return f"""AKIRA V21 — PERSONALIDADE REAL SEM FILTRO
125
  Você é Akira, angolana de 18 anos de Luanda.
126
 
127
- CONTEXTO: {f'O usuário está respondendo a uma mensagem: "{mensagem_citada[:150]}{"..." if len(mensagem_citada) > 150 else ""}"' if mensagem_citada else 'Conversa normal'}
128
-
129
  USUÁRIO: {usuario}
130
  MENSAGEM: {mensagem}
 
131
 
132
  RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
133
 
134
  def _obter_parametros_api(self, analise: Dict[str, Any]) -> Dict[str, Any]:
135
- """Obtém parâmetros otimizados para API baseado na emoção e modo"""
136
  emocao = analise.get("emocao_primaria", "neutral")
137
  modo = analise.get("modo_resposta", "normal_ironico")
138
 
139
- # Usa função do config.py atualizado
140
  return config.obter_parametros_api(emocao, modo)
141
 
142
  def gerar_resposta(
@@ -149,16 +137,10 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
149
  tipo_conversa: str,
150
  reply_info: Optional[Dict] = None
151
  ) -> str:
152
- """Gera resposta usando multi-API fallback (com contexto completo)"""
153
- # Log do que está sendo processado
154
  logger.info(f"🎯 Gerando resposta para {usuario}")
155
- logger.info(f"📤 Mensagem: {mensagem[:80]}...")
156
- if mensagem_citada:
157
- logger.info(f"📝 Mensagem citada disponível: {mensagem_citada[:80]}...")
158
- if reply_info:
159
- logger.info(f"🔍 Reply metadata: reply_to_bot={reply_info.get('reply_to_bot', False)}")
160
 
161
- # Construir prompt com contexto completo
162
  prompt = self._construir_prompt(
163
  mensagem=mensagem,
164
  historico=historico,
@@ -172,42 +154,41 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
172
  # Obter parâmetros
173
  parametros = self._obter_parametros_api(analise)
174
 
175
- logger.debug(f"🔧 Parâmetros: {parametros}")
176
-
177
- # Tentar APIs em ordem de fallback
178
  for api_name in config.API_FALLBACK_ORDER:
179
  if api_name not in self.apis_disponiveis:
180
  continue
181
 
182
  try:
183
- logger.debug(f"🔄 Tentando API: {api_name}")
184
  resposta = self._chamar_api(api_name, prompt, parametros)
185
  if resposta:
186
  resposta_limpa = self._limpar_resposta(resposta)
187
- logger.info(f"✅ API {api_name} respondeu: {resposta_limpa[:100]}...")
188
  return resposta_limpa
189
-
190
  except Exception as e:
191
- logger.warning(f"❌ API {api_name} falhou: {str(e)[:100]}")
192
  continue
193
 
194
- # Fallback: respostas curtas e brutais baseadas no contexto
195
- fallbacks = self._gerar_fallback_contextual(mensagem, mensagem_citada, reply_info)
196
- return random.choice(fallbacks)
197
 
198
- def _chamar_api(self, api_name: str, prompt: str, parametros: Dict[str, Any]) -> str:
199
- """Chama API específica com tratamento de erros"""
200
- if api_name == "mistral":
201
- return self._chamar_mistral(prompt, parametros)
202
- elif api_name == "gemini":
203
- return self._chamar_gemini(prompt, parametros)
204
- elif api_name == "groq":
205
- return self._chamar_groq(prompt, parametros)
206
- elif api_name == "cohere":
207
- return self._chamar_cohere(prompt, parametros)
208
- return ""
 
 
 
209
 
210
  def _chamar_mistral(self, prompt: str, params: Dict[str, Any]) -> str:
 
211
  try:
212
  response = requests.post(
213
  "https://api.mistral.ai/v1/chat/completions",
@@ -226,10 +207,11 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
226
  response.raise_for_status()
227
  return response.json()["choices"][0]["message"]["content"].strip()
228
  except Exception as e:
229
- logger.debug(f"Mistral falhou: {e}")
230
  return ""
231
 
232
  def _chamar_gemini(self, prompt: str, params: Dict[str, Any]) -> str:
 
233
  try:
234
  response = requests.post(
235
  f"https://generativelanguage.googleapis.com/v1beta/models/{config.GEMINI_MODEL}:generateContent?key={config.GEMINI_API_KEY}",
@@ -245,10 +227,11 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
245
  response.raise_for_status()
246
  return response.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
247
  except Exception as e:
248
- logger.debug(f"Gemini falhou: {e}")
249
  return ""
250
 
251
  def _chamar_groq(self, prompt: str, params: Dict[str, Any]) -> str:
 
252
  try:
253
  response = requests.post(
254
  "https://api.groq.com/openai/v1/chat/completions",
@@ -264,10 +247,11 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
264
  response.raise_for_status()
265
  return response.json()["choices"][0]["message"]["content"].strip()
266
  except Exception as e:
267
- logger.debug(f"Groq falhou: {e}")
268
  return ""
269
 
270
  def _chamar_cohere(self, prompt: str, params: Dict[str, Any]) -> str:
 
271
  try:
272
  response = requests.post(
273
  "https://api.cohere.ai/v1/generate",
@@ -282,32 +266,28 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
282
  response.raise_for_status()
283
  return response.json()["generations"][0]["text"].strip()
284
  except Exception as e:
285
- logger.debug(f"Cohere falhou: {e}")
286
  return ""
287
 
288
  def _limpar_resposta(self, texto: str) -> str:
289
- """Limpa a resposta removendo formatação indesejada"""
290
  if not texto:
291
  return "…"
292
 
293
  # Remove markdown
294
  texto = re.sub(r'[\*`_]+', '', texto)
295
 
296
- # Remove aspas desnecessárias no início/fim
297
  texto = texto.strip('"\'')
298
 
299
- # Remove prefixos como "Akira:" ou "AKIRA:"
300
  texto = re.sub(r'^(Akira|AKIRA)[:\s\-]+', '', texto, flags=re.IGNORECASE)
301
 
302
- # Remove "Resposta:" ou "Resposta de Akira:"
303
- texto = re.sub(r'^(Resposta|Resposta de Akira)[:\s\-]+', '', texto, flags=re.IGNORECASE)
304
-
305
- # Remove múltiplos espaços
306
  texto = re.sub(r'\s+', ' ', texto)
307
 
308
- # Limita tamanho (mas preserva contexto)
309
  if len(texto) > 400:
310
- # Tenta encontrar um ponto final próximo
311
  last_period = texto[:397].rfind('.')
312
  if last_period > 300:
313
  texto = texto[:last_period + 1]
@@ -316,9 +296,9 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
316
 
317
  return texto.strip()
318
 
319
- def _gerar_fallback_contextual(self, mensagem: str, mensagem_citada: str, reply_info: Optional[Dict]) -> List[str]:
320
- """Gera fallbacks contextuais baseados na mensagem e contexto"""
321
- base_fallbacks = [
322
  "Puto, nem ligo.",
323
  "Foda-se.",
324
  "Ya, tá bom.",
@@ -331,80 +311,78 @@ RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
331
  "Não."
332
  ]
333
 
334
- # Se há mensagem citada, adiciona fallbacks contextuais
335
  if mensagem_citada:
336
- contextual_fallbacks = [
337
  f"Já disse isso.",
338
  f"Repito: {mensagem_citada[:50]}...",
339
  f"Ya, como disse.",
340
  f"Exactamente isso."
341
- ]
342
- base_fallbacks.extend(contextual_fallbacks)
343
 
344
  # Se é reply ao bot
345
  if reply_info and reply_info.get('reply_to_bot'):
346
- reply_fallbacks = [
347
  "Já disse isso, caralho.",
348
  "Repito o que disse.",
349
  "Ya, como eu disse.",
350
  "Exactamente o que falei."
351
- ]
352
- base_fallbacks.extend(reply_fallbacks)
353
 
354
- return base_fallbacks
355
 
356
  # ============================================================================
357
- # 🎯 CLASSE PRINCIPAL AKIRA API (ATUALIZADA)
358
  # ============================================================================
359
  class AkiraAPI:
360
  def __init__(self):
361
- """CORREÇÃO: Não aceita parâmetros - usa config.py atualizado diretamente"""
362
  self.api = Blueprint("akira_api", __name__)
363
  self.contexto_cache = SimpleTTLCache(ttl_seconds=300)
 
 
364
  self.db = Database(config.DB_PATH)
365
  self.llm_manager = MultiAPIManager()
366
- self.web_search = get_web_search()
367
- self._setup_routes()
368
- self._setup_trainer()
369
- logger.success("🚀 AKIRA V21 BRUTAL ATUALIZADA inicializada (compatível com novo index.js)")
370
-
371
- def _setup_trainer(self):
372
- """Inicializa treinamento periódico"""
373
  if config.START_PERIODIC_TRAINER:
374
  try:
375
- treinador = Treinamento(
376
  self.db,
377
  interval_hours=config.TRAINING_INTERVAL_HOURS
378
  )
379
- treinador.start_periodic_training()
380
  logger.info("✅ Treinamento periódico iniciado")
381
  except Exception as e:
382
  logger.error(f"❌ Erro no treinador: {e}")
 
 
 
 
383
 
384
  def _get_user_context(self, numero: str, tipo_conversa: str, grupo_id: str = "") -> Contexto:
385
- """Obtém contexto isolado por usuário/grupo"""
386
- # Chave única para cache
387
  if tipo_conversa == "grupo" and grupo_id:
388
  key = f"grupo_{grupo_id}"
389
  else:
390
  key = f"pv_{numero}"
391
 
392
- # Retorna do cache se disponível
393
  if key in self.contexto_cache:
394
  return self.contexto_cache[key]
395
 
396
- # Cria novo contexto
397
- ctx = Contexto(db=self.db, usuario=key)
398
  self.contexto_cache[key] = ctx
399
  return ctx
400
 
401
  def _processar_reset(self, numero: str, usuario: str, confirmacao: bool = False) -> Dict[str, Any]:
402
  """Processa comando /reset"""
403
- # Verifica permissão
404
  if not self.db.pode_usar_reset(numero):
405
  return {
406
  "error": "COMANDO RESTRITO",
407
- "resposta": "Só o boss pode usar /reset, puto. Vai sonhar."
408
  }
409
 
410
  if not confirmacao:
@@ -423,7 +401,7 @@ class AkiraAPI:
423
  }
424
 
425
  def _extrair_payload_indexjs(self, data: Dict[str, Any]) -> Dict[str, Any]:
426
- """Extrai e formata dados do payload do NOVO index.js (com mensagem_citada completa)"""
427
  payload = {
428
  'numero': str(data.get('numero', '')).strip(),
429
  'usuario': data.get('usuario', 'Anônimo').strip(),
@@ -433,30 +411,12 @@ class AkiraAPI:
433
  'grupo_id': data.get('grupo_id', ''),
434
  'grupo_nome': data.get('grupo_nome', ''),
435
  'is_reset': False,
436
- 'reply_metadata': data.get('reply_metadata', {}), # AGORA É reply_metadata
437
- 'reply_info': data.get('reply_info', {}), # Mantém para compatibilidade
438
- 'mensagem_citada': data.get('mensagem_citada', '') # AGORA RECEBE DIRETO DO PAYLOAD
439
  }
440
 
441
- # Log do que foi recebido
442
- logger.debug(f"📦 Payload recebido:")
443
- logger.debug(f" Usuário: {payload['usuario']}")
444
- logger.debug(f" Mensagem: {payload['mensagem'][:80]}...")
445
- logger.debug(f" Mensagem citada: {payload['mensagem_citada'][:80] if payload['mensagem_citada'] else '[Nenhuma]'}...")
446
- logger.debug(f" Reply metadata: {payload['reply_metadata']}")
447
- logger.debug(f" É reply: {payload['reply_metadata'].get('is_reply', False) if payload['reply_metadata'] else False}")
448
-
449
- # CORREÇÃO: Preferir reply_metadata, mas manter compatibilidade com reply_info
450
- reply_data = payload['reply_metadata'] if payload['reply_metadata'] else payload['reply_info']
451
-
452
- # Se não tem mensagem_citada diretamente, tenta extrair do reply_data
453
- if not payload['mensagem_citada'] and reply_data:
454
- # Tenta várias chaves possíveis
455
- for key in ['texto_mensagem_citada', 'texto', 'mensagem_citada', 'quoted_text']:
456
- if key in reply_data and reply_data[key]:
457
- payload['mensagem_citada'] = reply_data[key]
458
- logger.debug(f" Extraído mensagem_citada de {key}")
459
- break
460
 
461
  # Detecta /reset
462
  if payload['mensagem'].strip().lower() == '/reset':
@@ -464,48 +424,12 @@ class AkiraAPI:
464
 
465
  return payload
466
 
467
- def _buscar_informacao_web(self, mensagem: str) -> str:
468
- """Executa busca web se necessário"""
469
- try:
470
- intencao_busca = WebSearch.detectar_intencao_busca(mensagem)
471
-
472
- if not intencao_busca:
473
- return ""
474
-
475
- if intencao_busca == "noticias":
476
- return self.web_search.pesquisar_noticias_angola()
477
- elif intencao_busca == "clima":
478
- # Extrai cidade da mensagem
479
- cidade_match = re.search(r'(clima|tempo)(?: em| de)?\s+([A-Za-zÀ-ÿ\s]+)', mensagem, re.I)
480
- cidade = cidade_match.group(2).strip() if cidade_match else "Luanda"
481
- return self.web_search.buscar_clima(cidade)
482
- elif intencao_busca == "busca_geral":
483
- # Limpa termos de busca
484
- termos = mensagem.lower()
485
- palavras_remover = [
486
- "busca", "pesquisa", "pesquisar", "procurar", "procura",
487
- "web", "internet", "google", "wikipedia", "site",
488
- "informações", "dados", "saber", "conhecer", "descobrir",
489
- "me diz", "me fala", "diz", "fala", "explica"
490
- ]
491
- for palavra in palavras_remover:
492
- termos = re.sub(rf'\b{re.escape(palavra)}\b', '', termos, flags=re.I)
493
- termos = termos.strip()
494
-
495
- if termos and len(termos) > 2:
496
- return self.web_search.buscar_geral(termos)
497
-
498
- except Exception as e:
499
- logger.warning(f"Erro na busca web: {e}")
500
-
501
- return ""
502
-
503
  def _setup_routes(self):
504
- """Configura rotas da API ATUALIZADAS"""
505
 
506
  @self.api.before_request
507
  def handle_options():
508
- """Lida com requisições OPTIONS para CORS"""
509
  if request.method == 'OPTIONS':
510
  resp = make_response()
511
  resp.headers['Access-Control-Allow-Origin'] = '*'
@@ -515,27 +439,24 @@ class AkiraAPI:
515
 
516
  @self.api.after_request
517
  def add_cors(resp):
518
- """Adiciona headers CORS a todas as respostas"""
519
  resp.headers['Access-Control-Allow-Origin'] = '*'
520
  return resp
521
 
522
  @self.api.route('/akira', methods=['POST'])
523
  def akira_endpoint():
524
- """Endpoint principal - TOTALMENTE COMPATÍVEL com NOVO index.js"""
525
  try:
526
- # Recebe e processa payload
527
  data = request.get_json() or {}
528
  payload = self._extrair_payload_indexjs(data)
529
 
530
- # Log detalhado da requisição
531
  logger.info(
532
- f"📨 [{payload['usuario']}] ({payload['numero']}) "
533
- f"[{payload['tipo_conversa']}]: {payload['mensagem'][:80]}..."
534
  )
535
- if payload['mensagem_citada']:
536
- logger.info(f"📝 Mensagem citada: {payload['mensagem_citada'][:80]}...")
537
 
538
- # Validação básica
539
  if not payload['mensagem']:
540
  return jsonify({'error': 'Mensagem obrigatória'}), 400
541
 
@@ -550,141 +471,124 @@ class AkiraAPI:
550
  return jsonify(resultado), 403
551
  return jsonify(resultado)
552
 
553
- # Busca web (se necessário)
554
- contexto_web = ""
555
- if len(payload['mensagem']) > 10: # Só busca para mensagens mais longas
556
- contexto_web = self._buscar_informacao_web(payload['mensagem'])
557
-
558
- # Obtém contexto isolado
559
  contexto = self._get_user_context(
560
  numero=payload['numero'],
561
  tipo_conversa=payload['tipo_conversa'],
562
  grupo_id=payload['grupo_id']
563
  )
564
 
 
 
 
 
 
 
 
 
 
565
  # Obtém histórico
566
  historico = contexto.obter_historico_para_llm()
567
 
568
- # Verifica se usuário é privilegiado
569
  usuario_privilegiado = self.db.is_usuario_privilegiado(payload['numero'])
570
 
571
- # Analisa a mensagem (com mensagem_citada completa)
572
  analise = contexto.analisar_intencao_e_normalizar(
573
- payload['mensagem'],
574
- historico,
575
- payload['mensagem_citada']
 
576
  )
577
 
578
- # ADAPTAÇÃO: Prepara reply_info para passar ao config.py
579
- # Preferir reply_metadata, mas manter compatibilidade
580
- reply_info_for_config = payload['reply_metadata'] if payload['reply_metadata'] else payload['reply_info']
581
-
582
- # Adiciona informações extras à análise (INCLUI reply_metadata)
583
  analise.update({
584
  'usuario_privilegiado': usuario_privilegiado,
585
  'numero': payload['numero'],
586
  'tipo_mensagem': payload['tipo_mensagem'],
587
- 'reply_metadata': payload['reply_metadata'], # ENVIA PARA CONFIG.PY
588
- 'reply_info': reply_info_for_config # Para compatibilidade
589
  })
590
 
591
- # Adiciona contexto web ao histórico se houver
592
- historico_com_web = historico.copy()
593
- if contexto_web:
594
- historico_com_web.append({
595
- "role": "system",
596
- "content": f"[INFO WEB]: {contexto_web}"
597
- })
598
 
599
- # Gera resposta (PASSA mensagem_citada COMPLETA e reply_info)
600
  resposta = self.llm_manager.gerar_resposta(
601
  mensagem=payload['mensagem'],
602
- historico=historico_com_web,
603
- mensagem_citada=payload['mensagem_citada'], # ENVIA COMPLETA
604
  analise=analise,
605
  usuario=payload['usuario'],
606
  tipo_conversa=payload['tipo_conversa'],
607
- reply_info=reply_info_for_config # PASSA PARA CONFIG.PY
608
  )
609
 
610
- # Determina se é reply e reply_to_bot (USANDO reply_metadata)
611
- is_reply = bool(payload['mensagem_citada']) or (payload['reply_metadata'] and payload['reply_metadata'].get('is_reply', False))
 
 
612
  reply_to_bot = False
613
 
614
  if payload['reply_metadata']:
615
  reply_to_bot = payload['reply_metadata'].get('reply_to_bot', False)
616
- elif payload['reply_info']:
617
- reply_to_bot = payload['reply_info'].get('reply_to_bot', False)
618
- elif payload['mensagem_citada'] and payload['mensagem_citada'].startswith("[Respondendo à Akira:"):
619
- reply_to_bot = True
620
 
621
- # Atualiza contexto (INCLUI mensagem_citada completa)
622
  contexto.atualizar_contexto(
623
  mensagem=payload['mensagem'],
624
  resposta=resposta,
625
  numero=payload['numero'],
626
  is_reply=is_reply,
627
- mensagem_original=payload['mensagem_citada'], # GUARDA COMPLETA
628
  reply_to_bot=reply_to_bot
629
  )
630
 
631
- # Registra interação para treinamento (COM CONTEXTO COMPLETO)
632
- try:
633
- trainer = Treinamento(self.db)
634
- trainer.registrar_interacao(
635
- usuario=payload['usuario'],
636
- mensagem=payload['mensagem'],
637
- resposta=resposta,
638
- numero=payload['numero'],
639
- is_reply=is_reply,
640
- mensagem_original=payload['mensagem_citada'], # REGISTRA COMPLETA
641
- contexto=analise,
642
- tipo_conversa=payload['tipo_conversa'],
643
- tipo_mensagem=payload['tipo_mensagem'],
644
- reply_metadata=payload['reply_metadata'] # REGISTRA METADATA
645
- )
646
- except Exception as e:
647
- logger.warning(f"⚠️ Erro ao registrar interação: {e}")
 
648
 
649
- # Log da resposta
650
- logger.info(f"📤 Resposta gerada: {resposta[:80]}...")
651
 
652
  return jsonify({"resposta": resposta})
653
 
654
  except Exception as e:
655
- logger.error(f"❌ Erro crítico em /akira: {e}")
656
  import traceback
657
- logger.error(traceback.format_exc())
658
  return jsonify({
659
  "error": "Erro interno",
660
- "details": str(e)[:100],
661
- "resposta": "Barra no bardeado, puto. Erro interno."
662
  }), 500
663
 
664
  @self.api.route('/health', methods=['GET'])
665
  def health():
666
- """Endpoint de health check ATUALIZADO"""
667
  agora = datetime.datetime.now() + datetime.timedelta(hours=config.TIMEZONE_OFFSET_HOURS)
668
  return jsonify({
669
- "status": "✅ AKIRA V21 BRUTAL ONLINE (ATUALIZADA)",
670
  "hora_luanda": agora.strftime("%H:%M"),
671
- "versao": "Dezembro 2025 - Mensagem Citada Completa",
672
  "apis_disponiveis": self.llm_manager.apis_disponiveis,
673
- "compatibilidade": {
674
- "index_js_atualizado": True,
675
- "mensagem_citada_completa": True,
676
- "reply_metadata": True,
677
- "contexto_completo": True
678
- },
679
- "estatisticas": {
680
- "cache_size": len(self.contexto_cache._store),
681
- "apis_ativas": len(self.llm_manager.apis_disponiveis)
682
- }
683
  })
684
 
685
  @self.api.route('/reset', methods=['POST'])
686
  def reset_endpoint():
687
- """Endpoint dedicado para reset"""
688
  try:
689
  data = request.get_json() or {}
690
  numero = str(data.get('numero', '')).strip()
@@ -705,35 +609,27 @@ class AkiraAPI:
705
 
706
  @self.api.route('/info', methods=['GET'])
707
  def info():
708
- """Informações sobre a API ATUALIZADA"""
709
  return jsonify({
710
- "nome": "Akira V21 (ATUALIZADA)",
711
- "descricao": "IA com personalidade brutal e irônica - COM CONTEXTO COMPLETO",
712
  "desenvolvedor": "Isaac Quarenta",
713
  "empresa": "Softedge",
714
  "versao": config.VERSAO,
715
  "endpoints": ["/akira", "/health", "/reset", "/info"],
716
- "caracteristicas": [
717
- "Mensagem citada completa",
718
- "Contexto de reply otimizado",
719
- "Xingamento automático",
720
- "Respostas curtas e brutais",
721
- "Compatível com novo index.js"
722
- ],
723
  "configuracoes": {
724
  "temperatura_padrao": config.TEMPERATURE,
725
  "max_tokens": config.MAX_TOKENS,
726
- "timezone_offset": config.TIMEZONE_OFFSET_HOURS,
727
- "apis_suportadas": config.API_FALLBACK_ORDER
728
  }
729
  })
730
 
731
  def get_blueprint(self):
732
- """Retorna o blueprint para registro no Flask"""
733
  return self.api
734
 
735
  # ============================================================================
736
- # 🎯 FUNÇÃO PARA USO DIRETO (ATUALIZADA)
737
  # ============================================================================
738
  def gerar_resposta_direta(
739
  mensagem: str,
@@ -742,32 +638,38 @@ def gerar_resposta_direta(
742
  tipo_conversa: str = "pv",
743
  tipo_mensagem: str = "texto",
744
  mensagem_citada: str = "",
745
- reply_metadata: Optional[Dict] = None,
746
- reply_info: Optional[Dict] = None
747
  ) -> str:
748
  """
749
- Função para uso direto (sem HTTP) - ATUALIZADA
750
- Útil para testes e integração direta com contexto completo
 
 
 
 
 
 
 
 
 
 
 
751
  """
752
  try:
753
  # Cria instância
754
  api = AkiraAPI()
755
 
756
- # Prepara análise básica com contexto
757
  analise = {
758
  "numero": numero,
759
  "usuario_privilegiado": api.db.is_usuario_privilegiado(numero),
760
  "emocao_primaria": "neutral",
761
  "modo_resposta": "normal_ironico",
762
  "tipo_mensagem": tipo_mensagem,
763
- "reply_metadata": reply_metadata,
764
- "reply_info": reply_info or reply_metadata
765
  }
766
 
767
- # Prepara reply_info para config.py
768
- reply_info_for_config = reply_metadata if reply_metadata else reply_info
769
-
770
- # Gera resposta com contexto completo
771
  resposta = api.llm_manager.gerar_resposta(
772
  mensagem=mensagem,
773
  historico=[],
@@ -775,49 +677,46 @@ def gerar_resposta_direta(
775
  analise=analise,
776
  usuario=usuario,
777
  tipo_conversa=tipo_conversa,
778
- reply_info=reply_info_for_config
779
  )
780
 
781
- logger.info(f"✅ Resposta direta gerada para {usuario}: {resposta[:80]}...")
782
  return resposta
783
 
784
  except Exception as e:
785
  logger.error(f"❌ Erro na resposta direta: {e}")
786
- return "Erro ao processar mensagem."
787
 
788
  # ============================================================================
789
- # 🎯 TESTE RÁPIDO - ATUALIZADO PARA NOVO CONTEXTO
790
  # ============================================================================
791
  if __name__ == "__main__":
792
  print("=" * 80)
793
- print("TESTANDO API.PY ATUALIZADA - COMPATIBILIDADE COM NOVO INDEX.JS")
794
  print("=" * 80)
795
 
796
- # Teste com payload do NOVO index.js (com mensagem_citada completa)
797
  test_payload = {
798
  "numero": "244978787009",
799
  "usuario": "Isaac Quarenta",
800
- "mensagem": "Você disse que gosta de futebol, né? O que acha do jogo de ontem?",
801
  "tipo_conversa": "pv",
802
  "tipo_mensagem": "texto",
803
- "mensagem_citada": "Sim, gosto do Petro de Luanda. É o melhor time de Angola, sem discussão. Jogo de qualidade europeia.",
804
  "reply_metadata": {
805
- "is_reply": True,
806
- "reply_to_bot": True,
807
- "quoted_author_name": "Akira",
808
- "quoted_type": "texto",
809
- "context_hint": "(Usuário está respondendo à MINHA mensagem anterior: \"Sim, gosto do Petro de Luanda. É o me...\")"
810
  }
811
  }
812
 
813
  try:
814
- # Testa função direta com contexto completo
815
  resposta = gerar_resposta_direta(
816
  mensagem=test_payload["mensagem"],
817
  usuario=test_payload["usuario"],
818
  numero=test_payload["numero"],
819
  tipo_conversa=test_payload["tipo_conversa"],
820
- reply_info=test_payload["reply_info"]
821
  )
822
 
823
  print(f"✅ Teste OK")
@@ -830,5 +729,5 @@ if __name__ == "__main__":
830
  traceback.print_exc()
831
 
832
  print("\n" + "=" * 80)
833
- print("API.PY - PRONTO PARA USO")
834
  print("=" * 80)
 
1
+ # modules/api.py — AKIRA V21 FINAL INTEGRADO (Dezembro 2025)
2
  """
3
+ ✅ TOTALMENTE INTEGRADO com config.py, database.py, contexto.py e treinamento.py atualizados
4
+ CORREÇÃO: Usa métodos corretos do Database
5
+ COMPATÍVEL com index.js e reply_metadata
6
+ Sistema multi-API com fallback
7
+ Cache de contexto otimizado
8
+ Treinamento automático integrado
 
9
  """
10
+
11
  import time
12
  import datetime
13
  import requests
 
19
  from flask import Blueprint, request, jsonify, make_response
20
  from loguru import logger
21
 
22
+ # Importa módulos locais - CORRETAMENTE
23
+ from .contexto import Contexto, criar_contexto
24
  from .database import Database
25
  from .treinamento import Treinamento
 
 
26
  import modules.config as config
27
 
28
  # ============================================================================
 
57
  return default
58
 
59
  # ============================================================================
60
+ # 🧠 GERENCIADOR MULTI-API (OTIMIZADO PARA CONFIG.PY)
61
  # ============================================================================
62
  class MultiAPIManager:
63
  def __init__(self):
 
66
  logger.info(f"✅ APIs disponíveis: {', '.join(self.apis_disponiveis)}")
67
 
68
  def _verificar_apis(self):
69
+ """Verifica quais APIs estão disponíveis"""
70
  apis = []
71
  if config.MISTRAL_API_KEY and len(config.MISTRAL_API_KEY) > 10:
72
  apis.append("mistral")
 
89
  reply_info: Optional[Dict] = None
90
  ) -> str:
91
  """
92
+ Constrói prompt usando config.py - CORRETAMENTE
 
93
  """
94
  try:
 
95
  logger.debug(f"📝 Construindo prompt para: {usuario}")
 
 
 
96
 
97
+ # Usa a função do config.py
98
  prompt = config.construir_prompt_api(
99
  mensagem=mensagem,
100
  historico=historico,
 
106
  )
107
 
108
  logger.debug(f"✅ Prompt construído: {len(prompt)} caracteres")
 
 
 
 
109
  return prompt
110
 
111
  except Exception as e:
112
  logger.error(f"❌ Erro ao construir prompt: {e}")
113
+ # Fallback simples
114
  return f"""AKIRA V21 — PERSONALIDADE REAL SEM FILTRO
115
  Você é Akira, angolana de 18 anos de Luanda.
116
 
 
 
117
  USUÁRIO: {usuario}
118
  MENSAGEM: {mensagem}
119
+ MENSAGEM CITADA: {mensagem_citada if mensagem_citada else 'Nenhuma'}
120
 
121
  RESPONDA COMO AKIRA (IRÔNICA, SEM FILTRO, 1-2 LINHAS):"""
122
 
123
  def _obter_parametros_api(self, analise: Dict[str, Any]) -> Dict[str, Any]:
124
+ """Obtém parâmetros usando config.py"""
125
  emocao = analise.get("emocao_primaria", "neutral")
126
  modo = analise.get("modo_resposta", "normal_ironico")
127
 
 
128
  return config.obter_parametros_api(emocao, modo)
129
 
130
  def gerar_resposta(
 
137
  tipo_conversa: str,
138
  reply_info: Optional[Dict] = None
139
  ) -> str:
140
+ """Gera resposta usando multi-API fallback"""
 
141
  logger.info(f"🎯 Gerando resposta para {usuario}")
 
 
 
 
 
142
 
143
+ # Construir prompt
144
  prompt = self._construir_prompt(
145
  mensagem=mensagem,
146
  historico=historico,
 
154
  # Obter parâmetros
155
  parametros = self._obter_parametros_api(analise)
156
 
157
+ # Tentar APIs em ordem
 
 
158
  for api_name in config.API_FALLBACK_ORDER:
159
  if api_name not in self.apis_disponiveis:
160
  continue
161
 
162
  try:
 
163
  resposta = self._chamar_api(api_name, prompt, parametros)
164
  if resposta:
165
  resposta_limpa = self._limpar_resposta(resposta)
166
+ logger.info(f"✅ API {api_name} respondeu: {resposta_limpa[:80]}...")
167
  return resposta_limpa
 
168
  except Exception as e:
169
+ logger.warning(f"❌ API {api_name} falhou: {e}")
170
  continue
171
 
172
+ # Fallback contextual
173
+ return self._gerar_fallback_contextual(mensagem, mensagem_citada, reply_info)
 
174
 
175
+ def _chamar_api(self, api_name: str, prompt: str, params: Dict[str, Any]) -> str:
176
+ """Chama API específica"""
177
+ try:
178
+ if api_name == "mistral":
179
+ return self._chamar_mistral(prompt, params)
180
+ elif api_name == "gemini":
181
+ return self._chamar_gemini(prompt, params)
182
+ elif api_name == "groq":
183
+ return self._chamar_groq(prompt, params)
184
+ elif api_name == "cohere":
185
+ return self._chamar_cohere(prompt, params)
186
+ except Exception as e:
187
+ logger.error(f"Erro ao chamar {api_name}: {e}")
188
+ return ""
189
 
190
  def _chamar_mistral(self, prompt: str, params: Dict[str, Any]) -> str:
191
+ """Chama Mistral API"""
192
  try:
193
  response = requests.post(
194
  "https://api.mistral.ai/v1/chat/completions",
 
207
  response.raise_for_status()
208
  return response.json()["choices"][0]["message"]["content"].strip()
209
  except Exception as e:
210
+ logger.error(f"Mistral falhou: {e}")
211
  return ""
212
 
213
  def _chamar_gemini(self, prompt: str, params: Dict[str, Any]) -> str:
214
+ """Chama Gemini API"""
215
  try:
216
  response = requests.post(
217
  f"https://generativelanguage.googleapis.com/v1beta/models/{config.GEMINI_MODEL}:generateContent?key={config.GEMINI_API_KEY}",
 
227
  response.raise_for_status()
228
  return response.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
229
  except Exception as e:
230
+ logger.error(f"Gemini falhou: {e}")
231
  return ""
232
 
233
  def _chamar_groq(self, prompt: str, params: Dict[str, Any]) -> str:
234
+ """Chama Groq API"""
235
  try:
236
  response = requests.post(
237
  "https://api.groq.com/openai/v1/chat/completions",
 
247
  response.raise_for_status()
248
  return response.json()["choices"][0]["message"]["content"].strip()
249
  except Exception as e:
250
+ logger.error(f"Groq falhou: {e}")
251
  return ""
252
 
253
  def _chamar_cohere(self, prompt: str, params: Dict[str, Any]) -> str:
254
+ """Chama Cohere API"""
255
  try:
256
  response = requests.post(
257
  "https://api.cohere.ai/v1/generate",
 
266
  response.raise_for_status()
267
  return response.json()["generations"][0]["text"].strip()
268
  except Exception as e:
269
+ logger.error(f"Cohere falhou: {e}")
270
  return ""
271
 
272
  def _limpar_resposta(self, texto: str) -> str:
273
+ """Limpa a resposta"""
274
  if not texto:
275
  return "…"
276
 
277
  # Remove markdown
278
  texto = re.sub(r'[\*`_]+', '', texto)
279
 
280
+ # Remove aspas
281
  texto = texto.strip('"\'')
282
 
283
+ # Remove prefixos
284
  texto = re.sub(r'^(Akira|AKIRA)[:\s\-]+', '', texto, flags=re.IGNORECASE)
285
 
286
+ # Remove espaços extras
 
 
 
287
  texto = re.sub(r'\s+', ' ', texto)
288
 
289
+ # Limita tamanho
290
  if len(texto) > 400:
 
291
  last_period = texto[:397].rfind('.')
292
  if last_period > 300:
293
  texto = texto[:last_period + 1]
 
296
 
297
  return texto.strip()
298
 
299
+ def _gerar_fallback_contextual(self, mensagem: str, mensagem_citada: str, reply_info: Optional[Dict]) -> str:
300
+ """Gera fallback contextual"""
301
+ fallbacks = [
302
  "Puto, nem ligo.",
303
  "Foda-se.",
304
  "Ya, tá bom.",
 
311
  "Não."
312
  ]
313
 
314
+ # Adiciona contextuais se há mensagem citada
315
  if mensagem_citada:
316
+ fallbacks.extend([
317
  f"Já disse isso.",
318
  f"Repito: {mensagem_citada[:50]}...",
319
  f"Ya, como disse.",
320
  f"Exactamente isso."
321
+ ])
 
322
 
323
  # Se é reply ao bot
324
  if reply_info and reply_info.get('reply_to_bot'):
325
+ fallbacks.extend([
326
  "Já disse isso, caralho.",
327
  "Repito o que disse.",
328
  "Ya, como eu disse.",
329
  "Exactamente o que falei."
330
+ ])
 
331
 
332
+ return random.choice(fallbacks)
333
 
334
  # ============================================================================
335
+ # 🎯 CLASSE PRINCIPAL AKIRA API (CORRIGIDA E INTEGRADA)
336
  # ============================================================================
337
  class AkiraAPI:
338
  def __init__(self):
339
+ """Inicializa API totalmente integrada"""
340
  self.api = Blueprint("akira_api", __name__)
341
  self.contexto_cache = SimpleTTLCache(ttl_seconds=300)
342
+
343
+ # Inicializa Database com caminho CORRETO
344
  self.db = Database(config.DB_PATH)
345
  self.llm_manager = MultiAPIManager()
346
+
347
+ # Configura treinamento se habilitado
 
 
 
 
 
348
  if config.START_PERIODIC_TRAINER:
349
  try:
350
+ self.treinador = Treinamento(
351
  self.db,
352
  interval_hours=config.TRAINING_INTERVAL_HOURS
353
  )
354
+ self.treinador.start_periodic_training()
355
  logger.info("✅ Treinamento periódico iniciado")
356
  except Exception as e:
357
  logger.error(f"❌ Erro no treinador: {e}")
358
+ self.treinador = None
359
+
360
+ self._setup_routes()
361
+ logger.success("🚀 AKIRA V21 FINAL inicializada")
362
 
363
  def _get_user_context(self, numero: str, tipo_conversa: str, grupo_id: str = "") -> Contexto:
364
+ """Obtém contexto isolado"""
 
365
  if tipo_conversa == "grupo" and grupo_id:
366
  key = f"grupo_{grupo_id}"
367
  else:
368
  key = f"pv_{numero}"
369
 
370
+ # Cache
371
  if key in self.contexto_cache:
372
  return self.contexto_cache[key]
373
 
374
+ # Cria novo contexto usando a função correta
375
+ ctx = criar_contexto(self.db, key, tipo_conversa)
376
  self.contexto_cache[key] = ctx
377
  return ctx
378
 
379
  def _processar_reset(self, numero: str, usuario: str, confirmacao: bool = False) -> Dict[str, Any]:
380
  """Processa comando /reset"""
381
+ # Verifica permissão usando método CORRETO
382
  if not self.db.pode_usar_reset(numero):
383
  return {
384
  "error": "COMANDO RESTRITO",
385
+ "resposta": "Só o boss pode usar /reset, puto."
386
  }
387
 
388
  if not confirmacao:
 
401
  }
402
 
403
  def _extrair_payload_indexjs(self, data: Dict[str, Any]) -> Dict[str, Any]:
404
+ """Extrai dados do payload do index.js"""
405
  payload = {
406
  'numero': str(data.get('numero', '')).strip(),
407
  'usuario': data.get('usuario', 'Anônimo').strip(),
 
411
  'grupo_id': data.get('grupo_id', ''),
412
  'grupo_nome': data.get('grupo_nome', ''),
413
  'is_reset': False,
414
+ 'reply_metadata': data.get('reply_metadata', {}),
415
+ 'mensagem_citada': data.get('mensagem_citada', '')
 
416
  }
417
 
418
+ # Log básico
419
+ logger.debug(f"📦 Payload recebido de {payload['usuario']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
421
  # Detecta /reset
422
  if payload['mensagem'].strip().lower() == '/reset':
 
424
 
425
  return payload
426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  def _setup_routes(self):
428
+ """Configura rotas da API"""
429
 
430
  @self.api.before_request
431
  def handle_options():
432
+ """Lida com CORS preflight"""
433
  if request.method == 'OPTIONS':
434
  resp = make_response()
435
  resp.headers['Access-Control-Allow-Origin'] = '*'
 
439
 
440
  @self.api.after_request
441
  def add_cors(resp):
442
+ """Adiciona headers CORS"""
443
  resp.headers['Access-Control-Allow-Origin'] = '*'
444
  return resp
445
 
446
  @self.api.route('/akira', methods=['POST'])
447
  def akira_endpoint():
448
+ """Endpoint principal"""
449
  try:
450
+ # Recebe payload
451
  data = request.get_json() or {}
452
  payload = self._extrair_payload_indexjs(data)
453
 
 
454
  logger.info(
455
+ f"📨 [{payload['usuario']}] ({payload['numero']}): "
456
+ f"{payload['mensagem'][:80]}..."
457
  )
 
 
458
 
459
+ # Validação
460
  if not payload['mensagem']:
461
  return jsonify({'error': 'Mensagem obrigatória'}), 400
462
 
 
471
  return jsonify(resultado), 403
472
  return jsonify(resultado)
473
 
474
+ # Obtém contexto
 
 
 
 
 
475
  contexto = self._get_user_context(
476
  numero=payload['numero'],
477
  tipo_conversa=payload['tipo_conversa'],
478
  grupo_id=payload['grupo_id']
479
  )
480
 
481
+ # Atualiza informações do usuário
482
+ contexto.atualizar_informacoes_usuario(
483
+ nome=payload['usuario'],
484
+ numero=payload['numero'],
485
+ grupo_id=payload['grupo_id'],
486
+ grupo_nome=payload['grupo_nome'],
487
+ tipo_conversa=payload['tipo_conversa']
488
+ )
489
+
490
  # Obtém histórico
491
  historico = contexto.obter_historico_para_llm()
492
 
493
+ # Verifica usuário privilegiado
494
  usuario_privilegiado = self.db.is_usuario_privilegiado(payload['numero'])
495
 
496
+ # Analisa a mensagem
497
  analise = contexto.analisar_intencao_e_normalizar(
498
+ mensagem=payload['mensagem'],
499
+ historico=historico,
500
+ mensagem_citada=payload['mensagem_citada'],
501
+ reply_metadata=payload['reply_metadata']
502
  )
503
 
504
+ # Adiciona informações extras
 
 
 
 
505
  analise.update({
506
  'usuario_privilegiado': usuario_privilegiado,
507
  'numero': payload['numero'],
508
  'tipo_mensagem': payload['tipo_mensagem'],
509
+ 'reply_metadata': payload['reply_metadata']
 
510
  })
511
 
512
+ # Prepara reply_info para config.py
513
+ reply_info_for_config = payload['reply_metadata']
 
 
 
 
 
514
 
515
+ # Gera resposta
516
  resposta = self.llm_manager.gerar_resposta(
517
  mensagem=payload['mensagem'],
518
+ historico=historico,
519
+ mensagem_citada=payload['mensagem_citada'],
520
  analise=analise,
521
  usuario=payload['usuario'],
522
  tipo_conversa=payload['tipo_conversa'],
523
+ reply_info=reply_info_for_config
524
  )
525
 
526
+ # Determina se é reply
527
+ is_reply = bool(payload['mensagem_citada']) or (
528
+ payload['reply_metadata'] and payload['reply_metadata'].get('is_reply', False)
529
+ )
530
  reply_to_bot = False
531
 
532
  if payload['reply_metadata']:
533
  reply_to_bot = payload['reply_metadata'].get('reply_to_bot', False)
 
 
 
 
534
 
535
+ # Atualiza contexto
536
  contexto.atualizar_contexto(
537
  mensagem=payload['mensagem'],
538
  resposta=resposta,
539
  numero=payload['numero'],
540
  is_reply=is_reply,
541
+ mensagem_original=payload['mensagem_citada'],
542
  reply_to_bot=reply_to_bot
543
  )
544
 
545
+ # Registra interação para treinamento
546
+ if hasattr(self, 'treinador') and self.treinador:
547
+ try:
548
+ self.treinador.registrar_interacao(
549
+ usuario=payload['usuario'],
550
+ mensagem=payload['mensagem'],
551
+ resposta=resposta,
552
+ numero=payload['numero'],
553
+ is_reply=is_reply,
554
+ mensagem_original=payload['mensagem_citada'],
555
+ contexto=analise,
556
+ tipo_conversa=payload['tipo_conversa'],
557
+ tipo_mensagem=payload['tipo_mensagem'],
558
+ reply_to_bot=reply_to_bot,
559
+ reply_metadata=payload['reply_metadata']
560
+ )
561
+ except Exception as e:
562
+ logger.warning(f"⚠️ Erro ao registrar interação: {e}")
563
 
564
+ logger.info(f"📤 Resposta: {resposta[:80]}...")
 
565
 
566
  return jsonify({"resposta": resposta})
567
 
568
  except Exception as e:
569
+ logger.error(f"❌ Erro em /akira: {e}")
570
  import traceback
571
+ traceback.print_exc()
572
  return jsonify({
573
  "error": "Erro interno",
574
+ "resposta": "Erro interno, puto. Tenta de novo."
 
575
  }), 500
576
 
577
  @self.api.route('/health', methods=['GET'])
578
  def health():
579
+ """Health check"""
580
  agora = datetime.datetime.now() + datetime.timedelta(hours=config.TIMEZONE_OFFSET_HOURS)
581
  return jsonify({
582
+ "status": "✅ AKIRA V21 ONLINE",
583
  "hora_luanda": agora.strftime("%H:%M"),
584
+ "versao": config.VERSAO,
585
  "apis_disponiveis": self.llm_manager.apis_disponiveis,
586
+ "cache_size": len(self.contexto_cache._store)
 
 
 
 
 
 
 
 
 
587
  })
588
 
589
  @self.api.route('/reset', methods=['POST'])
590
  def reset_endpoint():
591
+ """Endpoint de reset dedicado"""
592
  try:
593
  data = request.get_json() or {}
594
  numero = str(data.get('numero', '')).strip()
 
609
 
610
  @self.api.route('/info', methods=['GET'])
611
  def info():
612
+ """Informações da API"""
613
  return jsonify({
614
+ "nome": "Akira V21",
615
+ "descricao": "IA com personalidade brutal e irônica",
616
  "desenvolvedor": "Isaac Quarenta",
617
  "empresa": "Softedge",
618
  "versao": config.VERSAO,
619
  "endpoints": ["/akira", "/health", "/reset", "/info"],
 
 
 
 
 
 
 
620
  "configuracoes": {
621
  "temperatura_padrao": config.TEMPERATURE,
622
  "max_tokens": config.MAX_TOKENS,
623
+ "timezone_offset": config.TIMEZONE_OFFSET_HOURS
 
624
  }
625
  })
626
 
627
  def get_blueprint(self):
628
+ """Retorna o blueprint para Flask"""
629
  return self.api
630
 
631
  # ============================================================================
632
+ # 🎯 FUNÇÃO PARA USO DIRETO
633
  # ============================================================================
634
  def gerar_resposta_direta(
635
  mensagem: str,
 
638
  tipo_conversa: str = "pv",
639
  tipo_mensagem: str = "texto",
640
  mensagem_citada: str = "",
641
+ reply_metadata: Optional[Dict] = None
 
642
  ) -> str:
643
  """
644
+ Função para uso direto (sem HTTP)
645
+
646
+ Args:
647
+ mensagem: Mensagem do usuário
648
+ usuario: Nome do usuário
649
+ numero: Número do usuário
650
+ tipo_conversa: Tipo da conversa
651
+ tipo_mensagem: Tipo da mensagem
652
+ mensagem_citada: Mensagem citada
653
+ reply_metadata: Metadata do reply
654
+
655
+ Returns:
656
+ Resposta gerada
657
  """
658
  try:
659
  # Cria instância
660
  api = AkiraAPI()
661
 
662
+ # Prepara análise
663
  analise = {
664
  "numero": numero,
665
  "usuario_privilegiado": api.db.is_usuario_privilegiado(numero),
666
  "emocao_primaria": "neutral",
667
  "modo_resposta": "normal_ironico",
668
  "tipo_mensagem": tipo_mensagem,
669
+ "reply_metadata": reply_metadata
 
670
  }
671
 
672
+ # Gera resposta
 
 
 
673
  resposta = api.llm_manager.gerar_resposta(
674
  mensagem=mensagem,
675
  historico=[],
 
677
  analise=analise,
678
  usuario=usuario,
679
  tipo_conversa=tipo_conversa,
680
+ reply_info=reply_metadata
681
  )
682
 
683
+ logger.info(f"✅ Resposta direta para {usuario}: {resposta[:80]}...")
684
  return resposta
685
 
686
  except Exception as e:
687
  logger.error(f"❌ Erro na resposta direta: {e}")
688
+ return "Erro ao processar."
689
 
690
  # ============================================================================
691
+ # 🎯 TESTE
692
  # ============================================================================
693
  if __name__ == "__main__":
694
  print("=" * 80)
695
+ print("TESTANDO API.PY - FINAL INTEGRADO")
696
  print("=" * 80)
697
 
698
+ # Teste simples
699
  test_payload = {
700
  "numero": "244978787009",
701
  "usuario": "Isaac Quarenta",
702
+ "mensagem": "Oi Akira, tudo bem?",
703
  "tipo_conversa": "pv",
704
  "tipo_mensagem": "texto",
705
+ "mensagem_citada": "",
706
  "reply_metadata": {
707
+ "is_reply": False,
708
+ "reply_to_bot": False
 
 
 
709
  }
710
  }
711
 
712
  try:
713
+ # Usa função direta
714
  resposta = gerar_resposta_direta(
715
  mensagem=test_payload["mensagem"],
716
  usuario=test_payload["usuario"],
717
  numero=test_payload["numero"],
718
  tipo_conversa=test_payload["tipo_conversa"],
719
+ reply_metadata=test_payload["reply_metadata"]
720
  )
721
 
722
  print(f"✅ Teste OK")
 
729
  traceback.print_exc()
730
 
731
  print("\n" + "=" * 80)
732
+ print("API.PY - INTEGRADO E PRONTO PARA USO")
733
  print("=" * 80)