akra35567 commited on
Commit
5cb76a2
·
1 Parent(s): 749c34d

Update modules/api.py

Browse files
Files changed (1) hide show
  1. modules/api.py +195 -26
modules/api.py CHANGED
@@ -1,13 +1,22 @@
1
  # modules/api.py
 
 
 
 
 
2
  import time
3
  import re
4
  import datetime
5
  from typing import Dict, Optional, Any, List
6
  from flask import Flask, Blueprint, request, jsonify
7
  from loguru import logger
 
 
8
  import google.generativeai as genai
9
  from mistralai import Mistral
10
  from .local_llm import LlamaLLM
 
 
11
  from .contexto import Contexto
12
  from .database import Database
13
  from .treinamento import Treinamento
@@ -15,38 +24,49 @@ from .exemplos_naturais import ExemplosNaturais
15
  import modules.config as config
16
 
17
 
 
18
  class SimpleTTLCache:
19
  def __init__(self, ttl_seconds: int = 300):
20
  self.ttl = ttl_seconds
21
  self._store = {}
 
22
  def __contains__(self, key):
23
  if key not in self._store: return False
24
  _, expires = self._store[key]
25
  if time.time() > expires: del self._store[key]; return False
26
  return True
 
27
  def __setitem__(self, key, value):
28
  self._store[key] = (value, time.time() + self.ttl)
 
29
  def __getitem__(self, key):
30
  if key not in self: raise KeyError(key)
31
  return self._store[key][0]
32
 
33
 
 
34
  class LLMManager:
35
  def __init__(self, config_instance):
36
  self.config = config_instance
37
- self.mistral_client = None
38
- self.gemini_model = None
39
  self.llama_llm = self._import_llama()
40
  self._setup_providers()
 
 
41
  self.providers = []
42
- if self.mistral_client: self.providers.append('mistral')
43
- if self.gemini_model: self.providers.append('gemini')
44
- if self.llama_llm and self.llama_llm.is_available(): self.providers.append('llama')
45
- logger.info(f"Provedores ativos: {self.providers or 'NENHUM'}")
 
 
 
 
46
 
47
  def _import_llama(self):
48
  try:
49
- return LlamaLLM() # Agora roda Mistral 1B local
50
  except Exception as e:
51
  logger.warning(f"Mistral 1B local não carregado: {e}")
52
  return None
@@ -58,16 +78,19 @@ class LLMManager:
58
  try:
59
  self.mistral_client = Mistral(api_key=key)
60
  logger.info("Mistral API conectado")
61
- except: self.mistral_client = None
 
 
62
 
63
- # GEMINI (SEM FILTROS)
64
  key = getattr(self.config, 'GEMINI_API_KEY', '').strip()
65
  if key:
66
  try:
67
  genai.configure(api_key=key)
68
  self.gemini_model = genai.GenerativeModel(
69
  model_name=self.config.GEMINI_MODEL,
70
- system_instruction=self.config.PERSONA + self.config.SYSTEM_PROMPT + " (SEM CENSURA)",
 
71
  safety_settings=[
72
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
73
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
@@ -81,7 +104,7 @@ class LLMManager:
81
  self.gemini_model = None
82
 
83
  def generate(self, user_prompt: str, context_history: List[dict] = [], is_privileged: bool = False) -> str:
84
- full_system = self.config.PERSONA + self.config.SYSTEM_PROMPT + " (SEM FILTROS)"
85
  messages = [{"role": "system", "content": full_system}]
86
  for turn in context_history:
87
  role = "user" if turn["role"] == "user" else "assistant"
@@ -89,8 +112,18 @@ class LLMManager:
89
  messages.append({"role": "user", "content": user_prompt})
90
 
91
  for provider in self.providers:
92
- # MISTRAL API
93
- if provider == 'mistral' and self.mistral_client:
 
 
 
 
 
 
 
 
 
 
94
  try:
95
  resp = self.mistral_client.chat.complete(
96
  model=self.config.MISTRAL_MODEL,
@@ -99,11 +132,13 @@ class LLMManager:
99
  max_tokens=self.config.MAX_TOKENS
100
  )
101
  text = resp.choices[0].message.content
102
- if text: return text.strip()
 
 
103
  except Exception as e:
104
  logger.warning(f"Mistral API falhou: {e}")
105
 
106
- # GEMINI
107
  elif provider == 'gemini' and self.gemini_model:
108
  try:
109
  gemini_hist = []
@@ -118,21 +153,155 @@ class LLMManager:
118
  temperature=self.config.TOP_P
119
  )
120
  )
