akra35567 commited on
Commit
e8afb10
·
1 Parent(s): 55a60a8

Update modules/treinamento.py

Browse files
Files changed (1) hide show
  1. modules/treinamento.py +175 -187
modules/treinamento.py CHANGED
@@ -1,4 +1,13 @@
1
- # treinamento.py
 
 
 
 
 
 
 
 
 
2
  import threading
3
  import time
4
  import logging
@@ -7,57 +16,53 @@ import re
7
  import json
8
  import collections
9
  from typing import Optional, Any, List, Dict, Tuple
 
10
 
11
  logger = logging.getLogger(__name__)
12
 
13
- # MODELO MAIS PESADO E ROBUSTO: paraphrase-multilingual-mpnet-base-v2
14
- # - 110M parâmetros
15
- # - Suporta 50+ idiomas (inclui português, gírias, sotaques)
16
- # - Excelente em: semântica, intenção, emoção, ironia, contexto
17
- # - Ideal para bots com "alma humana"
18
  try:
19
  from sentence_transformers import SentenceTransformer
20
- # Força o modelo mais poderoso
21
  MODEL_NAME = "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
22
- logger.info(f"Carregando modelo pesado: {MODEL_NAME}")
23
  except Exception as e:
24
  logger.warning(f"sentence_transformers não disponível: {e}")
25
  SentenceTransformer = None
26
  MODEL_NAME = None
27
 
28
- # Listas expandidas para análise emocional + gírias angolanas
29
- PALAVRAS_POSITIVAS = [
30
- 'bom', 'ótimo', 'incrível', 'maravilhoso', 'feliz', 'alegre', 'amor', 'gostar', 'adorei',
31
- 'top', 'show', 'legal', 'bacana', 'fixe', 'bué', 'oroh', 'máximo', 'perfeito', 'genial',
32
- 'divertido', 'hilário', 'gargalhada', 'rsrs', 'kkk', 'lol', 'haha', 'amo', 'adoro'
33
- ]
34
-
35
- PALAVRAS_NEGATIVAS = [
36
- 'ruim', 'péssimo', 'horrível', 'triste', 'ódio', 'raiva', 'chateado', 'detesto', 'odeio',
37
- 'merda', 'porra', 'caralho', 'puto', 'foda-se', 'tristeza', 'depressão', 'sofrimento',
38
- 'choro', 'lágrima', 'dor', 'sofrer', 'fracasso', 'perdi', 'derrota'
39
- ]
40
-
41
- GIRIAS_ANGOLANAS = [
42
- 'mano', 'puto', 'kkk', 'rsrs', 'lol', '', '', 'num', 'tipo', '', 'bah', 'uai',
43
- 'oxe', 'eita', 'caramba', 'pqp', 'fdp', 'vsf', 'mlk', 'mwangolé', 'kota', 'mané',
44
- 'oroh', 'bué', 'fixe', 'kota', 'baza', 'bazar', 'bazar fora', 'está fixe', 'está bué'
45
- ]
46
-
47
- PALAVRAS_RUDES = [
48
- 'puto', 'merda', 'porra', 'caralho', 'cacete', 'fdp', 'vsf', 'mlk', 'arrombado',
49
- 'viado', 'bicha', 'cu', 'buceta', 'rola', 'pau', 'bunda', 'peito', 'teta', 'bct',
50
- 'pnc', 'pnctl', 'fuder', 'foder', 'transar', 'comer', 'chupar', 'mamada', 'boquete',
51
- 'punheta', 'gozar', 'pqp', 'vai tomar no cu', 'vai se foder', 'seu filho da puta'
52
- ]
53
 
54
 
55
  class Treinamento:
56
  """
57
- Treinamento com o modelo MAIS PESADO E HUMANO:
58
- - paraphrase-multilingual-mpnet-base-v2
59
- - Aprendizado em tempo real + periódico
60
- - Detecta: intenção, emoção, ironia, gírias, tom, contexto
61
  """
