Browse Source

pasta training

GabrielRamison 2 months ago
parent
commit
fbaacc3be6

+ 75 - 0
training/README.md

@@ -0,0 +1,75 @@
+# Treinamento do agente de atendimento (estágio 2)
+
+Fine-tuning (SFT com LoRA) do Llama-3.1-8B com os atendimentos reais do ifbot que
+foram **bem avaliados pelo estágio 1** (LLM-as-judge): nota do atendente ≥ 8,
+problema resolvido e cliente não-insatisfeito. O juiz funciona como filtro de
+qualidade do dataset — só "assim se atende" entra no treino.
+
+## Fluxo completo
+
+```
+avaliações (estágio 1, MySQL)
+      │  node backend/scripts/exportarDatasetTreinamento.js
+      ▼
+training/data/{train,valid}.jsonl        ← formato chat {"messages":[...]}
+      │  ./treinar_lora_mlx.sh
+      ▼
+training/adapters/                       ← LoRA treinado (MLX, roda no M4 16GB)
+      │  ./publicar_no_ollama.sh
+      ▼
+ollama: star-atendente                   ← modelo fundido + quantizado q4_K_M
+```
+
+## Passo a passo
+
+1. **Acumular avaliações** — o job do backend avalia sozinho; para acelerar:
+   `cd backend && node scripts/avaliarTudo.js --lote 25` (~20s por atendimento no llama3.1).
+
+2. **Exportar o dataset** (da pasta `backend/`):
+   ```
+   node scripts/exportarDatasetTreinamento.js            # score>=8, resolvido=sim
+   node scripts/exportarDatasetTreinamento.js --score-min 7 --incluir-parcial   # filtro mais frouxo
+   ```
+   Gera `training/data/train.jsonl`, `valid.jsonl` e `manifest.json` (rastreia
+   protocolos usados e filtros). Telefones são mascarados; mensagens do bot/sistema
+   ficam de fora; cada amostra começa no cliente e termina na resposta do atendente.
+
+3. **Treinar** (nesta máquina, Apple Silicon):
+   ```
+   ./treinar_lora_mlx.sh        # 3 épocas
+   ./treinar_lora_mlx.sh 2      # 2 épocas
+   ```
+   Usa `mlx-lm` com o modelo 4-bit (`mlx-community/Meta-Llama-3.1-8B-Instruct-4bit`),
+   batch 1, seq 4096, gradient checkpointing — cabe nos 16 GB do M4, mas feche apps
+   pesados e espere algumas horas com centenas de amostras. Acompanhe o `Val loss`:
+   se começar a subir enquanto o train loss cai, reduza as épocas (overfitting).
+
+   *Alternativa com GPU NVIDIA (mais rápido, datasets maiores):* os mesmos JSONL
+   funcionam no [unsloth](https://github.com/unslothai/unsloth) ou axolotl — treinar
+   lá, trazer o adapter PEFT e converter com `convert_lora_to_gguf.py` do llama.cpp.
+
+4. **Testar o adapter antes de publicar**:
+   ```
+   source .venv/bin/activate
+   mlx_lm.generate --model mlx-community/Meta-Llama-3.1-8B-Instruct-4bit \
+     --adapter-path adapters --max-tokens 200 \
+     --prompt "Boa tarde, minha internet caiu"
+   ```
+
+5. **Publicar no Ollama**: `./publicar_no_ollama.sh` — funde o LoRA, cria o modelo
+   `star-atendente` quantizado e remove o intermediário fp16 (~16 GB temporários em disco).
+
+6. **Validar e ativar**: compare respostas com o llama3.1 puro em perguntas típicas
+   de suporte. Para o Oráculo usar: `OLLAMA_CHAT_MODEL=star-atendente` no `backend/.env`.
+
+## Quando re-treinar
+
+O dataset cresce conforme o sync importa protocolos novos e o juiz os avalia.
+Re-exportar + re-treinar vale a pena a cada leva significativa de amostras novas
+(ex.: +30%). O `manifest.json` diz o que entrou em cada rodada.
+
+## Próximo estágio (futuro)
+
+Com volume maior, os scores viabilizam **DPO**: pares "atendimento nota alta" vs
+"nota baixa" para o mesmo tipo de problema, treinando por preferência em vez de
+imitação. Exige mais dados e curadoria — só faz sentido depois do SFT mostrar limite.

+ 52 - 0
training/avaliar-tudo.log

@@ -0,0 +1,52 @@
+[config] RATE_LIMIT_PER_MINUTE=0 — rate limiting desabilitado
+[avaliar-tudo] 820 pendentes, lotes de 25
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (340s) — restam 795
+[avaliar-tudo] +19 avaliados, 6 sem diálogo, 0 erros (317s) — restam 770
+[avaliar-tudo] +18 avaliados, 7 sem diálogo, 0 erros (283s) — restam 745
+[avaliar-tudo] +12 avaliados, 13 sem diálogo, 0 erros (172s) — restam 720
+[avaliar-tudo] +12 avaliados, 13 sem diálogo, 0 erros (153s) — restam 695
+[avaliar-tudo] +8 avaliados, 17 sem diálogo, 0 erros (163s) — restam 670
+[avaliar-tudo] +11 avaliados, 14 sem diálogo, 0 erros (251s) — restam 645
+[config] RATE_LIMIT_PER_MINUTE=0 — rate limiting desabilitado
+[avaliar-tudo] 676 pendentes, lotes de 25
+[avaliar-tudo] +21 avaliados, 4 sem diálogo, 0 erros (443s) — restam 651
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (473s) — restam 626
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (382s) — restam 608
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (506s) — restam 585
+[config] RATE_LIMIT_PER_MINUTE=0 — rate limiting desabilitado
+[avaliar-tudo] 561 pendentes, lotes de 25
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (330s) — restam 536
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (405s) — restam 524
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (423s) — restam 527
+[avaliar-tudo] +21 avaliados, 4 sem diálogo, 0 erros (397s) — restam 504
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (347s) — restam 479
+[avaliar-tudo] +18 avaliados, 7 sem diálogo, 0 erros (237s) — restam 454
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (373s) — restam 429
+[avaliar-tudo] +21 avaliados, 4 sem diálogo, 0 erros (371s) — restam 439
+[avaliar-tudo] +25 avaliados, 0 sem diálogo, 0 erros (450s) — restam 414
+[avaliar-tudo] +16 avaliados, 9 sem diálogo, 0 erros (204s) — restam 413
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (377s) — restam 388
+[avaliar-tudo] +25 avaliados, 0 sem diálogo, 0 erros (457s) — restam 363
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (439s) — restam 338
+[avaliar-tudo] +25 avaliados, 0 sem diálogo, 0 erros (408s) — restam 313
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (394s) — restam 320
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (432s) — restam 295
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (313s) — restam 289
+[avaliar-tudo] +21 avaliados, 4 sem diálogo, 0 erros (385s) — restam 264
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (384s) — restam 263
+[avaliar-tudo] +25 avaliados, 0 sem diálogo, 0 erros (349s) — restam 252
+[avaliar-tudo] +20 avaliados, 5 sem diálogo, 0 erros (296s) — restam 227
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (342s) — restam 201
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (363s) — restam 176
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (326s) — restam 151
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (443s) — restam 157
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (446s) — restam 149
+[avaliar-tudo] +19 avaliados, 6 sem diálogo, 0 erros (306s) — restam 124
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (325s) — restam 99
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (297s) — restam 88
+[avaliar-tudo] +15 avaliados, 10 sem diálogo, 0 erros (222s) — restam 78
+[avaliar-tudo] +23 avaliados, 2 sem diálogo, 0 erros (453s) — restam 53
+[avaliar-tudo] +22 avaliados, 3 sem diálogo, 0 erros (322s) — restam 46
+[avaliar-tudo] +24 avaliados, 1 sem diálogo, 0 erros (425s) — restam 21
+[avaliar-tudo] +19 avaliados, 2 sem diálogo, 0 erros (299s) — restam 0
+[avaliar-tudo] fila zerada

+ 191 - 0
training/data/manifest.json

@@ -0,0 +1,191 @@
+{
+  "geradoEm": "2026-07-09T20:26:57.651Z",
+  "filtro": {
+    "scoreMin": 8,
+    "resolvidos": [
+      "sim"
+    ],
+    "sentimentoExcluido": "negativo",
+    "maxChars": 16000
+  },
+  "train": 158,
+  "valid": 18,
+  "protocolos": [
+    "SUP0000014761/2026",
+    "ATE0000007799/2026",
+    "SUF0000000162/2025",
+    "FIN0000007047/2026",
+    "FIN0000007168/2026",
+    "FIN0000006783/2025",
+    "SUP0000011286/2026",
+    "FIN0000006898/2025",
+    "FIN0000007102/2026",
+    "VEN0000003056/2026",
+    "FIN0000006790/2025",
+    "FIN0000007085/2026",
+    "FIN0000007019/2026",
+    "SUP0000014404/2026",
+    "SUP0000011339/2026",
+    "ATE0000008008/2026",
+    "SUP0000014624/2026",
+    "SUP0000014435/2026",
+    "SUP0000010904/2026",
+    "FIN0000006878/2026",
+    "VEN0000003068/2026",
+    "SUP0000014741/2026",
+    "FIN0000006902/2026",
+    "VEN0000003050/2026",
+    "ATE0000007988/2026",
+    "SUP0000014822/2026",
+    "FIN0000007148/2026",
+    "FIN0000007190/2026",
+    "FIN0000006852/2026",
+    "FIN0000007150/2026",
+    "ATE0000007695/2026",
+    "FIN0000007046/2026",
+    "FIN0000006990/2026",
+    "SUP0000014808/2026",
+    "FIN0000006871/2026",
+    "ATE0000007685/2026",
+    "FIN0000007037/2026",
+    "SUP0000025560/2025",
+    "SUP0000014447/2026",
+    "SUP0000014735/2026",
+    "FIN0000006763/2025",
+    "SUP0000025875/2025",
+    "FIN0000006935/2026",
+    "FIN0000007070/2026",
+    "SUI0000000595/2026",
+    "POS0000014846/2025",
+    "ATE0000007993/2026",
+    "FIN0000006845/2026",
+    "SUP0000004376/2025",
+    "FIN0000007188/2026",
+    "SUP0000014343/2026",
+    "SUP0000014322/2026",
+    "FIN0000003317/2025",
+    "POS0000006180/2026",
+    "FIN0000007027/2026",
+    "SUP0000014832/2026",
+    "POS0000006082/2026",
+    "FIN0000006859/2026",
+    "FIN0000007145/2026",
+    "POS0000014841/2025",
+    "FIN0000007119/2026",
+    "ATE0000011401/2025",
+    "SUP0000014742/2026",
+    "SUP0000014551/2026",
+    "ATE0000007645/2026",
+    "FIN0000007098/2026",
+    "SUP0000014729/2026",
+    "SUP0000014707/2026",
+    "ATE0000007991/2026",
+    "FIN0000007174/2026",
+    "SUP0000014307/2026",
+    "FIN0000006973/2026",
+    "ATE0000007838/2026",
+    "FIN0000007056/2026",
+    "FIN0000007058/2026",
+    "FIN0000006887/2026",
+    "SUP0000014731/2026",
+    "ATE0000011406/2025",
+    "FIN0000006827/2026",
+    "FIN0000006904/2025",
+    "SUP0000014734/2026",
+    "SUP0000025868/2025",
+    "ATE0000007971/2026",
+    "SUP0000014041/2026",
+    "FIN0000006861/2026",
+    "SUP0000014233/2026",
+    "SUP0000014115/2026",
+    "FIN0000006881/2026",
+    "FIN0000006815/2025",
+    "SUP0000014318/2026",
+    "SUP0000014777/2026",
+    "FIN0000007132/2026",
+    "SUP0000014836/2026",
+    "SUP0000014771/2026",
+    "SUP0000014179/2026",
+    "SUP0000011405/2026",
+    "ATE0000007958/2026",
+    "ATE0000008001/2026",
+    "ATE0000007853/2026",
+    "FIN0000006865/2026",
+    "SUP0000011452/2026",
+    "FIN0000006814/2025",
+    "ATE0000007955/2026",
+    "FIN0000006615/2025",
+    "FIN0000006844/2026",
+    "FIN0000006897/2025",
+    "SUP0000014499/2026",
+    "VEN0000003085/2026",
+    "SUP0000014579/2026",
+    "FIN0000007045/2026",
+    "ATE0000007987/2026",
+    "VEN0000003061/2026",
+    "FIN0000006773/2025",
+    "FIN0000007173/2026",
+    "SUP0000014274/2026",
+    "SUP0000014543/2026",
+    "FIN0000006936/2026",
+    "FIN0000007008/2026",
+    "FIN0000006952/2026",
+    "SUP0000014749/2026",
+    "POS0000014849/2025",
+    "FIN0000007114/2026",
+    "SUP0000025823/2025",
+    "FIN0000003066/2025",
+    "FIN0000006885/2026",
+    "POS0000008459/2025",
+    "SUP0000013642/2026",
+    "SUP0000014712/2026",
+    "SUP0000014762/2026",
+    "FIN0000007124/2026",
+    "FIN0000006872/2026",
+    "SUI0000000611/2026",
+    "FIN0000003698/2026",
+    "FIN0000007094/2026",
+    "ATE0000007921/2026",
+    "SUP0000014755/2026",
+    "SUP0000014518/2026",
+    "SUP0000014323/2026",
+    "FIN0000006977/2026",
+    "SUP0000011731/2024",
+    "SUP0000014779/2026",
+    "SUP0000013994/2026",
+    "ATE0000008003/2026",
+    "ATE0000007728/2026",
+    "SUP0000014039/2026",
+    "ATE0000008009/2026",
+    "FIN0000007028/2026",
+    "FIN0000006853/2026",
+    "FIN0000006843/2025",
+    "SUP0000014817/2026",
+    "ATE0000007981/2026",
+    "FIN0000007179/2026",
+    "ATE0000011008/2025",
+    "FIN0000007192/2026",
+    "SUP0000014100/2026",
+    "FIN0000006948/2026",
+    "POS0000006159/2026",
+    "SUP0000014718/2026",
+    "SUP0000014745/2026",
+    "FIN0000006817/2025",
+    "FIN0000006782/2026",
+    "SUP0000014173/2026",
+    "FIN0000007096/2026",
+    "SUP0000014642/2026",
+    "ATE0000008007/2026",
+    "FIN0000006851/2026",
+    "SUP0000013963/2026",
+    "FIN0000006674/2025",
+    "SUP0000025912/2025",
+    "PLA0000000972/2026",
+    "FIN0000007118/2026",
+    "FIN0000007152/2026",
+    "FIN0000007126/2026",
+    "SUP0000014785/2026",
+    "FIN0000007011/2026",
+    "SUP0000014556/2026"
+  ]
+}

File diff suppressed because it is too large
+ 0 - 0
training/data/train.jsonl


File diff suppressed because it is too large
+ 2 - 0
training/data/valid.jsonl


+ 46 - 0
training/publicar_no_ollama.sh

@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# Funde o adapter LoRA treinado no modelo base (fp16), importa no Ollama como
+# "star-atendente" (quantizado q4_K_M) e limpa o intermediário.
+#
+# Pré-requisito: ./treinar_lora_mlx.sh concluído (adapters/) e Ollama rodando.
+set -euo pipefail
+cd "$(dirname "$0")"
+
+MODEL="mlx-community/Meta-Llama-3.1-8B-Instruct-4bit"
+ADAPTER_DIR="adapters"
+FUSED_DIR="fused"
+NOME_OLLAMA="star-atendente"
+
+if [[ ! -f "$ADAPTER_DIR/adapters.safetensors" ]]; then
+  echo "ERRO: adapter não encontrado em $ADAPTER_DIR/. Rode ./treinar_lora_mlx.sh antes." >&2
+  exit 1
+fi
+
+source .venv/bin/activate
+
+echo "==> Fundindo adapter no modelo base (gera fp16 em $FUSED_DIR/, ~16 GB em disco)"
+mlx_lm.fuse \
+  --model "$MODEL" \
+  --adapter-path "$ADAPTER_DIR" \
+  --save-path "$FUSED_DIR" \
+  --de-quantize
+
+echo "==> Montando Modelfile (template herdado do llama3.1 instalado no Ollama)"
+TEMPLATE=$(ollama show llama3.1 --template)
+cat > Modelfile <<EOF
+FROM ./$FUSED_DIR
+TEMPLATE """$TEMPLATE"""
+SYSTEM """Você é um atendente da Star Internet, atendendo clientes pelo WhatsApp. Seja cordial, objetivo e resolutivo: entenda o problema, faça as perguntas necessárias, explique os passos com clareza e conduza o atendimento até a solução ou encaminhamento correto. Responda em português brasileiro."""
+PARAMETER temperature 0.3
+PARAMETER num_ctx 8192
+EOF
+
+echo "==> Importando no Ollama como $NOME_OLLAMA (quantizando q4_K_M)"
+ollama create "$NOME_OLLAMA" -f Modelfile --quantize q4_K_M
+
+echo "==> Limpando fp16 intermediário"
+rm -rf "$FUSED_DIR"
+
+echo
+echo "Pronto. Teste com:  ollama run $NOME_OLLAMA 'Boa tarde, minha internet está lenta'"
+echo "Para o backend usar no chat: OLLAMA_CHAT_MODEL=$NOME_OLLAMA no backend/.env"

+ 48 - 0
training/treinar_lora_mlx.sh

@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+# Treina um LoRA do Llama-3.1-8B-Instruct com os atendimentos exportados (estágio 2),
+# usando MLX no Apple Silicon. Ajustado para 16 GB de RAM (M4): modelo 4-bit, batch 1.
+#
+# Pré-requisito: dataset exportado (backend: node scripts/exportarDatasetTreinamento.js)
+# Uso: ./treinar_lora_mlx.sh [epocas]   (default 3)
+set -euo pipefail
+cd "$(dirname "$0")"
+
+MODEL="mlx-community/Meta-Llama-3.1-8B-Instruct-4bit"
+DATA_DIR="data"
+ADAPTER_DIR="adapters"
+EPOCAS="${1:-3}"
+
+if [[ ! -f "$DATA_DIR/train.jsonl" ]]; then
+  echo "ERRO: $DATA_DIR/train.jsonl não existe. Rode antes: cd ../backend && node scripts/exportarDatasetTreinamento.js" >&2
+  exit 1
+fi
+
+if [[ ! -d .venv ]]; then
+  python3 -m venv .venv
+fi
+source .venv/bin/activate
+pip -q install --upgrade mlx-lm
+
+N_TRAIN=$(wc -l < "$DATA_DIR/train.jsonl" | tr -d ' ')
+ITERS=$(( N_TRAIN * EPOCAS ))
+if (( ITERS < 100 )); then ITERS=100; fi
+
+echo "Treinando: $N_TRAIN amostras, $EPOCAS épocas => $ITERS iterações (batch 1)"
+
+mlx_lm.lora \
+  --model "$MODEL" \
+  --train \
+  --data "$DATA_DIR" \
+  --adapter-path "$ADAPTER_DIR" \
+  --batch-size 1 \
+  --num-layers 16 \
+  --iters "$ITERS" \
+  --max-seq-length 4096 \
+  --grad-checkpoint \
+  --save-every 100 \
+  --steps-per-eval 100
+
+echo
+echo "Adapter salvo em $ADAPTER_DIR/. Teste rápido:"
+echo "  mlx_lm.generate --model $MODEL --adapter-path $ADAPTER_DIR --prompt 'Boa tarde, minha internet caiu'"
+echo "Para publicar no Ollama: ./publicar_no_ollama.sh"

Some files were not shown because too many files changed in this diff