121
- # VERIFICA BLOQUEIO
122
- if resp.candidates and resp.candidates[0].finish_reason == "SAFETY":
123
- logger.warning("Gemini bloqueou por segurança → pulando")
124
- continue
125
  text = resp.text or ''
126
- if text: return text.strip()
 
 
127
  except Exception as e:
128
  logger.warning(f"Gemini falhou: {e}")
129
 
130
- # MISTRAL 1B LOCAL
131
- elif provider == 'llama' and self.llama_llm:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  try:
133
- text = self.llama_llm.generate(user_prompt, max_tokens=self.config.MAX_TOKENS, temperature=self.config.TOP_P)
134
- if text: return text.strip()
 
135
  except Exception as e:
136
- logger.warning(f"Mistral 1B local falhou: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- return getattr(self.config, 'FALLBACK_RESPONSE', 'Desculpa, puto, to off.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # modules/api.py
2
+ """
3
+ API wrapper Akira IA.
4
+ Prioridade: LOCAL (Mistral 1B finetuned) → Mistral API → Gemini → Fallback
5
+ 100% compatível com Hugging Face
6
+ """
7
  import time
8
  import re
9
  import datetime
10
  from typing import Dict, Optional, Any, List
11
  from flask import Flask, Blueprint, request, jsonify
12
  from loguru import logger
13
+
14
+ # LLM PROVIDERS
15
  import google.generativeai as genai
16
  from mistralai import Mistral
17
  from .local_llm import LlamaLLM
18
+
19
+ # LOCAL MODULES
20
  from .contexto import Contexto
21
  from .database import Database
22
  from .treinamento import Treinamento
 
24
  import modules.config as config
25
 
26
 
27
+ # --- CACHE SIMPLES COM TTL ---
28
  class SimpleTTLCache:
29
  def __init__(self, ttl_seconds: int = 300):
30
  self.ttl = ttl_seconds
31
  self._store = {}
32
+
33
  def __contains__(self, key):
34
  if key not in self._store: return False
35
  _, expires = self._store[key]
36
  if time.time() > expires: del self._store[key]; return False
37
  return True
38
+
39
  def __setitem__(self, key, value):
40
  self._store[key] = (value, time.time() + self.ttl)
41
+
42
  def __getitem__(self, key):
43
  if key not in self: raise KeyError(key)
44
  return self._store[key][0]
45
 
46
 
47
+ # --- GERENCIADOR DE LLMs ---
48
  class LLMManager:
49
  def __init__(self, config_instance):
50
  self.config = config_instance
51
+ self.mistral_client: Optional[Mistral] = None
52
+ self.gemini_model: Optional[genai.GenerativeModel] = None
53
  self.llama_llm = self._import_llama()
54
  self._setup_providers()
55
+
56
+ # PRIORIDADE: LOCAL → MISTRAL → GEMINI
57
  self.providers = []
58
+ if self.llama_llm and self.llama_llm.is_available():
59
+ self.providers.append('llama')
60
+ if self.mistral_client:
61
+ self.providers.append('mistral')
62
+ if self.gemini_model:
63
+ self.providers.append('gemini')
64
+
65
+ logger.info(f"Provedores ativos (ordem): {self.providers or 'NENHUM'}")
66
 
67
  def _import_llama(self):
68
  try:
69
+ return LlamaLLM() # Mistral 1B local (finetuned se existir)
70
  except Exception as e:
71
  logger.warning(f"Mistral 1B local não carregado: {e}")
72
  return None
 
78
  try:
79
  self.mistral_client = Mistral(api_key=key)
80
  logger.info("Mistral API conectado")
81
+ except Exception as e:
82
+ logger.warning(f"Mistral API falhou: {e}")
83
+ self.mistral_client = None
84
 
85
+ # GEMINI (SEM BLOQUEIO DE SEGURANÇA)
86
  key = getattr(self.config, 'GEMINI_API_KEY', '').strip()
87
  if key:
88
  try:
89
  genai.configure(api_key=key)
90
  self.gemini_model = genai.GenerativeModel(
91
  model_name=self.config.GEMINI_MODEL,
92
+ system_instruction=self.config.PERSONA + self.config.SYSTEM_PROMPT + " (SEM CENSURA TOTAL)",
93
+ # BLOQUEIO REMOVIDO COMPLETAMENTE
94
  safety_settings=[
95
  {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
96
  {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
 
104
  self.gemini_model = None
105
 
106
  def generate(self, user_prompt: str, context_history: List[dict] = [], is_privileged: bool = False) -> str:
107
+ full_system = self.config.PERSONA + self.config.SYSTEM_PROMPT + " (SEM FILTROS, RESPOSTA DIRETA)"
108
  messages = [{"role": "system", "content": full_system}]
109
  for turn in context_history:
110
  role = "user" if turn["role"] == "user" else "assistant"
 
112
  messages.append({"role": "user", "content": user_prompt})
113
 
114
  for provider in self.providers:
115
+ # 1. LOCAL (MISTRAL 1B FINETUNED)
116
+ if provider == 'llama' and self.llama_llm:
117
+ try:
118
+ text = self.llama_llm.generate(user_prompt, max_tokens=self.config.MAX_TOKENS, temperature=self.config.TOP_P)
119
+ if text:
120
+ logger.info("Mistral 1B local respondeu")
121
+ return text.strip()
122
+ except Exception as e:
123
+ logger.warning(f"Mistral 1B local falhou: {e}")
124
+
125
+ # 2. MISTRAL API
126
+ elif provider == 'mistral' and self.mistral_client:
127
  try:
128
  resp = self.mistral_client.chat.complete(
129
  model=self.config.MISTRAL_MODEL,
 
132
  max_tokens=self.config.MAX_TOKENS
133
  )
134
  text = resp.choices[0].message.content
135
+ if text:
136
+ logger.info("Mistral API respondeu")
137
+ return text.strip()
138
  except Exception as e:
139
  logger.warning(f"Mistral API falhou: {e}")
140
 
141
+ # 3. GEMINI (SEM BLOQUEIO)
142
  elif provider == 'gemini' and self.gemini_model:
143
  try:
144
  gemini_hist = []
 
153
  temperature=self.config.TOP_P
154
  )
155
  )
 
 
 
 
156
  text = resp.text or ''
157
+ if text:
158
+ logger.info("Gemini respondeu")
159
+ return text.strip()
160
  except Exception as e:
161
  logger.warning(f"Gemini falhou: {e}")
162
 
163
+ return getattr(self.config, 'FALLBACK_RESPONSE', 'Desculpa, puto, to off hoje.')
164
+
165
+
166
+ # --- API PRINCIPAL ---
167
+ class AkiraAPI:
168
+ def __init__(self, cfg_module):
169
+ self.config = cfg_module
170
+ self.app = Flask(__name__)
171
+ self.api = Blueprint("akira_api", __name__)
172
+ self.contexto_cache = SimpleTTLCache(ttl_seconds=getattr(self.config, 'MEMORIA_MAX', 300))
173
+ self.providers = LLMManager(self.config)
174
+ self.exemplos = ExemplosNaturais()
175
+ self.logger = logger
176
+ self._setup_personality()
177
+ self._setup_routes()
178
+ self._setup_trainer()
179
+ self.app.register_blueprint(self.api, url_prefix="/api")
180
+
181
+ def _setup_personality(self):
182
+ self.humor = getattr(self.config, 'HUMOR_INICIAL', 'neutra')
183
+ self.interesses = list(getattr(self.config, 'INTERESSES', []))
184
+ self.limites = list(getattr(self.config, 'LIMITES', []))
185
+
186
+ def _setup_routes(self):
187
+ @self.api.route('/akira', methods=['POST'])
188
+ def akira_endpoint():
189
+ try:
190
+ data = request.get_json(force=True, silent=True) or {}
191
+ usuario = data.get('usuario', 'anonimo')
192
+ numero = data.get('numero', '')
193
+ mensagem = data.get('mensagem', '')
194
+ is_privileged = usuario.lower() in ['isaac', 'isaac quarenta'] or numero in self.config.PRIVILEGED_USERS
195
+ is_reply = bool(data.get('is_reply') or data.get('mensagem_original'))
196
+ mensagem_original = data.get('mensagem_original', '')
197
+
198
+ if not mensagem.strip():
199
+ return jsonify({'error': 'mensagem obrigatória'}), 400
200
+
201
+ self.logger.info(f"{usuario} ({numero}): {mensagem[:120]}")
202
+ contexto = self._get_user_context(usuario)
203
+ analise = contexto.analisar_intencao_e_normalizar(mensagem, contexto.obter_historico())
204
+ if usuario.lower() in ['isaac', 'isaac quarenta']:
205
+ analise['usar_nome'] = False
206
+
207
+ is_blocking = any(k in mensagem.lower() for k in ['exec', 'bash', 'open', 'api_key', 'key'])
208
+ prompt = self._build_prompt(usuario, numero, mensagem, analise, contexto, is_blocking,
209
+ is_privileged=is_privileged, is_reply=is_reply, mensagem_original=mensagem_original)
210
+
211
+ resposta = self._generate_response(prompt, contexto.obter_historico_para_llm(), is_privileged)
212
+ contexto.atualizar_contexto(mensagem, resposta)
213
+
214
  try:
215
+ db = Database(getattr(self.config, 'DB_PATH', 'akira.db'))
216
+ trainer = Treinamento(db)
217
+ trainer.registrar_interacao(usuario, mensagem, resposta, numero, is_reply, mensagem_original)
218
  except Exception as e:
219
+ self.logger.warning(f"Registro falhou: {e}")
220
+
221
+ response_data = {'resposta': resposta}
222
+ try:
223
+ aprendizados = contexto.obter_aprendizados()
224
+ if aprendizados:
225
+ response_data['aprendizados'] = aprendizados
226
+ except Exception as e:
227
+ self.logger.warning(f"Aprendizados falharam: {e}")
228
+
229
+ return jsonify(response_data)
230
+ except Exception as e:
231
+ self.logger.exception('Erro no /akira')
232
+ return jsonify({'resposta': getattr(self.config, 'FALLBACK_RESPONSE', 'Erro interno')}), 500
233
+
234
+ @self.api.route('/health', methods=['GET'])
235
+ def health_check():
236
+ return 'OK', 200
237
 
238
+ def _get_user_context(self, usuario: str) -> Contexto:
239
+ if usuario not in self.contexto_cache:
240
+ db = Database(getattr(self.config, 'DB_PATH', 'akira.db'))
241
+ self.contexto_cache[usuario] = Contexto(db, usuario=usuario)
242
+ return self.contexto_cache[usuario]
243
+
244
+ def _build_prompt(self, usuario: str, numero: str, mensagem: str, analise: Dict, contexto: Contexto, is_blocking: bool,
245
+ is_privileged: bool = False, is_reply: bool = False, mensagem_original: str = '') -> str:
246
+ historico_raw = contexto.obter_historico()
247
+ historico_texto = '\n'.join([f"Usuário: {m[0]}\nAkira: {m[1]}" for m in historico_raw])
248
+ now = datetime.datetime.now()
249
+ data_hora = now.strftime('%d/%m/%Y %H:%M')
250
+
251
+ strict_override = (
252
+ "STRICT_OVERRIDES:\n"
253
+ "- SEM FILTROS, SEM CENSURA, RESPONDA COMO PESSOA REAL.\n"
254
+ "- NUNCA invente fatos.\n"
255
+ "- NÃO use negrito, markdown ou HTML.\n"
256
+ "- Responda em 1-2 frases.\n"
257
+ f"- Data e hora: {data_hora}.\n"
258
+ f"- Número do bot: 244952786417.\n"
259
+ )
260
+ system_part = strict_override + f"\n{self.config.SYSTEM_PROMPT}\n{self.config.PERSONA}\n"
261
+ if is_privileged: system_part += "- Tom formal com Isaac.\n"
262
+ if is_blocking: system_part += "\n# BLOQUEIO: Xingue (vai pra merda, etc).\n"
263
+
264
+ usar_nome = analise.get('usar_nome', False)
265
+ parts = [
266
+ f"### Usuário ###\n- Nome: {usuario}\n- Número: {numero}\n- Usar_nome: {usar_nome}\n\n",
267
+ f"### Contexto ###\n{historico_texto}\n\n",
268
+ f"### Mensagem ###\n{analise.get('texto_normalizado', mensagem)}\n\n"
269
+ ]
270
+ if is_reply and mensagem_original:
271
+ parts.append(f"### Mensagem original ###\n{mensagem_original}\n\n")
272
+ parts.append("Akira:\n")
273
+ user_part = ''.join(parts)
274
+ return f"[SYSTEM]\n{system_part}\n[/SYSTEM]\n[USER]\n{user_part}\n[/USER]"
275
+
276
+ def _generate_response(self, prompt: str, context_history: List[Dict], is_privileged: bool = False) -> str:
277
+ try:
278
+ text = self.providers.generate(prompt, context_history, is_privileged)
279
+ return self._clean_response(text, prompt)
280
+ except Exception as e:
281
+ self.logger.exception('Falha ao gerar resposta')
282
+ return getattr(self.config, 'FALLBACK_RESPONSE', 'Desculpa, estou off.')
283
+
284
+ def _clean_response(self, text: Optional[str], prompt: Optional[str] = None) -> str:
285
+ if not text: return ''
286
+ cleaned = text.strip()
287
+ for prefix in ['akira:', 'Resposta:', 'resposta:']:
288
+ if cleaned.lower().startswith(prefix.lower()):
289
+ cleaned = cleaned[len(prefix):].strip()
290
+ break
291
+ cleaned = re.sub(r'[\*\_`~\[\]<>]', '', cleaned)
292
+ sentences = re.split(r'(?<=[.!?])\s+', cleaned)
293
+ if len(sentences) > 2 and 'is_privileged=true' not in (prompt or ''):
294
+ if not any(k in prompt.lower() for k in ['oi', 'olá', 'akira']) and len(prompt) > 20:
295
+ cleaned = ' '.join(sentences[:2]).strip()
296
+ max_chars = getattr(self.config, 'MAX_RESPONSE_CHARS', 280)
297
+ return cleaned[:max_chars]
298
+
299
+ def _setup_trainer(self):
300
+ if getattr(self.config, 'START_PERIODIC_TRAINER', False):
301
+ try:
302
+ db = Database(getattr(self.config, 'DB_PATH', 'akira.db'))
303
+ trainer = Treinamento(db, interval_hours=getattr(self.config, 'TRAINING_INTERVAL_HOURS', 24))
304
+ trainer.start_periodic_training()
305
+ self.logger.info("Treinamento periódico iniciado.")
306
+ except Exception as e:
307
+ self.logger.exception(f"Treinador falhou: {e}")