62
 
63
  def __init__(self, db, contexto: Optional[Any] = None, interval_hours: int = 1):
@@ -67,27 +72,23 @@ class Treinamento:
67
  self._thread = None
68
  self._running = False
69
  self._model = None
70
- self.privileged_users = ['244937035662', 'isaac', 'isaac quarenta', 'ceo', 'fundador']
 
71
 
72
  # ================================================================
73
- # CARREGAMENTO DO MODELO PESADO (com fallback)
74
  # ================================================================
75
 
76
- def _ensure_model(self):
77
  if self._model is not None:
78
  return
79
- if self.contexto and hasattr(self.contexto, 'model') and self.contexto.model:
80
- self._model = self.contexto.model
81
- return
82
- if SentenceTransformer is None or MODEL_NAME is None:
83
- logger.warning("Modelo pesado não disponível. Usando análise heurística.")
84
  return
85
  try:
86
- logger.info(f"Carregando modelo pesado: {MODEL_NAME} (pode demorar 10-20s)...")
87
  self._model = SentenceTransformer(MODEL_NAME)
88
- logger.info("Modelo pesado carregado com sucesso! Akira agora é mais humana.")
89
  except Exception as e:
90
- logger.error(f"Falha ao carregar modelo pesado: {e}")
91
  self._model = None
92
 
93
  # ================================================================
@@ -95,11 +96,11 @@ class Treinamento:
95
  # ================================================================
96
 
97
  def registrar_interacao(self, usuario: str, mensagem: str, resposta: str, numero: str = '', is_reply: bool = False, mensagem_original: str = ''):
98
- """Registra + aprende na hora com modelo pesado"""
99
  try:
100
  self.db.salvar_mensagem(usuario, mensagem, resposta, numero, is_reply, mensagem_original)
101
  self._aprender_em_tempo_real(numero, mensagem, resposta)
102
- logger.info(f"Interação aprendida em tempo real: {numero}")
103
  except Exception as e:
104
  logger.warning(f'Erro ao registrar: {e}')
105
 
@@ -109,180 +110,168 @@ class Treinamento:
109
 
110
  texto = f"{msg} {resp}".lower()
111
 
112
- # === ANÁLISE COM MODELO PESADO (se disponível) ===
113
- self._ensure_model()
114
  if self._model:
115
  try:
116
- # Embedding da mensagem completa
117
  emb = self._model.encode(texto).tobytes()
118
  self.db.salvar_embedding(texto, emb)
 
119
 
120
- # Similaridade com frases emocionais (exemplo)
121
- frases_emocao = {
122
- "feliz": "estou muito feliz hoje",
123
- "triste": "estou muito triste e sozinho",
124
- "raiva": "estou puto com tudo",
125
- "amor": "eu te amo muito"
126
- }
127
- embs_ref = self._model.encode(list(frases_emocao.values()))
128
- sims = self._model.encode(texto) @ embs_ref.T
129
- emocao_pred = list(frases_emocao.keys())[sims.argmax()]
130
- intensidade = float(sims.max())
131
- self.db.salvar_aprendizado_detalhado(numero, "emocao_ia", json.dumps({
132
- "emocao": emocao_pred,
133
- "intensidade": intensidade,
134
- "fonte": "mpnet"
135
- }))
136
- except Exception as e:
137
- logger.warning(f"Erro no modelo pesado: {e}")
138
-
139
- # === ANÁLISE HEURÍSTICA (sempre) ===
140
  rude = any(p in texto for p in PALAVRAS_RUDES)
141
  tom = 'rude' if rude else 'casual'
142
 
143
  palavras = [p for p in re.findall(r'\b\w{4,}\b', texto)
144
- if p not in {'não', 'que', 'com', 'pra', 'pro', 'uma', 'ele', 'ela', 'isso'}]
145
  contador = collections.Counter(palavras)
146
  top_girias = [w for w, c in contador.most_common(5) if c > 1]
