|
|
преди 1 месец | |
|---|---|---|
| .. | ||
| data | преди 1 месец | |
| README.md | преди 1 месец | |
| publicar_no_ollama.sh | преди 3 месеца | |
| treinar_lora_mlx.sh | преди 1 месец | |
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.
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
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).
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 — protocolosTrain/protocolosValid, por atendimento — 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.
Modo de amostragem (--modo) — importa porque o mlx-lm com --mask-prompt só
calcula loss no último turno de cada amostra:
por-turno (default, ciclo 5+): cada atendimento gera várias amostras, cada uma
terminando num turno diferente do atendente (prefixo da conversa até ali). Sorteia até
--alvos-por-atendimento (2) turnos com >= --min-palavras-alvo (8) palavras, mais
acks/fechamentos curtos limitados a --frac-triviais (0,15) do total, pra o modelo
ainda saber fechar conversa. --max-alvo-repetido (30) limita quantas vezes um mesmo
texto de atendente ("Vou transferir seu atendimento…") pode ser alvo. O split
treino/validação é por atendimento, então não há vazamento.ultimo-turno (ciclos 1-4): 1 amostra por atendimento terminando no último turno.
Nos ciclos 1-4 isso fazia o alvo ser quase sempre o fechamento ("Disponha!", mediana
de 7 palavras, 43,6% com <=5) e o modelo aprendia a fechar conversa em vez de resolver.
Mantido só pra reproduzir os datasets antigos.Com por-turno o dataset tem ~2x mais amostras que o ultimo-turno: prefira 1 época
(./treinar_lora_mlx.sh 1) pra manter o tempo de treino próximo ao dos ciclos anteriores.
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 2048, 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).
O script roda automaticamente, antes do treino:
filtrar_por_tokens.py — descarta amostras cujo tamanho real em tokens
(tokenizer do próprio modelo) passa de 1600. exportarDatasetTreinamento.js
só filtra por caracteres (--max-chars), que não é proxy confiável de
tokens em pt-BR; uma amostra de 14-18 mil caracteres pode passar de 4000
tokens — bem acima do --max-seq-length de 2048 — e isso já causou OOM
de GPU (e reinício da máquina) durante a validação em 04/08.run_lora_seguro.py, que capa o memory_limit/wired_limit
do MLX em 9 GiB antes de carregar o modelo. Por padrão o MLX permite até
1.5x o working set recomendado da GPU (~19 GiB neste M4), acima da RAM
física (16 GiB) — sem teto explícito, um pico de memória derruba o sistema
inteiro em vez de estourar uma exceção Python capturável.Alternativa com GPU NVIDIA (mais rápido, datasets maiores): os mesmos JSONL
funcionam no unsloth ou axolotl — treinar
lá, trazer o adapter PEFT e converter com convert_lora_to_gguf.py do llama.cpp.
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"
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).
Validar e ativar: compare respostas com o llama3.1 puro em perguntas típicas
de suporte. O backend já usa star-atendente por default
(config.atendenteSugestao.model, na feature de sugestão de resposta ao
atendente) — só é preciso setar OLLAMA_ATENDENTE_MODEL=star-atendente no
backend/.env se o modelo for publicado com outro nome. Não confundir com
OLLAMA_CHAT_MODEL, que é o modelo do chat RAG geral.
eval/) — rode antes de publicar qualquer modelo ou mudar o prompt da sugestãoHarness com 30 cenários fictícios (eval/cenarios.json), sem dado de cliente:
# modelo MLX (base, ciclos, checkpoints) — da pasta training/, com a .venv
HF_HUB_OFFLINE=1 .venv/bin/python eval/gerar_respostas.py --variantes base ciclo5 ciclo5@1400 --temp 0.3 --n-amostras 5 --saida eval/out/x.json
# caminho de produção (Ollama + atendenteSugestaoService) — da pasta backend/
node training/eval/gerar_ollama.mjs --n 3 --saida training/eval/out/y.json \
--variante atual=prompt:atual,rag:nenhum,guard:1 --variante antiga=prompt:producao_v0.txt,rag:avaliacao,guard:0
# julgar (juiz llama3.1 local + regex de src/services/atendenteSugestaoGuardrails.js)
node training/eval/juiz.mjs training/eval/out/y.json --saida training/eval/out/y.julgado.json
node training/eval/juiz.mjs --calibrar # concordância do juiz com eval/vereditos_manuais.json
Cuidados de leitura: o juiz erra a fronteira adequada/parcial (69% de concordância exata com o manual, 88% em
3 níveis útil/fraca/afirma_falso) e tem falsos positivos de afirma_falso (recusas corretas); confira
manualmente os casos marcados. Com n≈90 por variante, diferenças de poucos pontos são ruído. ciclo5@N é o
arquivo numerado N da 2ª tentativa: iteração global = N + 1400. Resultados de 25/09/2026: o ciclo 5 ficou em
33% de respostas úteis contra 79% da base; nenhum checkpoint chegou perto da base.
Sugestão de resposta em produção (desde 25/09/2026): base llama3.1, não o LoRA. Prompt com regras
anti-invenção, guardrail pós-geração e RAG desligado (ATENDENTE_SUGESTAO_RAG_TIPO=nenhum; o RAG de avaliação
elevava as afirmações inventadas de ~3% para ~10%). Chaves: ATENDENTE_SUGESTAO_GUARDRAIL,
ATENDENTE_SUGESTAO_RAG_TIPO (nenhum|avaliacao|conversa), ATENDENTE_SUGESTAO_RAG_ENABLED.
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.
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.