# modules/treinamento.py import threading import time import json import os import shutil import subprocess from loguru import logger from sentence_transformers import SentenceTransformer from peft import LoraConfig from .database import Database from .local_llm import _get_llm # EMBEDDING MODEL TOP PRA ANGOLANO + PORTUGUÊS EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" embedding_model = SentenceTransformer(EMBEDDING_MODEL) FINETUNED_PATH = "/home/user/data/finetuned_hermes" os.makedirs(FINETUNED_PATH, exist_ok=True) def gerar_embedding(text: str): return embedding_model.encode(text, convert_to_numpy=True) class Treinamento: def __init__(self, db: Database, interval_hours: int = 3): self.db = db self.interval_hours = interval_hours self._thread = None self._running = False self.llm = _get_llm() if self.llm: logger.info("TREINAMENTO CONECTADO AO TINYLLAMA 1.1B GGUF (LORA COMPATÍVEL!)") else: logger.warning("LLM não carregado → finetune desativado") def registrar_interacao(self, usuario, mensagem, resposta, numero='', is_reply=False, mensagem_original=''): self.db.salvar_mensagem(usuario, mensagem, resposta, numero, is_reply, mensagem_original) self._aprender_roleplay(numero, mensagem, resposta) self._salvar_embedding(usuario, mensagem, resposta) def _salvar_embedding(self, usuario, msg, resp): """SALVA EMBEDDINGS PRA BUSCA SEMÂNTICA (NLP AVANÇADO)""" try: msg_emb = gerar_embedding(msg) resp_emb = gerar_embedding(resp) path = f"{FINETUNED_PATH}/embeddings.jsonl" entry = { "usuario": usuario, "msg": msg, "resp": resp, "msg_emb": msg_emb.tolist(), "resp_emb": resp_emb.tolist(), "timestamp": time.time() } with open(path, "a", encoding="utf-8") as f: json.dump(entry, f, ensure_ascii=False) f.write("\n") logger.debug(f"Embedding salvo: {msg[:30]}...") except Exception as e: logger.error(f"Erro ao salvar embedding: {e}") def _aprender_roleplay(self, numero: str, msg: str, resp: str): if not numero or not self.llm: return dataset_path = f"{FINETUNED_PATH}/roleplay_tinyllama.jsonl" entry = { "messages": [ {"role": "system", "content": "Tu és Akira, kota fixe de Luanda. Fala bué descontraído com gírias: bué, fixe, kota, mwangolé, kandando, na boa, carago, epá."}, {"role": "user", "content": msg}, {"role": "assistant", "content": resp} ] } with open(dataset_path, "a", encoding="utf-8") as f: json.dump(entry, f, ensure_ascii=False) f.write("\n") logger.debug(f"Roleplay TinyLlama salvo: {msg[:30]}... → {resp[:30]}...") def train_once(self): if not self.llm: logger.warning("TinyLlama não carregado. Pulando finetune.") return dataset_path = f"{FINETUNED_PATH}/roleplay_tinyllama.jsonl" if not os.path.exists(dataset_path) or os.path.getsize(dataset_path) < 500: logger.info("Poucos dados pro TinyLlama. Esperando mais kandandos...") return logger.info("INICIANDO FINETUNE LORA TURBO PRO TINYLLAMA 1.1B (3 SEGUNDOS + SOTAQUE LUANDA MELHORADO!)") try: lora_path = f"{FINETUNED_PATH}/temp_lora_tiny" os.makedirs(lora_path, exist_ok=True) # LoRA CONFIG OTIMIZADA PRA TINYLLAMA config = LoraConfig( r=64, lora_alpha=128, target_modules=["q_proj", "v_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) config.save_pretrained(lora_path) # COMANDO CORRETO PRA TREINAR LORA COM llama.cpp (FUNCIONA COM TINYLLAMA!) cmd = [ "python", "-m", "llama_cpp.convert", "--model", "/home/user/models/tinyllama-1.1b-chat-v1.0.Q5_K_M.gguf", "--outfile", f"{lora_path}/adapter_model.bin", "--lora-out", lora_path, "--train", dataset_path, "--epochs", "2", "--lora-r", "64", "--lora-alpha", "128", "--batch", "8", "--threads", "4", "--ctx", "2048", "--adam-iter", "100" ] logger.info("Rodando finetune LoRA TinyLlama...") result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) if result.returncode == 0 and os.path.exists(f"{lora_path}/adapter_model.bin"): # SUBSTITUI O LORA ATUAL shutil.move(f"{lora_path}/adapter_model.bin", f"{FINETUNED_PATH}/adapter_model.bin") shutil.move(f"{lora_path}/adapter_config.json", f"{FINETUNED_PATH}/adapter_config.json") logger.info("LORA ANGOLANO TURBO ATUALIZADO COM SUCESSO! SOTAQUE DE LUANDA NÍVEL MÁXIMO!") # LIMPA DATASET open(dataset_path, 'w').close() logger.info("Dataset limpo. TinyLlama tá mais angolano que nunca!") else: logger.error(f"Erro no finetune: {result.stderr[:500]}") except subprocess.TimeoutExpired: logger.warning("Finetune demorou → pulando (HF Spaces tem limite)") except Exception as e: logger.error(f"Erro crítico no finetune TinyLlama: {e}") def _run_loop(self): interval = self.interval_hours * 3600 while self._running: try: self.train_once() except Exception as e: logger.exception(f"Erro no loop de treino: {e}") time.sleep(interval) def start_periodic_training(self): if self._running or not self.llm: return self._running = True self._thread = threading.Thread(target=self._run_loop, daemon=True) self._thread.start() logger.info(f"TREINAMENTO PERIÓDICO TINYLLAMA INICIADO (a cada {self.interval_hours}h)")