147
 
148
  # Salvar tom
149
- intensidade_tom = 0.8 if rude else 0.5
150
- self.db.registrar_tom_usuario(numero, tom, intensidade_tom, texto[:100])
151
 
152
  # Salvar gírias
153
  for giria in top_girias:
154
- significado = "gíria agressiva" if rude else "gíria local"
155
  self.db.salvar_giria_aprendida(numero, giria, significado, texto[:100])
156
 
 
 
 
 
157
  # ================================================================
158
- # TREINAMENTO PERIÓDICO (a cada hora)
159
  # ================================================================
160
 
161
- def train_once(self):
162
- logger.info("Iniciando treinamento periódico com modelo pesado...")
163
- data = self._fetch_recent_data(limit=1000)
164
- if not data:
165
- logger.info("Nenhum dado para treinar.")
166
- self._salvar_ultimo_treino()
 
 
 
 
 
 
 
 
167
  return
168
 
169
- usuarios = set(row[1] for row in data if row[1] and row[1].startswith('244'))
170
- for numero in usuarios:
171
- msgs = self._fetch_user_messages(numero, limit=50)
172
- if len(msgs) < 3:
173
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- analise = self._analisar_com_mpnet(msgs) if self._model else self._analisar_heuristica(msgs)
176
- tom = self._detectar_tom_usuario(msgs, numero)
177
 
178
- self.db.salvar_aprendizado_detalhado(numero, 'emocao_predominante', analise['emocao_predominante'])
179
- self.db.salvar_aprendizado_detalhado(numero, 'intensidade_emocional', str(analise['intensidade_media']))
180
- self.db.registrar_tom_usuario(numero, tom, analise['intensidade_media'])
181
 
182
- self._gerar_embeddings_globais(data)
 
 
 
183
  self._salvar_ultimo_treino()
184
- logger.info("Treinamento concluído com sucesso.")
185
-
186
- def _analisar_com_mpnet(self, mensagens: List[Tuple]) -> Dict:
187
- """Análise emocional com modelo pesado"""
188
- textos = [f"{m} {r}" for m, r in mensagens]
189
- embs = self._model.encode(textos)
190
-
191
- # Frases de referência
192
- refs = {
193
- "feliz": "estou muito feliz e animado",
194
- "triste": "estou triste e deprimido",
195
- "raiva": "estou com raiva e irritado",
196
- "amor": "eu amo e adoro essa pessoa"
197
- }
198
- ref_embs = self._model.encode(list(refs.values()))
199
- sims = embs @ ref_embs.T
200
- emocoes = [list(refs.keys())[i] for i in sims.argmax(axis=1)]
201
- intensidades = sims.max(axis=1)
202
-
203
- counter = collections.Counter(emocoes)
204
- return {
205
- 'emocao_predominante': counter.most_common(1)[0][0],
206
- 'intensidade_media': float(intensidades.mean())
207
- }
208
-
209
- def _analisar_heuristica(self, mensagens: List[Tuple]) -> Dict:
210
- counter = collections.Counter()
211
- intensidade = 0
212
- total = len(mensagens)
213
- for msg, resp in mensagens:
214
- texto = (msg or '') + ' ' + (resp or '')
215
- analise = self.db.analisar_emocoes_mensagem(texto)
216
- counter[analise['emocao']] += 1
217
- intensidade += analise['intensidade']
218
- return {
219
- 'emocao_predominante': counter.most_common(1)[0][0] if counter else 'neutro',
220
- 'intensidade_media': intensidade / total if total > 0 else 0
221
- }
222
-
223
- def _detectar_tom_usuario(self, mensagens: List[Tuple], numero: str) -> str:
224
  if numero in self.privileged_users:
225
  return 'formal'
226
  counter = collections.Counter()
227
  for msg, _ in mensagens:
228
- msg_lower = (msg or '').lower()
229
- if any(p in msg_lower for p in PALAVRAS_RUDES):
230
  counter['rude'] += 1
