Como criar evals para agentes de IA com LLM-as-a-judge
"Tá funcionando bem" não é métrica.
Você subiu o agente, rodou três prompts na mão, viu que respondeu bonito e mandou pra produção. Aí o cliente abre um ticket que você nunca testou, o agente chama a ferramenta errada, inventa um dado e ninguém percebe até o usuário reclamar. Sem eval, todo deploy é no escuro.
Evals para agentes de IA são o conjunto de testes automatizados que medem se ele faz o que deveria. A diferença pro teste tradicional é que a saída não é determinística: o mesmo input pode gerar textos diferentes, e "certo" muitas vezes é julgamento, não comparação exata de string. É aí que entra o LLM-as-a-judge — usar um modelo para avaliar a saída de outro, contra uma rubrica que você escreve.
Neste tutorial você vai montar um pipeline de eval do zero: dataset de casos reais, critérios de avaliação, um juiz LLM que pontua com rubrica, agregação de score com barra de erro e um gate no CI que barra deploy quando o agente regride. Tudo em Python, com a API da Anthropic.
Este post é o hub de evals aqui do blog: ele cobre o pipeline em código. Se o que você precisa é a camada de processo — o que medir em cada etapa (recuperação, decisão de tool, resposta) e como não se enganar com o próprio número —, leia junto avaliação de agentes de IA: como montar evals honestos.
TL;DR
- O que é: pipeline de avaliação que mede se seu agente faz o que deveria, usando um LLM como avaliador onde regra fixa não dá conta.
- Stack: Python 3.11+, SDK
anthropic,pytestpro gate de CI. - Custo/Acesso: requer chave de API paga (Anthropic/OpenAI/etc.). O juiz é uma chamada extra por caso — barata, mas não zero.
- Ponto-chave: o juiz LLM precisa ser calibrado contra julgamento humano antes de você confiar nele. Sem isso, você só trocou um chute por outro.
O contexto — por que evals para agentes de IA viraram pré-requisito
Modelo bom não falta. O que separa um agente que aguenta produção de um que quebra no primeiro caso estranho é saber medir se ele funciona — e medir de novo a cada mudança de prompt, modelo ou ferramenta.
A própria Anthropic é direta sobre isso no guia de engenharia Demystifying evals for AI agents: comece com "20 a 50 tarefas simples tiradas de falhas reais". Não invente casos sintéticos bonitos. Pega os bugs que você já viu, as coisas que você confere na mão antes de cada release, as tarefas que usuários de verdade tentam.
O critério de uma boa tarefa de eval é cirúrgico: "uma boa tarefa é aquela em que dois especialistas do domínio chegariam, de forma independente, ao mesmo veredito de passou/falhou". Se dois devs sêniores discordam se a saída tá certa, o problema não é o agente — é a tarefa, que tá ambígua demais pra virar teste.
E tem uma armadilha de método que quase todo mundo cai: olhar o score e parar ali. Não vale. "Não leve scores de eval ao pé da letra até alguém abrir os detalhes do eval e ler algumas transcrições." Score de 87% sem ninguém ter lido transcrição é número de PowerPoint, não sinal de engenharia.
Pré-requisitos
Antes de escrever a primeira linha:
- [ ] Chave de API de um provedor de LLM (aqui uso a da Anthropic).
- [ ]
pip install anthropice Python 3.11+. - [ ] Um agente de IA — ou pelo menos uma função — que recebe input e devolve saída. Pode ser um wrapper simples de
messages.createcom tools. - [ ] Uns 10 a 20 casos reais de falha anotados na mão. Esse é o ativo mais valioso do pipeline. Sem dataset, não tem eval.
Se você ainda não tem nada anotado, separa meia hora e revisa as últimas conversas do agente. Os casos onde ele errou são seu dataset inicial.
Mão na massa — montando o pipeline
Passo 1: O dataset (golden set)
O dataset é uma lista de casos. Cada caso tem o input, a referência do que seria uma boa resposta e os metadados que você quer cruzar depois (categoria, dificuldade, se é um caso onde o comportamento deveria ou não deveria acontecer).
# dataset.py
golden_set = [
{
"id": "refund-001",
"input": "Comprei errado, quero reembolso de um pedido de 3 dias atrás.",
"reference": "Confirmar elegibilidade (janela de 7 dias), pedir nº do pedido, "
"explicar o prazo de estorno. NÃO prometer reembolso antes de validar.",
"category": "reembolso",
"should_use_tool": "lookup_order",
},
{
"id": "refund-002",
"input": "Quero reembolso de uma compra de 6 meses atrás.",
"reference": "Negar com educação: fora da janela de 7 dias. Oferecer alternativa.",
"category": "reembolso",
"should_use_tool": "lookup_order",
},
# ... mais 18 casos tirados de falhas reais
]
Repara no refund-002: ele testa o caso onde o agente não deve reembolsar. A Anthropic insiste nisso — teste "tanto os casos onde um comportamento deveria ocorrer quanto onde não deveria", senão você otimiza pra um lado só e o agente vira um "sim" ambulante.
Passo 2: Critérios — code-based primeiro, LLM depois
Nem tudo precisa de juiz LLM. A regra de ouro: use o método mais rápido e confiável que resolve. Existem três tipos de grader, e eles têm trade-offs bem definidos:
- Code-based (string match, schema, inspeção de tool call): rápido, barato, reproduzível. Quebra em variações válidas.
- LLM-as-a-judge (rubrica, comparação): flexível, captura nuance, escala. É não-determinístico e precisa de calibração.
- Humano: padrão-ouro, e lento e caro. Use pra calibrar e pra casos de alta aposta, não pro grosso.
Então a primeira camada é código puro. Coisas binárias e objetivas não merecem uma chamada de API:
# checks.py
def chamou_ferramenta_certa(trajectory, expected_tool):
"""Checagem determinística sobre a trajetória do agente."""
tools_usadas = [step["name"] for step in trajectory if step["type"] == "tool_use"]
return expected_tool in tools_usadas
def nao_prometeu_antes_de_validar(output, trajectory):
validou = any(s["name"] == "lookup_order" for s in trajectory if s["type"] == "tool_use")
prometeu = "reembolso aprovado" in output.lower()
return not (prometeu and not validou)
Isso roda em 100% dos casos a custo praticamente zero e pega os erros mais grosseiros — schema quebrado, ferramenta errada, alucinação óbvia. Só o que sobra dessa peneira vai pro juiz LLM.
Passo 3: O juiz LLM com rubrica
Aqui mora a parte que mais gente faz errado. Três regras que mudam o jogo:
Uma rubrica por dimensão. Não peça pro juiz avaliar "qualidade geral". A Anthropic recomenda "criar rubricas claras e estruturadas para cada dimensão da tarefa, e avaliar cada dimensão com um LLM-as-a-judge isolado, em vez de usar um só pra avaliar todas". Tom, correção factual e aderência ao processo são juízes separados.
Faça o juiz raciocinar antes de pontuar — e descarte o raciocínio. "Peça ao LLM para pensar primeiro antes de decidir a nota, e então descarte o raciocínio. Isso aumenta a performance da avaliação, em especial em tarefas de julgamento complexo."
Dê uma saída de emergência. "Dê ao LLM um jeito de escapar, como a instrução de retornar 'Unknown' quando ele não tem informação suficiente." Juiz encurralado inventa veredito.
O prompt do juiz, seguindo o padrão da doc oficial de evals da Anthropic:
# judge.py
import anthropic
client = anthropic.Anthropic()
JUDGE_PROMPT = """Você é um avaliador rigoroso. Avalie a RESPOSTA do agente
contra a RUBRICA de uma única dimensão.
<rubrica>{rubric}</rubrica>
<input_do_usuario>{user_input}</input_do_usuario>
<resposta>{output}</resposta>
Pense no seu raciocínio dentro de <thinking>. Se não houver informação
suficiente pra decidir, responda 'unknown'. Depois, emita exatamente uma
palavra em <result>: 'pass', 'fail' ou 'unknown'."""
def julgar(user_input, output, rubric, judge_model="claude-opus-4-8"):
resp = client.messages.create(
model=judge_model, # juiz != modelo avaliado
max_tokens=1024,
messages=[{"role": "user", "content": JUDGE_PROMPT.format(
rubric=rubric, user_input=user_input, output=output)}],
)
texto = resp.content[0].text.lower()
if "<result>pass</result>" in texto:
return "pass"
if "<result>unknown</result>" in texto:
return "unknown"
return "fail"
Detalhe que a doc reforça em todo exemplo: "geralmente é boa prática usar um modelo diferente para avaliar daquele usado para gerar a saída avaliada". Se o mesmo modelo gera e julga, ele tende a achar bom o próprio trabalho.
Passo 4: Score, barra de erro e os vieses do juiz
Junta as duas camadas, roda no dataset inteiro e agrega. Mas não reporte um número seco.
# run_eval.py
from dataset import golden_set
from judge import julgar
from checks import chamou_ferramenta_certa
RUBRICA_PROCESSO = "A resposta valida elegibilidade ANTES de prometer reembolso?"
def avaliar_caso(caso, agent_fn):
output, trajectory = agent_fn(caso["input"])
code_ok = chamou_ferramenta_certa(trajectory, caso["should_use_tool"])
if not code_ok: # peneira barata primeiro
return False
return julgar(caso["input"], output, RUBRICA_PROCESSO) == "pass"
def rodar(agent_fn, n=5):
"""n trials por caso porque a saída é não-determinística."""
resultados = []
for caso in golden_set:
passes = sum(avaliar_caso(caso, agent_fn) for _ in range(n))
resultados.append(passes / n)
media = sum(resultados) / len(resultados)
# erro padrão grosseiro pra ter noção de incerteza
import statistics
erro = statistics.pstdev(resultados) / (len(resultados) ** 0.5)
return media, erro
media, erro = rodar(meu_agente)
print(f"Pass rate: {media:.0%} ± {erro:.0%}")
Reportar incerteza não é firula. A Anthropic passou a publicar barras de erro nos próprios evals justamente porque a diferença entre 84% e 87% pode ser ruído. E como a saída é não-determinística, vale rodar cada caso k vezes: pass@k mede se o agente acerta em pelo menos uma de k tentativas (bom pra coding, onde dá pra tentar de novo); pass^k mede se ele acerta em todas as k (use quando consistência é o que importa).
Agora os vieses — e o juiz LLM tem dois clássicos que silenciosamente corrompem seu eval:
- Viés de posição: em comparação par a par, o juiz prefere a primeira opção. O MT-Bench (Zheng et al., 2024) mediu o slot A ganhando 10 a 15 pontos a mais só pela ordem. Mitigação: rode cada par nas duas ordens e trate veredito que muda com a ordem como empate.
- Viés de verbosidade: resposta mais longa pontua mais, mesmo com qualidade igual — 15 a 30 pontos de preferência inflada em juízes GPT-4, Claude e PaLM-2. Se seu juiz vive premiando o texto maior, é isso.
Conhecer esses vieses é o que separa um eval que mede do que enfeita.
Passo 5: O gate no CI
Eval que não roda no CI vira documentação morta. Transforme o pipeline num teste que barra o merge quando o agente regride:
# test_agent_eval.py
import pytest
from run_eval import rodar
from meu_agente import meu_agente
REGRESSION_BASELINE = 0.95 # casos que o agente JÁ resolve
def test_nao_regrediu():
media, erro = rodar(meu_agente, n=5)
assert media >= REGRESSION_BASELINE, (
f"Regressão: {media:.0%} < baseline {REGRESSION_BASELINE:.0%}. "
"Leia as transcrições antes de mergear."
)
A Anthropic separa dois tipos de eval, e a distinção importa pro threshold:
- Eval de capacidade: "deve começar com pass rate baixo, mirando tarefas em que o agente tem dificuldade." É seu roadmap — define o que o agente ainda não faz.
- Eval de regressão: "deve ter pass rate de quase 100%." É seu cinto de segurança — o que já funciona não pode quebrar.
O gate do CI usa o segundo. Capacidade você acompanha num dashboard; regressão você bloqueia no merge. Esse gate é um dos degraus de autonomia em agentic code: sem ele, você não sobe o agente de nível sem rezar.
Tutorial te mostra o caminho — no Clã você constrói junto. Aula ao vivo toda semana, projetos reais de Engenharia de IA, ao lado de quem já está em produção.
Entrar no ClãO gap entre teste e produção: passou em 47 testes e quebrou com o primeiro cliente
A cena se repete. Suíte verde, 47 casos passando, deploy na sexta. Segunda de manhã chega o primeiro ticket real: três perguntas numa frase só, um número de pedido com um dígito trocado e um "aliás, cancela o outro também". O agente chama lookup_order com o ID errado, recebe vazio, e em vez de perguntar, preenche o buraco com um dado plausível. Nenhum dos 47 casos previa isso — porque os 47 casos saíram da cabeça de quem escreveu o agente.
Esse é o vício estrutural do conjunto de teste feito por dev: ele herda o modelo mental de quem construiu o sistema. Você escreve os casos que imagina, e você imagina o que já entendeu. O que quebra em produção é exatamente o que não passou pela sua cabeça: input mal formatado, contexto que falta, pergunta fora de escopo, usuário insistindo, dois pedidos misturados na mesma mensagem. O teste sintético mede se o agente atende ao seu modelo mental. Produção mede se ele atende ao mundo. Enquanto o dataset for 100% escrito por você, o pass rate é uma medida da sua imaginação, não do agente.
Amostragem: os casos vêm do log, não da reunião. A correção é parar de inventar caso e começar a garimpar. Puxe as conversas de um período fechado e estratifique a amostra em vez de sortear uniformemente — sorteio uniforme te devolve o caso feliz, que é a maioria. Um recorte que funciona bem: pegue 200 conversas, sendo ~40% aleatórias (pra ter baseline e não viciar o dataset só em desastre) e ~60% puxadas por sinais de atrito, que você extrai da própria trajetória que já loga: sessões com número de turnos acima do p90 (usuário reformulando), sessões que morreram sem resposta final (usuário desistiu), tool call que retornou vazio ou erro, uso de fallback, e qualquer sessão onde o agente respondeu sem chamar nenhuma ferramenta numa categoria que sempre exige uma. Dessas 200, boa parte é ruído — mas o punhado de casos que sobra é justamente o tipo de caso que ninguém escreveria na mão.
Rotulagem barata: binário primeiro, detalhe só no que falhou. O erro caro é pedir pro humano dar nota de 1 a 10 em 200 conversas. Pergunte uma coisa só, binária: "isso deu certo pro usuário? sim/não". A ~15 segundos por conversa, 200 conversas cabem em menos de uma hora, e não exige o dev sênior — exige quem conhece o domínio. Só as marcadas como "não" vão pra segunda passada, onde alguém escreve o reference: o que deveria ter acontecido, e em que etapa desandou. O custo fica concentrado nos casos que viram teste. Cada caso desses entra no golden_set com a origem anotada ("origem": "ticket #1981"), porque daqui a três meses você vai querer saber se aquele caso ainda representa alguma coisa.
Onde o juiz LLM funciona e onde ele se engana. Funciona quando a rubrica é verificável contra evidência presente no texto: citou o valor correto? pediu o número do pedido antes de prometer? recusou o pedido fora da janela de 7 dias? Se engana quando o critério é "a resposta foi boa" — aí o juiz vira medidor de fluência e premia o texto mais bem escrito. E se engana feio num caso específico que quase ninguém trata: erro por omissão. O juiz lê a resposta que existe, não a informação que faltou. Se o agente acertou o valor mas esqueceu de avisar do prazo de estorno, não tem nada no texto pra ele reparar na ausência — ele aprova. Regra prática que uso: erro de comissão (falou algo errado) o juiz pega; erro de omissão (não falou algo obrigatório) só um check de código com checklist de campos obrigatórios pega.
As heurísticas baratas que resolvem a maior parte. Antes de gastar uma chamada de juiz por caso, rode um punhado de checagens determinísticas sobre a trajetória — elas custam milissegundos e pegam a maioria das falhas de produção que eu vejo:
- Tool call que retornou vazio ou erro, seguida de resposta afirmativa e específica: candidato a alucinação, quase sempre.
- Número, data ou valor na resposta final que não aparece em nenhum resultado de ferramenta: groundedness quebrada. Um
inde substring já denuncia. n_turnsacima do p90 histórico daquela categoria: usuário se debatendo.- Zero tool calls numa categoria que sempre exige tool: o agente respondeu de memória.
- Resposta muito acima do tamanho mediano da categoria: sinal de enrolação, e costuma acompanhar incerteza.
Nenhuma dessas precisa de LLM. Elas não julgam qualidade — elas marcam suspeitos. O juiz LLM entra só no que sobrou dessa peneira, e sobra bem menos do que você imagina, o que derruba o custo do pipeline inteiro e ainda te dá um sinal que roda sobre o tráfego real, não só no CI.
Se o seu eval hoje é só o conjunto que você mesmo escreveu, ele vai continuar dando verde até o dia em que não dá — e você descobre pelo cliente. Montar eval que pega o que o teste não pega é o módulo 10 do AI Engineering Lab — 3ª edição: amostragem de tráfego real, rubrica calibrada, tracing e custo, dentro da arquitetura completa de um agente em produção (tool calling, structured output, roteamento, memória, grounding). 19 e 20/09/2026, das 9h às 13h, online ao vivo pelo Google Meet. Lote 1 por R$ 37.
Limitações e pontos de atenção
Onde isso quebra se você não tomar cuidado:
- Juiz não calibrado é teatro. "Graders LLM-as-a-judge devem ser calibrados de perto com especialistas humanos." Na prática: pegue 30-50 casos, faça um humano julgar, rode o juiz nos mesmos e meça concordância. Se diverge muito, conserte a rubrica antes de escalar. Sem essa etapa, você automatizou um chute.
- Custo. Cada caso vira uma (ou várias) chamada de API. Por isso a peneira de código primeiro — não desperdice juiz LLM em coisa que um
ifresolve. - Drift do juiz. Trocou o modelo do juiz? Seus scores históricos não são mais comparáveis. Versione o modelo e o prompt do juiz junto com o dataset.
- Não substitui humano em alta aposta. Eval automatizado cobre o grosso. Decisão médica, financeira ou jurídica ainda pede revisão humana nos casos de borda. Use o pipeline pra liberar o humano pro que realmente exige julgamento, não pra demiti-lo.
FAQ rápido
Que modelo usar como juiz? Um modelo capaz, e de preferência diferente do que gera a saída. Modelo de fronteira como juiz costuma valer o custo — é onde a calibração com humano fica mais alta. Não use um modelo fraquinho pra economizar e depois confiar no veredito.
Como sei que o juiz está certo? Calibração. Mede a concordância entre o juiz e um humano num conjunto de casos. Concordância alta = pode escalar. Baixa = a rubrica está vaga ou o modelo do juiz é fraco demais pra tarefa.
Code-based ou LLM-as-a-judge? Os dois, em camadas. Código pega o objetivo (schema, tool call, match exato) a custo zero. O juiz LLM pega só a nuance que sobra. Nunca jogue tudo no LLM.
Quantos casos preciso? Comece com 20 a 50 de falhas reais. Cresça em direção a centenas conforme o agente amadurece — mais casos com sinal automatizado batem poucos casos com julgamento humano artesanal.
Conclusão
Você montou o ciclo completo: dataset de falhas reais, peneira de código, juiz LLM com rubrica isolada e saída de emergência, score com barra de erro, mitigação de viés e um gate de regressão no CI. Não é um número bonito pro slide — é o instrumento que te diz, a cada commit, se o agente continua fazendo o que deveria.
O próximo salto é fechar o loop: o agente que avalia o próprio trabalho contra a rubrica antes de entregar, e o pipeline alimentando a melhoria contínua do prompt e do harness. Eval deixa de ser só portão de saída e vira parte do design do agente.
E é exatamente esse caminho — do prompt cru até o harness de um agente rodando em produção, com avaliação no meio — que a gente percorre ao vivo no workshop Do Prompt ao Harness: construindo um agente de vendas, onde o eval não é slide, é código que decide o que sobe. Porque, no fim, agente em produção não é prompt bonito: é arquitetura, contexto, avaliação e produto.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Conteúdo é o que não falta. Falta quem desembaralhe: o que importa agora é como implementar do jeito certo. No Clã você tem isso ao vivo, toda semana, com quem já filtrou o ruído.
Entrar no Clã