Por que “parece certo” não é um teste para um prompt
Você aperta a redação de um prompt que roteia tickets, testa nos três exemplos abertos no seu notebook de rascunho e coloca no ar. Uma semana depois, reclamações de cobrança que antes caíam na fila certa começam a cair em “pergunta”. Não os três casos que você checou: os outros quatrocentos que você não checou. Nada quebra. Não há stack trace. O prompt, lido em voz alta, ainda parece bom.
A revisão de código e algumas checagens manuais não pegam isso porque o mesmo prompt com o mesmo input não devolve o mesmo texto duas vezes. Você não pode comparar duas execuções byte a byte e chamar uma diferença de bug, como faria com uma função pura. O que você pode checar é se cada saída tem as propriedades que importam: a categoria certa, um resumo dentro do limite de tamanho, nenhum campo que a fonte nunca mencionou. Um eval roda essas checagens sobre um conjunto de inputs reais sempre que o prompt ou o modelo muda, então a regressão aparece no seu terminal em vez de no ticket de um cliente.
Quais são as partes de um LLM eval
Todo eval, seja qual for a ferramenta que o executa, tem três partes: um dataset de inputs representativos, uma assertion que checa cada saída contra o que aquele input deveria produzir, e uma taxa de aprovação que você acompanha de uma execução para outra.
Pense nisso como a suíte de testes de uma função que devolve uma frase ligeiramente diferente a cada vez. A assertion não pode checar uma correspondência exata, então checa uma propriedade da frase: se ela nomeia a categoria certa, se o número dentro dela dá para parsear. Uma vez que você aceita que o teste é “isso vale” e não “isso é idêntico”, escrever um é engenharia comum.
Como construir um dataset golden para um LLM eval
Pegue um prompt que transforma um e-mail de suporte bruto em um ticket estruturado:
| |
O dataset golden é um conjunto de e-mails reais pareados com a resposta que você já sabe estar correta. Reais, não inventados para fazer o prompt parecer bom:
| |
De cinco a dez casos bastam para começar. O tamanho importa menos do que de onde vêm os casos seguintes: falhas reais de produção.
Como escrever assertions determinísticas para a saída de um LLM
A maior parte do que dá errado nesse prompt não tem nada a ver com gosto. O modelo devolve JSON malformado, ou inventa uma quarta categoria, ou escreve quarenta palavras quando você pediu doze. Código comum pega tudo isso, sem precisar de um segundo modelo:
| |
Rode isso sobre todo o dataset e você tem uma taxa de aprovação, não um simples sim ou não:
| |
Essas checagens não custam nada e nunca falham de forma aleatória: a mesma saída ruim falha do mesmo jeito sempre. Escreva-as primeiro. Elas pegam a maioria das regressões reais, como uma mudança de schema ou um resumo que silenciosamente dobrou de tamanho.
Quando usar LLM-as-judge e como calibrá-lo
Categoria e urgência são objetivas, e o dataset já traz a resposta certa. A qualidade do resumo não. “Reembolso necessário, cobrança duplicada” e “Cliente tem uma pergunta de cobrança” são ambos JSON válido, ambos com menos de 12 palavras, e só um dos dois descreve de fato o e-mail. Nenhuma regex pega isso, então um segundo modelo avalia isso contra uma rubrica:
| |
Antes de confiar nesse veredito sobre o dataset inteiro, rode o judge em 15-20 casos que uma pessoa já avaliou manualmente e conte com que frequência eles concordam. Um judge que discorda das pessoas uma vez em cada três é uma assertion fraca disfarçada de autoridade de modelo.
Dois vieses pesam contra confiar demais nesse veredito: judges tendem a avaliar melhor respostas mais longas e elaboradas mesmo quando uma mais curta é igualmente correta, e um modelo que avalia suas próprias saídas as avalia de forma mais favorável do que um modelo independente faria. Use um modelo diferente como judge quando possível, e mantenha o prompt estreito: uma pergunta sim/não por chamada, não cinco notas em uma única requisição.
| Assertion determinística | LLM-as-judge | |
|---|---|---|
| Custo por caso | Grátis | Uma chamada extra ao modelo |
| O que pega | Schema, formato, fatos já presentes no dataset | Qualidade subjetiva, relevância, tom |
| Repetibilidade | Mesma saída, mesmo veredito | Alguma variação mesmo em temperatura 0 |
| Custo de configuração | Escrever a checagem e pronto | Precisa de calibração prévia com exemplos avaliados por humanos |
Em termos de suíte de testes, as checagens determinísticas funcionam como testes unitários: rodam em toda mudança sem adicionar custo, e o veredito nunca varia entre execuções. O judge se aproxima mais de uma checagem de aceitação, a que uma pessoa faria lendo cada saída. As trocas entre esses níveis são as mesmas tratadas em teste unitário vs teste de integração vs end-to-end.
Como rodar LLM evals na CI para pegar regressões
Um eval que só roda quando alguém se lembra não pega nada. Coloque o script junto do prompt que ele testa (evals/triage.py ao lado de prompts/triage.py) e rode na CI a cada mudança em qualquer um dos dois arquivos, antes do merge. Acompanhe a taxa de aprovação ao longo do tempo e falhe o build quando ela cair abaixo de uma baseline que você definiu. Uma queda de 95% para 88% é uma regressão que vale bloquear, mesmo que a maioria dos casos ainda passe.
Mantenha um conjunto de regressão dentro do dataset: todo caso que já foi um bug reportado, fixado permanentemente depois da correção. Para um prompt, isso te dá o ciclo descrito em o que o TDD realmente é. Mude o prompt, rode a suíte de novo, veja exatamente quais assertions viraram, corrija, repita. Alimente o dataset com as novas falhas de produção conforme elas acontecem, para não testar apenas os inputs para os quais o prompt foi projetado.
Como avaliar pipelines de tool calling e RAG
As mesmas três partes se aplicam aqui também. Para um ciclo de tool calling, o dataset pareia cada input com o nome correto da ferramenta e os argumentos corretos, e a assertion checa se o modelo escolheu essa ferramenta com os campos obrigatórios preenchidos. A mecânica do próprio ciclo está em function calling e tool use em LLMs.
Um pipeline RAG precisa de duas notas separadas. O retrieval trouxe o chunk que contém a resposta? A geração usou esse chunk corretamente? Se você avaliar isso junto, não dá para saber qual das duas metades quebrou. Veja como o RAG funciona para o pipeline que você está dividindo em dois.
Erros comuns com LLM evals
Construir o dataset com casos que já passam. Cinco exemplos escolhidos porque o prompt lida bem com eles vão passar para sempre e não vão te dizer nada sobre o input que vai quebrar tudo no mês que vem. Toda falha real que chegar até você deve entrar no dataset no mesmo dia.
Checar correspondência exata numa saída variável. summary == "Reembolso necessário por cobrança duplicada" falha no momento em que o modelo troca uma única palavra, mesmo quando a nova frase é igualmente correta. Cheque a propriedade, não o texto: a contagem de palavras, a categoria, se um fato obrigatório aparece.
Pular a calibração do judge. Um judge que você nunca comparou nem com uma dúzia de exemplos avaliados por humanos entrega os próprios vieses como se fossem verdade, incluindo o que favorece a própria saída.
Rodar de novo só o caso que você acabou de corrigir. É assim que um bug antigo volta sem ninguém notar. O conjunto de regressão existe exatamente para isso não acontecer.
Quando não vale a pena construir uma suíte de LLM eval
Um prompt que você vai rodar só uma vez, ou que uma pessoa revisa em cada chamada, não precisa de um harness. Coloque a checagem de validade do JSON inline se for barata e siga adiante. A suíte se paga quando um prompt vai para produção com usuários reais repetidamente ou você espera ajustá-lo mais de algumas vezes. É nesse momento que “pareceu bom nos meus três exemplos” deixa de ser suficiente.
Para começar: cinco inputs reais, uma assertion determinística para cada propriedade que importa, e um judge LLM só para a dimensão que o código não consegue checar. Rode antes do seu próximo ajuste de prompt, não depois da reclamação.