231
- elif any(p in msg_lower for p in ['por favor', 'obrigado', 'senhor']):
232
  counter['formal'] += 1
233
- elif any(p in msg_lower for p in GIRIAS_ANGOLANAS):
234
  counter['casual'] += 1
235
  else:
236
  counter['neutro'] += 1
237
  return counter.most_common(1)[0][0] if counter else 'neutro'
238
 
239
- def _gerar_embeddings_globais(self, data: List[Tuple]):
240
- if not self._model:
241
- return
242
- sentences = [row[2] for row in data if row[2] and len(row[2]) > 10][:256]
243
- try:
244
- embeddings = self._model.encode(sentences)
245
- for s, emb in zip(sentences, embeddings):
246
- self.db.salvar_embedding(s, emb.tobytes())
247
- except Exception as e:
248
- logger.warning(f'Erro ao gerar embeddings globais: {e}')
249
-
250
- def _fetch_recent_data(self, limit=1000) -> List[Tuple]:
251
- rows = []
252
- try:
253
- conn = sqlite3.connect(self.db.db_path)
254
- c = conn.cursor()
255
- c.execute('''
256
- SELECT usuario, numero, mensagem, resposta
257
- FROM mensagens
258
- WHERE resposta IS NOT NULL AND resposta != ''
259
- AND numero IS NOT NULL AND numero != '' AND numero != 'unknown'
260
- AND LENGTH(numero) >= 10 AND numero LIKE '244%'
261
- ORDER BY id DESC LIMIT ?
262
- ''', (limit,))
263
- rows = c.fetchall()
264
- conn.close()
265
- except Exception as e:
266
- logger.error(f'Erro ao buscar dados: {e}')
267
- return rows
268
-
269
- def _fetch_user_messages(self, numero: str, limit: int = 50) -> List[Tuple]:
270
- rows = []
271
- try:
272
- conn = sqlite3.connect(self.db.db_path)
273
- c = conn.cursor()
274
- c.execute('SELECT mensagem, resposta FROM mensagens WHERE numero=? ORDER BY id DESC LIMIT ?', (numero, limit))
275
- rows = c.fetchall()
276
- conn.close()
277
- except Exception as e:
278
- logger.error(f'Erro ao buscar mensagens do usuário {numero}: {e}')
279
- return rows
280
-
281
  def _salvar_ultimo_treino(self):
282
  try:
283
  self.db.salvar_info_geral('ultimo_treino', str(time.time()))
284
- except:
285
- pass
286
 
287
  # ================================================================
288
  # LOOP DE TREINAMENTO
@@ -290,7 +279,7 @@ class Treinamento:
290
 
291
  def _run_loop(self):
292
  interval = max(1, self.interval_hours) * 3600
293
- logger.info(f"Treinamento periódico iniciado (a cada {self.interval_hours}h)")
294
  while self._running:
295
  try:
296
  self.train_once()
@@ -300,11 +289,10 @@ class Treinamento:
300
  if not self._running:
301
  break
302
  time.sleep(1)
303
- logger.info("Treinamento periódico parado.")
304
 
305
  def start_periodic_training(self):
306
- if self._running:
307
- return
308
  self._running = True
309
  self._thread = threading.Thread(target=self._run_loop, daemon=True)
310
  self._thread.start()
 
1
+ # modules/treinamento.py
2
+ """
3
+ Sistema de treinamento avançado para Akira IA.
4
+ - Fine-tuning leve do YAYA-23-8B com LoRA (PEFT)
5
+ - Análise emocional com sentence-transformers (mpnet)
6
+ - Aprendizado em tempo real + periódico
7
+ - Gírias, tom, ironia, contexto angolano
8
+ - Integração total com database.py
9
+ """
10
+
11
  import threading
12
  import time
13
  import logging
 
16
  import json
17
  import collections
18
  from typing import Optional, Any, List, Dict, Tuple
