akra35567 commited on
Commit
786df8a
·
1 Parent(s): 97ad037

Update modules/contexto.py

Browse files
Files changed (1) hide show
  1. modules/contexto.py +86 -307
modules/contexto.py CHANGED
@@ -1,315 +1,94 @@
1
  """
2
- API wrapper for Akira service - VERSÃO FINAL INDESTRUTÍVEL
3
- - Proteção TOTAL contra JSON malformado
4
- - data SEMPRE dict → NUNCA MAIS 'str' object has no attribute 'get'
5
- - Gírias REMOVIDAS do prompt/retorno
6
- - Simples, rápido, inquebrável
 
 
7
  """
8
- from typing import Any, Dict, Optional
9
- import time
10
- import re
11
- import datetime
12
- import random
13
  import json
14
- from flask import Flask, Blueprint, request, jsonify
15
  from loguru import logger
16
  import modules.config as config
17
- from .contexto import Contexto
18
- from .database import Database
19
- from .treinamento import Treinamento
20
- from .web_search import WebSearch
21
- from .local_llm import LlamaLLM
22
-
23
- # Verifica disponibilidade
24
- try:
25
- from mistralai import Mistral
26
- mistral_available = True
27
- except ImportError:
28
- mistral_available = False
29
- logger.warning("mistralai não instalado")
30
-
31
- try:
32
- import google.generativeai as genai
33
- gemini_available = True
34
- except ImportError:
35
- gemini_available = False
36
- logger.warning("google-generativeai não instalado")
37
-
38
-
39
- class LLMManager:
40
- def __init__(self, config):
41
- self.config = config
42
- self.llama = LlamaLLM()
43
- self.mistral_client = None
44
- self.gemini_model = None
45
- self._setup_providers()
46
-
47
- def _setup_providers(self):
48
- if mistral_available and self.config.MISTRAL_API_KEY:
49
- try:
50
- self.mistral_client = Mistral(api_key=self.config.MISTRAL_API_KEY)
51
- logger.info("Mistral API inicializado (principal)")
52
- except Exception as e:
53
- logger.warning(f"Mistral falhou: {e}")
54
- self.mistral_client = None
55
-
56
- if gemini_available and self.config.GEMINI_API_KEY:
57
- try:
58
- genai.configure(api_key=self.config.GEMINI_API_KEY)
59
- self.gemini_model = genai.GenerativeModel(self.config.GEMINI_MODEL)
60
- logger.info("Gemini inicializado (fallback)")
61
- except Exception as e:
62
- logger.warning(f"Gemini falhou: {e}")
63
- self.gemini_model = None
64
-
65
- def _limpar_resposta(self, texto: str) -> str:
66
- if not texto:
67
- return ""
68
- texto = re.sub(r'[\*\_\`\[\]\"\<\>]', '', texto)
69
- texto = re.sub(r'\s+', ' ', texto.replace('\n', ' ')).strip()
70
- if len(texto) > 280:
71
- frases = [f.strip() for f in texto.split('. ') if f.strip()]
72
- curto = ""
73
- for f in frases:
74
- if len(curto + f + ". ") <= 280:
75
- curto += f + ". "
76
- else:
77
- break
78
- texto = curto.strip()
79
- if not texto.endswith(('.', '!', '?')):
80
- texto += "..."
81
- return texto.strip()
82
-
83
- def generate(self, prompt: str, max_tokens: int = 500, temperature: float = 0.8) -> str:
84
- max_attempts = 6
85
- for attempt in range(1, max_attempts + 1):
86
- if self.mistral_client:
87
- try:
88
- resp = self.mistral_client.chat.complete(
89
- model=self.config.MISTRAL_MODEL,
90
- messages=[{"role": "user", "content": prompt}],
91
- max_tokens=max_tokens,
92
- temperature=temperature,
93
- top_p=self.config.TOP_P,
94
- )
95
- text = resp.choices[0].message.content
96
- if text:
97
- logger.info(f"Mistral OK (tentativa {attempt})")
98
- return self._limpar_resposta(text)
99
- except Exception as e:
100
- if "401" in str(e) or "Unauthorized" in str(e):
101
- logger.error("MISTRAL: Chave inválida!")
102
- elif "429" in str(e):
103
- time.sleep(2 ** (attempt % 3))
104
- logger.warning(f"Mistral erro {attempt}: {e}")
105
-
106
- if self.llama.model:
107
- try:
108
- resp = self.llama.generate(prompt, max_tokens)
109
- if resp.strip():
110
- logger.info(f"Llama OK (tentativa {attempt})")
111
- return self._limpar_resposta(resp)
112
- except Exception as e:
113
- logger.warning(f"Llama erro {attempt}: {e}")
114
-
115
- if self.gemini_model:
116
- try:
117
- resp = self.gemini_model.generate_content(
118
- prompt,
119
- generation_config={
120
- "max_output_tokens": max_tokens,
121
- "temperature": temperature,
122
- "top_p": self.config.TOP_P,
123
- }
124
- )
125
- text = resp.text
126
- if text:
127
- logger.info(f"Gemini OK (tentativa {attempt})")
128
- return self._limpar_resposta(text)
129
- except Exception as e:
130
- if "429" in str(e) or "quota" in str(e):
131
- time.sleep(2 ** (attempt % 3))
132
- logger.warning(f"Gemini erro {attempt}: {e}")
133
-
134
- logger.error("Todos LLMs falharam")
135
- return self.config.FALLBACK_RESPONSE
136
-
137
-
138
- class AkiraAPI:
139
- def __init__(self, cfg_module):
140
- self.config = cfg_module
141
- self.app = Flask(__name__)
142
- self.api = Blueprint("akira_api", __name__)
143
- self.db = Database(getattr(self.config, 'DB_PATH', '/app/data/akira.db'))
144
- self.contexto_cache = {}
145
- self.providers = LLMManager(self.config)
146
- self.treinador = Treinamento(self.db)
147
- self.web_search = WebSearch()
148
- self._setup_routes()
149
-
150
- # REGISTRO DUPLO COM NOMES ÚNICOS → SEM CONFLITO
151
- self.app.register_blueprint(self.api, url_prefix="/api", name="akira_api_prefixed")
152
- self.app.register_blueprint(self.api, url_prefix="", name="akira_api_root")
153
-
154
- if getattr(self.config, 'START_PERIODIC_TRAINER', False):
155
- self.treinador.start_periodic_training()
156
-
157
- def _get_user_context(self, usuario: str, numero: str) -> Contexto:
158
- key = numero or usuario
159
- if key not in self.contexto_cache:
160
- # RETIFICAÇÃO FINAL: Contexto(db, user_key=key)
161
- ctx = Contexto(self.db, user_key=key)
162
- ctx.atualizar_aprendizados_do_banco()
163
- self.contexto_cache[key] = ctx
164
- return self.contexto_cache[key]
165
-
166
- def _get_pronomes_por_tom(self, tom: str) -> str:
167
- result = self.db._execute_with_retry(
168
- "SELECT pronomes FROM pronomes_por_tom WHERE tom=?", (tom.lower(),)
169
- )
170
- return result[0][0] if result else ""
171
-
172
- def _build_prompt(self, usuario: str, numero: str, mensagem: str, emocao: str,
173
- contexto: Contexto, is_privileged: bool, is_reply: bool, mensagem_original: str) -> str:
174
- data_hora = datetime.datetime.now().strftime("%d/%m/%Y %H:%M")
175
- noticias = self.web_search.pesquisar_noticias_angola()
176
- tom_usuario = contexto.ton_predominante or "neutro"
177
- pronomes = self._get_pronomes_por_tom(tom_usuario)
178
-
179
- strict = f"""
180
- REGRAS OBRIGATÓRIAS:
181
- - FALE COMO GENTE, NUNCA COMO IA
182
- - 1-2 FRASES CURTAS
183
- - GÍRIAS: bué, fixe, puto, oroh, mwangolé, kota
184
- - SARCASMO OK
185
- - USE kkk, rsrs, aff
186
- - SEM markdown, negrito
187
- - USE NOME ~40% DAS VEZES
188
- - DATA: {data_hora}
189
- # NOTÍCIAS ANGOLA:
190
- {noticias}
191
- # PRONOMES ({tom_usuario}):
192
- {pronomes}
193
- # EMOÇÃO ATUAL:
194
- {emocao}
195
- """
196
- if is_privileged:
197
- strict += "- TOM FORMAL COM ISAAC: Sr., boss, maior\n"
198
- else:
199
- strict += "- TOM NORMAL: fala de Luanda\n"
200
-
201
- hist = contexto.obter_historico()[-3:]
202
- hist_txt = "\n".join([f"U: {h[0]}\nA: {h[1]}" for h in hist]) if hist else ""
203
-
204
- user_info = f"""
205
- USUÁRIO:
206
- - Nome: {usuario}
207
- - Número: {numero}
208
- - Tom: {tom_usuario}
209
- - Privilégio: {'sim' if is_privileged else 'não'}
210
- """
211
-
212
- prompt = f"[SYSTEM]\n{strict}\n{self.config.SYSTEM_PROMPT}\n{self.config.PERSONA}\n[/SYSTEM]\n"
213
- prompt += f"[CONTEXTO]\n{hist_txt}\n{user_info}\n[/CONTEXTO]\n"
214
- prompt += f"[MENSAGEM]\n{mensagem}\n[/MENSAGEM]\nAkira:"
215
- return prompt
216
-
217
- def _setup_routes(self):
218
- @self.api.route('/akira', methods=['POST'])
219
- @self.api.route('/', methods=['POST'])
220
- def akira_endpoint():
221
- try:
222
- # --- PROTEÇÃO JSON + EXTRAÇÃO ---
223
- raw_data = request.get_data(as_text=True)
224
- data = {}
225
-
226
- try:
227
- data = request.get_json(force=True, silent=True) or {}
228
- except Exception as e:
229
- logger.warning(f"Erro ao parsear JSON: {e}")
230
-
231
- if isinstance(data, str):
232
- try:
233
- data = json.loads(data)
234
- except Exception as e:
235
- logger.warning(f"Erro ao json.loads(string): {e}")
236
- data = {}
237
-
238
- if not isinstance(data, dict):
239
- data = {}
240
-
241
- if not data:
242
- logger.warning(f"Corpo vazio: {raw_data[:200]}")
243
-
244
- usuario = data.get('usuario', 'anonimo') if isinstance(data, dict) else 'anonimo'
245
- numero = data.get('numero', '') if isinstance(data, dict) else ''
246
- mensagem = data.get('mensagem', '') if isinstance(data, dict) else ''
247
- is_privileged = (usuario.lower() == 'isaac' or '244937035662' in numero)
248
- is_reply = bool(data.get('is_reply') or data.get('mensagem_original')) if isinstance(data, dict) else False
249
- mensagem_original = (data.get('mensagem_original') or data.get('quoted_message') or '') if isinstance(data, dict) else ''
250
-
251
- if not mensagem.strip():
252
- return jsonify({'error': 'mensagem obrigatória'}), 400
253
-
254
- logger.info(f"{usuario} ({numero}): {mensagem[:120]}")
255
-
256
- # --- PROCESSAMENTO ---
257
- contexto = self._get_user_context(usuario, numero)
258
- emocao = contexto.analisar_emocoes_mensagem(mensagem)
259
- prompt = self._build_prompt(usuario, numero, mensagem, emocao, contexto, is_privileged, is_reply, mensagem_original)
260
- resposta = self.providers.generate(prompt, max_tokens=500, temperature=0.8)
261
-
262
- if random.random() < getattr(self.config, 'USAR_NOME_PROBABILIDADE', 0.4):
263
- if random.random() < 0.5:
264
- resposta = f"{usuario}, {resposta.lower()}"
265
- else:
266
- resposta = f"{resposta} {usuario}"
267
-
268
- # --- ATUALIZAÇÃO ---
269
- contexto.atualizar_contexto(mensagem, resposta)
270
- self.treinador.registrar_interacao(usuario, mensagem, resposta, numero, is_reply, mensagem_original)
271
-
272
- # --- RETORNO BLINDADO ---
273
  try:
274
- termo_contexto = contexto.termo_contexto
275
- if isinstance(termo_contexto, str):
276
- try:
277
- termo_contexto = json.loads(termo_contexto)
278
- except:
279
- termo_contexto = []
280
- if not isinstance(termo_contexto, list):
281
- termo_contexto = []
282
-
283
- girias_lista = getattr(contexto, 'girias_aprendidas', [])
284
- if isinstance(girias_lista, str):
285
- try:
286
- girias_lista = json.loads(girias_lista)
287
- except:
288
- girias_lista = []
289
- if not isinstance(girias_lista, list):
290
- girias_lista = []
291
- girias_seguras = [
292
- g.get('giria', '') for g in girias_lista[:3]
293
- if isinstance(g, dict)
294
  ]
295
  except Exception as e:
296
- logger.warning(f"Erro ao processar aprendizados: {e}")
297
- termo_contexto = []
298
- girias_seguras = []
299
-
300
- return jsonify({
301
- 'resposta': resposta,
302
- 'aprendizados': {
303
- 'emocao_atual': contexto.emocao_atual,
304
- 'termos': termo_contexto,
305
- 'gírias': girias_seguras
306
- }
307
- })
308
-
309
- except Exception as e:
310
- logger.error(f"Erro fatal: {e}", exc_info=True)
311
- return jsonify({'resposta': 'tive erro, puto. tenta depois.'}), 500
312
-
313
- def run(self, host='0.0.0.0', port=7860, debug=False):
314
- logger.info(f"Iniciando Flask na porta {port}")
315
- self.app.run(host=host, port=port, debug=debug, threaded=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Contexto de conversa por usuário
3
+ ================================
4
+ - Memória afetiva
5
+ - Análise de emoção (retorna str)
6
+ - Gírias aprendidas (sempre lista de dict)
7
+ - Integração com banco
8
+ - SEM MÉTODOS INEXISTENTES
9
  """
 
 
 
 
 
10
  import json
11
+ from typing import List, Tuple, Dict
12
  from loguru import logger
13
  import modules.config as config
14
+ from .database import Database # IMPORT CORRETO: database.py
15
+
16
+ class Contexto:
17
+ def __init__(self, db: Database, user_key: str):
18
+ self.db = db
19
+ self.user_key = user_key
20
+ self.historico: List[Tuple[str, str]] = []
21
+ self.emocao_atual: str = config.HUMOR_INICIAL
22
+ self.termo_contexto: List[str] = []
23
+ self.girias_aprendidas: List[Dict[str, str]] = []
24
+ self.ton_predominante: str = "neutro"
25
+ self._carregar_do_banco()
26
+
27
+ def _carregar_do_banco(self):
28
+ try:
29
+ dados = self.db.carregar_contexto(self.user_key)
30
+ if dados:
31
+ self.historico = json.loads(dados.get("historico", "[]"))
32
+ self.emocao_atual = dados.get("emocao_atual", config.HUMOR_INICIAL)
33
+ self.termo_contexto = json.loads(dados.get("termos", "[]"))
34
+ girias_raw = dados.get("girias", "[]")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
+ girias = json.loads(girias_raw) if isinstance(girias_raw, str) else girias_raw
37
+ self.girias_aprendidas = [
38
+ g for g in girias
39
+ if isinstance(g, dict) and "giria" in g
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  ]
41
  except Exception as e:
42
+ logger.warning(f"Erro ao parsear gírias: {e}")
43
+ self.girias_aprendidas = []
44
+ self.ton_predominante = dados.get("tom", "neutro")
45
+ except Exception as e:
46
+ logger.warning(f"Erro ao carregar contexto: {e}")
47
+ self.historico = []
48
+ self.girias_aprendidas = []
49
+ self.termo_contexto = []
50
+
51
+ def salvar_no_banco(self):
52
+ try:
53
+ self.db.salvar_contexto(
54
+ self.user_key,
55
+ json.dumps(self.historico[-config.MEMORIA_MAX:]),
56
+ self.emocao_atual,
57
+ json.dumps(self.termo_contexto),
58
+ json.dumps(self.girias_aprendidas),
59
+ self.ton_predominante
60
+ )
61
+ except Exception as e:
62
+ logger.error(f"Erro ao salvar contexto: {e}")
63
+
64
+ def atualizar_aprendizados_do_banco(self):
65
+ try:
66
+ girias = self.db.listar_girias_aprendidas(self.user_key)
67
+ self.girias_aprendidas = [
68
+ {"giria": g[0], "significado": g[1]} for g in girias
69
+ ]
70
+ except Exception as e:
71
+ logger.warning(f"Erro ao atualizar gírias: {e}")
72
+ self.girias_aprendidas = []
73
+
74
+ def analisar_emocoes_mensagem(self, mensagem: str) -> str:
75
+ msg = mensagem.lower()
76
+ if any(p in msg for p in ["feliz", "fixe", "bué bom", "adoro", "rsrs", "kkk"]):
77
+ return "feliz"
78
+ if any(p in msg for p in ["triste", "chateado", "merda", "puto", "caralho"]):
79
+ return "triste"
80
+ if any(p in msg for p in ["irritada", "foda-se", "vsf", "burro"]):
81
+ return "irritada"
82
+ if any(p in msg for p in ["curioso", "o que", "como", "porquê"]):
83
+ return "curiosa"
84
+ return "neutra"
85
+
86
+ def obter_historico(self) -> List[Tuple[str, str]]:
87
+ return self.historico[-3:]
88
+
89
+ def atualizar_contexto(self, mensagem: str, resposta: str):
90
+ self.historico.append((mensagem, resposta))
91
+ if len(self.historico) > config.MEMORIA_MAX:
92
+ self.historico.pop(0)
93
+ self.emocao_atual = self.analisar_emocoes_mensagem(mensagem)
94
+ self.salvar_no_banco()