Existe um padrão que se repete em todo projeto de IA que dá errado. Alguém mexe no prompt, testa com três perguntas, acha que melhorou e sobe. Duas semanas depois o sistema está pior num aspecto que ninguém estava olhando, e não há como saber em qual das onze mudanças isso aconteceu.
A cura é modesta: trinta casos, um script, um número. Leva meio dia para montar e transforma toda discussão de qualidade em comparação de duas colunas.
Comece pelos trinta casos
Não é preciso um conjunto de mil. Trinta casos bem escolhidos pegam quase toda regressão relevante. O que faz um bom conjunto:
- Casos reais, tirados do que os usuários realmente pediram — não do que você imagina que eles pedirão.
- Os difíceis incluídos: a pergunta ambígua, a que não tem resposta no seu acervo, a que tenta te enganar.
- Um caso por tipo de erro que você já viu acontecer. Toda vez que um bug aparecer em produção, ele vira caso 31, 32, 33.
- A resposta esperada anotada — nem que seja em forma de "precisa mencionar X e não pode dizer Y".
A melhor forma de chegar a cem casos não é sentar e escrever cem. É: toda vez que alguém reclamar de uma resposta, aquela pergunta entra no conjunto com a resposta certa anotada.
Em três meses você tem um conjunto que representa exatamente os seus problemas reais — e nenhuma regressão antiga volta sem ser detectada.
Três formas de pontuar, da mais confiável à menos
1. Verificação exata — use sempre que der
Quando a resposta certa é verificável por código, não envolva modelo nenhum no julgamento. Classificação em categorias, extração de campos, saída em JSON, cálculo numérico, "contém este código de produto".
É rápido, é de graça, é determinístico e não tem opinião. Se 60% do seu conjunto pode ser avaliado assim, avalie assim.
2. Verificação por regra — o meio-termo subestimado
Para resposta em texto livre, muita coisa ainda dá para checar com regra:
def checar(resposta, caso):
ok = []
ok.append(("cita_fonte", "[doc" in resposta))
ok.append(("tem_termo", all(t in resposta.lower() for t in caso["deve_conter"])))
ok.append(("sem_proibido", not any(t in resposta.lower() for t in caso["nao_pode"])))
ok.append(("tamanho", len(resposta.split()) <= caso.get("max_palavras", 400)))
ok.append(("sem_invencao", "não sei" in resposta.lower()
if caso["sem_resposta_no_acervo"] else True))
return dict(ok)
A última linha é a mais valiosa de todas. Coloque no conjunto uns cinco casos cuja resposta não existe no seu acervo e exija que o sistema diga que não sabe. É o teste que pega alucinação — e é o que quase ninguém faz.
3. Modelo-juiz — para o que sobrou
Para "a resposta está boa?", em que não há regra, use um modelo avaliando o outro. Funciona bem, com ressalvas importantes.
JUIZ = """Você avalia respostas de um assistente.
PERGUNTA: {pergunta}
RESPOSTA ESPERADA (referência): {esperada}
RESPOSTA DO SISTEMA: {obtida}
Avalie de 1 a 5:
- 5: correta e completa
- 4: correta, faltou detalhe secundário
- 3: parcialmente correta
- 2: majoritariamente errada
- 1: errada ou inventada
Responda em JSON: {{"nota": N, "motivo": "uma frase"}}"""
Prefere resposta longa. Entre duas corretas, ele dá nota maior à mais extensa. Se você está justamente tentando encurtar as respostas, o juiz vai te penalizar por acertar.
Prefere o próprio estilo. Um modelo tende a dar nota melhor a textos parecidos com os que ele produz. Use um modelo de família diferente da que gerou a resposta.
É frouxo com número. Ele raramente confere se o valor citado bate com a referência. Cheque números com regra, nunca com juiz.
O script
import json, statistics
from concurrent.futures import ThreadPoolExecutor
casos = json.load(open("casos.json", encoding="utf-8"))
def avaliar_um(caso):
resposta = meu_sistema(caso["pergunta"]) # o SEU sistema, como está
regras = checar(resposta, caso)
nota_juiz = julgar(caso, resposta) if caso.get("usa_juiz") else None
return {
"id": caso["id"],
"passou_regras": all(regras.values()),
"regras": regras,
"nota": nota_juiz,
"resposta": resposta,
}
with ThreadPoolExecutor(max_workers=8) as ex:
r = list(ex.map(avaliar_um, casos))
taxa = sum(x["passou_regras"] for x in r) / len(r)
notas = [x["nota"] for x in r if x["nota"] is not None]
print(f"regras: {taxa:.1%}")
print(f"juiz: {statistics.mean(notas):.2f}" if notas else "")
json.dump(r, open("resultado.json", "w", encoding="utf-8"),
ensure_ascii=False, indent=2)
for x in r: # o que interessa de verdade
if not x["passou_regras"]:
print(f"✗ {x['id']}: {[k for k,v in x['regras'].items() if not v]}")
Vinte linhas. Rode antes e depois de cada mudança e guarde o resultado.json com o nome da versão. É todo o processo.
Como ler o resultado sem se enganar
Com trinta casos, cada um vale 3,3 pontos percentuais. Isso significa que uma diferença de 3% é um caso — ou seja, ruído. Não comemore, não reverta.
| Diferença observada | Com 30 casos | Com 100 casos |
|---|---|---|
| 1 a 3 pontos | Ruído. Ignore. | Ruído. Ignore. |
| 4 a 8 pontos | Talvez. Olhe os casos que mudaram. | Provavelmente real. |
| Acima de 10 pontos | Real. | Real e relevante. |
E sempre abra os casos que mudaram de resultado. Duas mudanças que se anulam na média podem esconder "consertei cinco e quebrei cinco outros" — que não é empate, é troca de problema.
Fixe a temperatura em zero na avaliação. Com temperatura ligada, você vai medir sorte junto com qualidade e nunca vai saber a proporção.
O custo
Uma rodada de 30 casos, com o sistema respondendo e o juiz avaliando:
- Respostas: 30 × ~1.500 tokens de contexto e 400 de saída ≈ R$ 0,04
- Julgamento: 30 × ~900 tokens de entrada e 60 de saída ≈ R$ 0,02
- Menos de dez centavos por rodada, com modelos de entrada a R$ 0,49 por milhão
Barato o bastante para rodar a cada alteração, e não só antes de subir. Quem roda só na véspera do lançamento perdeu o principal benefício, que é descobrir a regressão no dia em que ela foi introduzida.
Rode a avaliação a cada mudança
API por token cobrada em reais, sem assinatura — uma rodada completa custa centavos e você paga só o que usar.
Criar conta →Erros comuns na hora de montar
- Usar só casos fáceis. Dá 98% na primeira rodada, nunca mais muda e não detecta nada. Se o seu conjunto não tem casos que falham hoje, ele é decorativo.
- Deixar o conjunto vazar para o prompt. Se você ajusta o prompt olhando os 30 casos até acertar todos, você não tem mais avaliação — tem gabarito decorado. Mantenha 10 casos que você não olha durante o ajuste.
- Medir só o final. Num sistema com recuperação de documentos, meça também o recall da busca separadamente. Quando a nota final cai, você precisa saber se foi a busca ou a geração.
- Avaliar sem custo e latência na mesma tabela. Um modelo 2 pontos melhor e três vezes mais caro pode não valer. Registre as três colunas juntas.
Conclusão
Avaliação de IA não precisa ser um projeto. Trinta casos reais, verificação por regra no que dá, modelo-juiz no resto, e um script que imprime um número e a lista de falhas.
O ganho não é técnico, é organizacional: a discussão sai de "eu acho que ficou melhor" para "84% contra 71%, e estes cinco casos regrediram". A partir daí, todo mundo para de discutir e vai olhar os cinco casos.
Continue: ajustar com LoRA · escolher embeddings · observabilidade