19
+ from dataclasses import dataclass
20
 
21
  logger = logging.getLogger(__name__)
22
 
23
+ # MODELO PESADO: paraphrase-multilingual-mpnet-base-v2
 
 
 
 
24
  try:
25
  from sentence_transformers import SentenceTransformer
 
26
  MODEL_NAME = "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
27
+ logger.info(f"Modelo NLP carregado: {MODEL_NAME}")
28
  except Exception as e:
29
  logger.warning(f"sentence_transformers não disponível: {e}")
30
  SentenceTransformer = None
31
  MODEL_NAME = None
32
 
33
+ # YAYA LOCAL (para fine-tuning)
34
+ try:
35
+ from modules.local_llm import YayaLLM
36
+ from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
37
+ from transformers import TrainingArguments, Trainer, DataCollatorForLanguageModeling
38
+ PEFT_AVAILABLE = True
39
+ except Exception as e:
40
+ logger.warning(f"PEFT não disponível: {e}")
41
+ PEFT_AVAILABLE = False
42
+
43
+ # Listas angolanas
44
+ PALAVRAS_POSITIVAS = ['bom', 'ótimo', 'incrível', 'feliz', 'alegre', 'fixe', 'bué', 'top', 'show', 'adoro', 'rsrs', 'kkk']
45
+ PALAVRAS_NEGATIVAS = ['ruim', 'péssimo', 'triste', 'ódio', 'puto', 'merda', 'caralho', 'chateado']
46
+ GIRIAS_ANGOLANAS = ['mano', 'puto', 'cota', 'mwangolé', 'kota', 'oroh', 'bué', 'fixe', 'baza', 'kuduro']
47
+ PALAVRAS_RUDES = ['caralho', 'puto', 'merda', 'fdp', 'vsf', 'burro', 'idiota', 'parvo']
48
+
49
+
50
+ @dataclass
51
+ class Interacao:
52
+ usuario: str
53
+ mensagem: str
54
+ resposta: str
55
+ numero: str
56
+ is_reply: bool = False
57
+ mensagem_original: str = ""
58
 
59
 
60
  class Treinamento:
61
  """
62
+ Treinamento contínuo da Akira:
63
+ - Registra interações
64
+ - Analisa tom, emoção, gírias
65
+ - Fine-tuning periódico do YAYA com LoRA
66
  """
67
 
68
  def __init__(self, db, contexto: Optional[Any] = None, interval_hours: int = 1):
 
72
  self._thread = None
73
  self._running = False
74
  self._model = None
75
+ self.yaya = YayaLLM() if PEFT_AVAILABLE else None
76
+ self.privileged_users = ['244937035662', 'isaac', 'isaac quarenta']
77
 
78
  # ================================================================
79
+ # CARREGAMENTO DO MODELO NLP
80
  # ================================================================
81
 
82
+ def _ensure_nlp_model(self):
83
  if self._model is not None:
84
  return
85
+ if SentenceTransformer is None:
 
 
 
 
86
  return
87
  try:
88
+ logger.info("Carregando modelo NLP pesado...")
89
  self._model = SentenceTransformer(MODEL_NAME)
 
90
  except Exception as e:
91
+ logger.error(f"Falha ao carregar modelo NLP: {e}")
92
  self._model = None
93
 
94
  # ================================================================
 
96
  # ================================================================
97
 
98
  def registrar_interacao(self, usuario: str, mensagem: str, resposta: str, numero: str = '', is_reply: bool = False, mensagem_original: str = ''):
99
+ """Salva + aprende na hora"""
100
  try:
101
  self.db.salvar_mensagem(usuario, mensagem, resposta, numero, is_reply, mensagem_original)
102
  self._aprender_em_tempo_real(numero, mensagem, resposta)
103
+ logger.info(f"Interação aprendida: {numero}")
104
  except Exception as e:
105
  logger.warning(f'Erro ao registrar: {e}')
106
 
 
110
 
111
  texto = f"{msg} {resp}".lower()
112
 
113
+ # === ANÁLISE NLP (se disponível) ===
114
+ self._ensure_nlp_model()
115
  if self._model:
116
  try:
 
117
  emb = self._model.encode(texto).tobytes()
118
  self.db.salvar_embedding(texto, emb)
119
+ except: pass
120
 
121
+ # === ANÁLISE HEURÍSTICA ===
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  rude = any(p in texto for p in PALAVRAS_RUDES)
123
  tom = 'rude' if rude else 'casual'
124
 
125
  palavras = [p for p in re.findall(r'\b\w{4,}\b', texto)
126
+ if p not in {'não', 'que', 'com', 'pra', 'uma', 'ele', 'ela'}]
127
  contador = collections.Counter(palavras)
128
  top_girias = [w for w, c in contador.most_common(5) if c > 1]
129
 
130
  # Salvar tom
131
+ intensidade = 0.9 if rude else 0.6
132
+ self.db.registrar_tom_usuario(numero, tom, intensidade, texto[:100])
133
 
134
  # Salvar gírias
135
  for giria in top_girias:
136
+ significado = "gíria rude" if rude else "gíria local"
137
  self.db.salvar_giria_aprendida(numero, giria, significado, texto[:100])
138
 
139
+ # Emoção
140
+ analise = self.db.analisar_emocoes_mensagem(texto)
141
+ self.db.salvar_aprendizado_detalhado(numero, "emocao_recente", json.dumps(analise))
142
+
143
  # ================================================================
144
+ # FINE-TUNING DO YAYA (LOKA)
145
  # ================================================================
146
 
147
+ def _prepare_dataset(self, interacoes: List[Interacao]) -> List[Dict]:
148
+ """Prepara dataset para LoRA"""
149
+ dataset = []
150
+ for i in interacoes:
151
+ prompt = f"[INST] <<SYS>>\nVocê é Akira, angolana sarcástica. Responda curto e com gírias.\n<</SYS>>\n\n{i.mensagem} [/INST]"
152
+ dataset.append({
153
+ "text": f"{prompt} {i.resposta}"
154
+ })
155
+ return dataset
156
+
157
+ def fine_tune_yaya(self):
158
+ """Fine-tuning leve com LoRA"""
159
+ if not PEFT_AVAILABLE or not self.yaya or not self.yaya.model:
160
+ logger.info("Fine-tuning desativado (YAYA/PEFT indisponível)")
161
  return
162
 
163
+ try:
164
+ logger.info("Iniciando fine-tuning do YAYA-23-8B com LoRA...")
165
+
166
+ # Pega últimas 500 interações
167
+ rows = self.db._execute_with_retry(
168
+ "SELECT usuario, mensagem, resposta, numero FROM mensagens WHERE resposta != '' ORDER BY id DESC LIMIT 500"
169
+ )
170
+ interacoes = [Interacao(u, m, r, n) for u, m, r, n in rows]
171
+
172
+ if len(interacoes) < 50:
173
+ logger.info("Poucos dados. Pulando fine-tuning.")
174
+ return
175
+
176
+ dataset = self._prepare_dataset(interacoes)
177
+
178
+ # Tokeniza
179
+ def tokenize_function(examples):
180
+ return self.yaya.tokenizer(examples["text"], truncation=True, max_length=512)
181
+
182
+ tokenized = [tokenize_function(d) for d in dataset]
183
+ from datasets import Dataset
184
+ hf_dataset = Dataset.from_list(tokenized)
185
+
186
+ # LoRA config
187
+ peft_config = LoraConfig(
188
+ r=16,
189
+ lora_alpha=32,
190
+ target_modules=["q_proj", "v_proj"],
191
+ lora_dropout=0.05,
192
+ bias="none",
193
+ task_type="CAUSAL_LM"
194
+ )
195
+
196
+ model = prepare_model_for_kbit_training(self.yaya.model)
197
+ model = get_peft_model(model, peft_config)
198
+
199
+ # Trainer
200
+ training_args = TrainingArguments(
201
+ output_dir="./yaya-finetuned",
202
+ per_device_train_batch_size=1,
203
+ gradient_accumulation_steps=4,
204
+ num_train_epochs=1,
205
+ learning_rate=2e-4,
206
+ fp16=True,
207
+ logging_steps=10,
208
+ save_steps=100,
209
+ save_total_limit=2,
210
+ report_to=[],
211
+ disable_tqdm=True
212
+ )
213
+
214
+ trainer = Trainer(
215
+ model=model,
216
+ args=training_args,
217
+ train_dataset=hf_dataset,
218
+ data_collator=DataCollatorForLanguageModeling(self.yaya.tokenizer, mlm=False)
219
+ )
220
+
221
+ trainer.train()
222
+ model.save_pretrained("./yaya-finetuned")
223
+ self.yaya.tokenizer.save_pretrained("./yaya-finetuned")
224
+ logger.info("Fine-tuning concluído! Modelo salvo.")
225
 
226
+ except Exception as e:
227
+ logger.error(f"Erro no fine-tuning: {e}")
228
 
229
+ # ================================================================
230
+ # TREINAMENTO PERIÓDICO
231
+ # ================================================================
232
 
233
+ def train_once(self):
234
+ logger.info("Treinamento periódico iniciado...")
235
+ self.fine_tune_yaya()
236
+ self._analisar_usuarios()
237
  self._salvar_ultimo_treino()
238
+ logger.info("Treinamento concluído.")
239
+
240
+ def _analisar_usuarios(self):
241
+ usuarios = set()
242
+ rows = self.db._execute_with_retry("SELECT DISTINCT numero FROM mensagens WHERE numero LIKE '244%'")
243
+ for r in rows:
244
+ usuarios.add(r[0])
245
+
246
+ for num in usuarios:
247
+ msgs = self.db._execute_with_retry(
248
+ "SELECT mensagem, resposta FROM mensagens WHERE numero=? ORDER BY id DESC LIMIT 20", (num,)
249
+ )
250
+ if len(msgs) < 3: continue
251
+
252
+ tom = self._detectar_tom(msgs, num)
253
+ self.db.salvar_preferencia_tom(num, tom)
254
+
255
+ def _detectar_tom(self, mensagens: List[Tuple], numero: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  if numero in self.privileged_users:
257
  return 'formal'
258
  counter = collections.Counter()
259
  for msg, _ in mensagens:
260
+ msg_l = (msg or '').lower()
261
+ if any(p in msg_l for p in PALAVRAS_RUDES):
262
  counter['rude'] += 1
263
+ elif any(p in msg_l for p in ['por favor', 'obrigado']):
264
  counter['formal'] += 1
265
+ elif any(p in msg_l for p in GIRIAS_ANGOLANAS):
266
  counter['casual'] += 1
267
  else:
268
  counter['neutro'] += 1
269
  return counter.most_common(1)[0][0] if counter else 'neutro'
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  def _salvar_ultimo_treino(self):
272
  try:
273
  self.db.salvar_info_geral('ultimo_treino', str(time.time()))
274
+ except: pass
 
275
 
276
  # ================================================================
277
  # LOOP DE TREINAMENTO
 
279
 
280
  def _run_loop(self):
281
  interval = max(1, self.interval_hours) * 3600
282
+ logger.info(f"Treinamento a cada {self.interval_hours}h")
283
  while self._running:
284
  try:
285
  self.train_once()
 
289
  if not self._running:
290
  break
291
  time.sleep(1)
292
+ logger.info("Treinamento parado.")
293
 
294
  def start_periodic_training(self):
295
+ if self._running: return
 
296
  self._running = True
297
  self._thread = threading.Thread(target=self._run_loop, daemon=True)
298
  self._thread.start()