Avaliacoes autogeridas para agentes de IA
Monte um ciclo de avaliacoes no seu repositorio com traces reais, testes deterministas e juiz LLM. Acompanhe a taxa de aprovacao por commit, como 51/60.
O que são avaliações autogeridas para agentes de IA
As avaliações autogeridas para agentes de IA são quatro elementos que mantém no seu próprio repositório: um ficheiro com casos guardados, um script que executa o agente nesses casos, um conjunto de verificações que avalia cada resposta e uma tabela de resultados que pode consultar. Nenhum desses elementos precisa de um fornecedor. O ciclo completo requer algumas centenas de linhas de Python e um ficheiro SQLite.
O agente funcionou na demonstração porque escolheu os cinco inputs manualmente. Falhou na segunda semana porque uma linha do prompt mudou, ou porque o modelo mudou, ou porque a descrição de uma ferramenta mudou, sem que nenhuma medição detetasse o problema. Um ciclo de avaliação transforma “parece pior agora” em “a taxa de aprovação passou de 58 em 60 para 51 em 60 no commit 4f1c9ab”.
O ciclo tem quatro passos, e este guia dedica uma secção a cada passo: recolher traces reais, transformar os casos interessantes em casos de teste, avaliar cada caso a cada alteração e armazenar a taxa de aprovação junto do commit que a produziu. O mesmo ciclo funciona independentemente daquilo em que executa o agente, e as frameworks autogeridas de agentes que vale a pena executar diferem sobretudo na quantidade do trace que fornecem sem configuração adicional.
Por que o agente deixa de funcionar na segunda semana
Um agente é um prompt, um modelo, um conjunto de definições de ferramentas e o contexto que é recuperado em tempo de execução. Os quatro elementos podem mudar sem que o código da aplicação seja alterado, por isso uma revisão de código normal não encontra nada que justifique uma objeção.
A causa mais comum é uma alteração no prompt. Você adiciona uma frase para impedir uma resposta rude. Essa frase altera o comportamento para entradas que ninguém voltou a testar, e os traces mostram isso claramente: o trace da semana passada para a mesma pergunta contém uma chamada de ferramenta create_refund, o desta semana não contém nenhuma, e a resposta é, em vez disso, um pedido de desculpa educado. Nenhum erro foi gerado, por isso nenhum alerta foi acionado.
A segunda causa é o modelo. Registre a string exata do modelo enviada em cada execução, claude-haiku-4-5-20251001 em vez de uma abreviação que você mantém apenas na memória, porque uma taxa de aprovação que cai no dia em que você mudou de modelo só pode ser diagnosticada quando o modelo está registado na linha correspondente.
A terceira causa são as ferramentas. Reformular a descrição de uma ferramenta altera o momento em que o modelo decide chamá-la. Se as suas ferramentas chegam através de servidores MCP executados numa VPS, o schema está noutro processo e pode mudar sem que exista qualquer diff no seu repositório. A quarta causa é a recuperação: a mesma pergunta consulta um índice que foi reconstruído durante a noite, e a resposta passa a seguir o novo documento.
Crie o conjunto de referência a partir dos traces que já recolhe
Não invente casos de avaliação. Extraia-os do tráfego. Se já utiliza tracing self-hosted do Langfuse para o seu agente, cada pedido é armazenado com a entrada, as chamadas às ferramentas e a saída. Esse é exatamente o material bruto de que um caso precisa.
Exporte uma janela de observações root através da API pública. A autenticação é básica: use a sua chave pública como nome de utilizador e a sua chave secreta como palavra-passe.
export LF_HOST="https://langfuse.example.com"
curl -sS -u "$LF_PUBLIC_KEY:$LF_SECRET_KEY" \
"$LF_HOST/api/public/v2/observations?limit=50&isRootObservation=true&fromStartTime=2026-07-01T00:00:00Z" \
| jq '.data[0]'Leia um registo antes de escrever qualquer lógica de análise. As linhas são devolvidas em data, mas os nomes dos campos que contêm a pergunta e a resposta dependem da forma como o seu agente instrumenta os spans. Por isso, mapeie o que realmente vê, em vez do que esperava. Depois, escreva os casos manualmente, com um objeto JSON por linha, em evals/cases.jsonl:
{"id": "refund-double-charge", "tags": ["smoke"], "input": "I was charged twice for order 41822.", "must_call": ["lookup_order", "create_refund"], "must_not_include": ["I cannot help"], "rubric": "The reply confirms exactly one refund for order 41822 and states the amount."}Estas cinco regras mantêm o conjunto útil para execução:
- 40 a 80 casos são suficientes para começar. Abaixo de 20, um caso instável altera a taxa de aprovação em 5 pontos, e um número que varia sem motivo deixa de ser levado a sério.
- Cada erro de produção que corrigir torna-se um caso no próprio dia da correção. É esse hábito que faz o conjunto crescer na direção certa.
- Um comportamento por caso. Um caso que verifica simultaneamente o valor do reembolso e o tom não permite concluir nada quando falha.
- O
idnunca muda, porque o ID permite comparar a execução de hoje com a do mês passado. - Remova os dados sensíveis antes de fazer commit. Este ficheiro vai para o git, por isso elimine nomes de clientes e quaisquer números de encomenda que não lhe pertençam.
Faça a avaliação primeiro com verificações determinísticas, porque elas não têm custo
Tudo o que tem uma resposta certa deve usar uma asserção simples. Não há chamada ao modelo, custo ou ambiguidade. As verificações determinísticas detetam regressões estruturais, que são as que afetam os sistemas à volta do seu agente: o JSON não é analisado, a ferramenta nunca foi chamada, a frase proibida voltou a aparecer ou a resposta não cita nenhuma fonte.
Apenas uma função conhece o seu agente. Todo o resto do harness é genérico.
import json, os, urllib.request
def run_agent(case):
req = urllib.request.Request(
os.environ["AGENT_URL"],
data=json.dumps({"input": case["input"]}).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def deterministic(case, result):
text = result.get("output", "")
called = [c["name"] for c in result.get("tool_calls", [])]
failures = []
for tool in case.get("must_call", []):
if tool not in called:
failures.append(f"tool not called: {tool}")
for phrase in case.get("must_not_include", []):
if phrase.lower() in text.lower():
failures.append(f"forbidden phrase: {phrase}")
if len(called) > case.get("max_tool_calls", 12):
failures.append(f"too many tool calls: {len(called)}")
return failuresMantenha o orçamento de ferramentas nessa lista. Um agente que resolve um caso em 3 chamadas hoje e em 11 amanhã sofreu uma regressão, mesmo quando a resposta final está correta, porque cada chamada tem um custo.
LLM como juiz e as quatro formas de falhar
Tudo o que passa pelas asserções precisa de um avaliador que leia. Um juiz LLM é uma segunda chamada ao modelo: recebe a pergunta, a resposta do agente e um critério e, em seguida, devolve um veredito. É a única forma prática de avaliar se a resposta responde ao que o utilizador pediu.
Quatro regras tornam um juiz utilizável:
- Veredito binário, nunca uma pontuação de 1 a 10. Uma escala devolve 7 e 8 para quase tudo, por isso o número nunca varia e não aprende nada com ele.
- Um critério por chamada. Pergunte sobre o valor do reembolso ou sobre o tom, mas não sobre ambos ao mesmo tempo.
- Dê ao juiz a resposta esperada sempre que o caso tiver uma. Avaliar com base numa referência é muito mais fácil do que avaliar de forma abstrata.
- Force o formato da saída e faça o parsing de forma estrita.
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def judge_prompt(case, output):
return (
"You grade one answer against one criterion.\n"
"Reply with JSON only, in this exact shape:\n"
'{"verdict": "pass", "confidence": "high", "reason": "one short sentence"}\n'
f"Criterion: {case['rubric']}\n"
f"Question: {case['input']}\n"
f"Answer: {output}\n"
"Length is not a criterion. Judge only the criterion above."
)
def judge(case, output, model):
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": judge_prompt(case, output)}],
)
return json.loads(msg.content[0].text)Agora, os modos de falha. Cada um tem um teste que pode executar ainda hoje, e executar esses testes é importante, porque um juiz não verificado produz números que parecem precisos, mas não significam nada.
Viés de comprimento. Respostas mais longas passam com mais frequência. Teste-o: pegue em dez respostas que o juiz reprovou, acrescente a cada uma dois parágrafos de texto confiante que não adicionem nenhum facto novo e avalie-as novamente. Qualquer veredito que mude para aprovado indica viés de comprimento, e o rubric é o elemento que deve corrigir.
Preferência pelo próprio modelo. Um juiz costuma avaliar de forma mais favorável a saída da sua própria família de modelos do que a saída de outra. Teste-o: avalie as mesmas 30 respostas com juízes de duas famílias diferentes e compare os vereditos caso a caso. Quando discordarem, leia o caso pessoalmente.
Viés de posição. Se usar o juiz para comparar duas respostas, A e B, troque a ordem e execute novamente. Um veredito que mude após a troca significa que a comparação por pares ainda não é segura para esse rubric.
Desvio do rubric. Critérios vagos produzem juízes demasiado permissivos. "A resposta é útil" aprova quase tudo. "A resposta indica o valor do reembolso em dólares" aprova apenas o que pretendia avaliar. Reescreva cada critério até ele indicar o facto que está a ser verificado.
Uma proteção abrange os quatro casos. Mantenha 30 casos que classificou manualmente e compare o juiz com as suas classificações sempre que alterar o modelo ou o prompt do juiz. Se ele discordar de si em mais de um caso em dez, corrija o rubric antes de confiar em qualquer taxa de aprovação que ele produza. O juiz é código, por isso deve ter controlo de versões e ser revisto como código.
Classifique com um modelo barato e escale para um modelo de fronteira
Avaliar cada caso com o modelo mais caro em cada commit faz com que a fatura das avaliações cresça mais do que o agente que está a ser testado. Ordene os avaliadores por preço e pare assim que a resposta estiver clara.
The data behind this chart
[
{
"label": "Haiku 4.5, Batch API",
"usd_per_1000_judge_calls": "0.90"
},
{
"label": "Haiku 4.5",
"usd_per_1000_judge_calls": "1.80"
},
{
"label": "Sonnet 5",
"usd_per_1000_judge_calls": "3.60"
},
{
"label": "Opus 5",
"usd_per_1000_judge_calls": "9.00"
}
]Estes valores pressupõem cerca de 1,200 tokens de entrada e 120 tokens de saída por chamada do avaliador, um tamanho realista para uma pergunta, uma resposta e um critério. Avaliar 1,000 casos custa 1.80 dólares americanos com Claude Haiku 4.5 e 9.00 com Claude Opus 5. A diferença parece insignificante até ser multiplicada. Um conjunto de 60 casos, avaliado em cada commit, com 40 commits por semana, representa 2,400 chamadas do avaliador por semana, antes de alguém executar o trabalho noturno.
Dois descontos aplicam-se diretamente ao trabalho de avaliação e podem ser acumulados. As execuções de avaliação não são interativas, por isso a Batch API reduz para metade os preços de entrada e de saída em troca de entrega assíncrona. Essa é a primeira barra do gráfico. A rubrica e as instruções são idênticas byte a byte em todas as chamadas, por isso o armazenamento em cache do prompt é adequado: uma leitura da cache custa um décimo do preço base de entrada, e uma gravação na cache com duração de cinco minutos custa 1.25 vezes o preço base de entrada. Assim, a cache fica amortizada depois de uma única utilização. Estes são os preços de tabela da Anthropic em agosto de 2026, e Sonnet 5 está com preços promocionais até 31 agosto 2026. Por isso, a terceira barra aumenta depois dessa data.
A sequência, por ordem:
- Verificações determinísticas em todos os casos. Não há qualquer custo de API.
- Um avaliador baseado num modelo pequeno para os casos que passaram essas verificações.
- Um avaliador baseado num modelo de fronteira apenas quando o avaliador pequeno indica falha ou indica aprovação com baixa confiança.
- Revisão humana de uma amostra pequena, uma vez por semana.
CHEAP = "claude-haiku-4-5-20251001"
STRICT = "claude-opus-5"
def grade(case, result):
hard = deterministic(case, result)
if hard:
return False, "deterministic", "; ".join(hard)
first = judge(case, result["output"], CHEAP)
if first["verdict"] == "pass" and first["confidence"] == "high":
return True, CHEAP, first["reason"]
second = judge(case, result["output"], STRICT)
return second["verdict"] == "pass", STRICT, second["reason"]Isto troca alguma precisão da avaliação por uma redução de custos, por isso meça essa diferença em vez de a presumir. Uma vez por mês, avalie também o conjunto completo com o avaliador rigoroso e compare as duas colunas. Se divergirem em mais do que alguns casos, a sua rubrica é demasiado vaga para o modelo pequeno. É a rubrica que deve corrigir. Controlar quanto o próprio agente gasta é uma tarefa separada, abordada em controlo de custos de um agente de IA num VPS.
Acompanhar a taxa de aprovação ao longo do tempo num sistema que lhe pertence
Uma taxa de aprovação que não pode ser associada a um commit é apenas uma perceção. Armazene uma linha por caso e por execução, com o commit e o modelo nessa linha.
CREATE TABLE IF NOT EXISTS results (
run_id TEXT NOT NULL,
ran_at TEXT NOT NULL,
git_sha TEXT NOT NULL,
agent_model TEXT NOT NULL,
case_id TEXT NOT NULL,
passed INTEGER NOT NULL,
graded_by TEXT NOT NULL,
reason TEXT
);SELECT run_id, git_sha, agent_model,
count(*) AS cases,
round(100.0 * sum(passed) / count(*), 1) AS pass_pct
FROM results
GROUP BY run_id
ORDER BY ran_at DESC
LIMIT 10;Carregue o esquema com sqlite3 evals/results.db < evals/schema.sql e leia a tendência com sqlite3 -box evals/results.db < evals/passrate.sql. Um ano de execuções diárias em 60 casos corresponde a cerca de 22,000 linhas, pelo que o armazenamento nunca se transforma num projeto próprio. Executar SQLite em produção numa VPS explica as definições que começam a ser importantes quando este ficheiro é partilhado entre máquinas.
O runner apresenta a mesma informação para uma pessoa:
run 2026-08-05T09:14:22Z sha 4f1c9ab model claude-sonnet-5 58/60 pass (96.7%)
FAIL refund-double-charge deterministic: tool not called: create_refund
FAIL pto-policy-question judge(opus): reply gives no dollar amountExecute a suite nas alterações que podem quebrar um agente: alterações ao prompt, ao modelo e às ferramentas, em vez de executar a suite em todos os commits do repositório. Um hook pre-push cobre o subconjunto rápido:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
python3 evals/run.py --set smoke || exit 1
EOF
chmod +x .git/hooks/pre-pushAs execuções completas são mais lentas e devem ser agendadas. Um serviço e temporizador systemd na VPS executa todas as verificações durante a noite com o prompt implementado. Isto deteta alterações externas ao repositório, como uma ferramenta alojada cujo comportamento mudou.
Revisão humana por amostragem, não exaustiva
O juiz é calibrado com base em avaliações humanas, por isso alguém precisa de as produzir. Leia uma amostra todas as semanas: todos os casos em que o juiz falhou, mais dez aprovações escolhidas aleatoriamente. As aprovações aleatórias são a metade importante, porque um juiz que começou a aprovar respostas incorretas sem chamar a atenção parece perfeito em qualquer dashboard baseado nas próprias avaliações.
Quinze casos a três minutos cada correspondem a 45 minutos por semana. Esse trabalho devolve correções para a rubrica nos casos em que há divergência entre si e o juiz, além de novos casos para tipos de falha que ninguém tinha previsto. Registe a avaliação humana na mesma tabela, com graded_by definido como human, para que a concordância entre o juiz e a pessoa se torne uma consulta, em vez de depender da memória.
O que falha no próprio harness de avaliação
anthropic.RateLimitError na primeira execução completa. Os 60 casos executados em paralelo excedem o limite de pedidos ou de tokens do seu nível. Limite a simultaneidade a 4 workers e transfira a execução noturna para a Batch API.
json.JSONDecodeError: Expecting value: line 1 column 1 (char 0) do juiz. O modelo respondeu em prosa ou envolveu o JSON num bloco de código. Tente novamente uma vez e registe o caso como erro. Nunca permita que uma falha de análise seja contabilizada como aprovação, porque uma suite que converte erros em aprovações aproxima-se dos 100% enquanto o agente piora.
Casos instáveis. A mesma entrada passa numa execução e falha na seguinte porque o agente amostra a saída. Execute o caso instável 3 vezes e registe a fração, em vez de eliminar o caso. Um caso que passa em 2 de 3 execuções revela um problema real de robustez, e um cliente irá encontrá-lo.
Degradação do conjunto dourado. Alguém edita uma resposta esperada para fazer a suite passar. Reveja os diffs de evals/cases.jsonl com o mesmo cuidado que aplica aos diffs do agente, porque esse ficheiro é a sua definição escrita do que está correto.
Uma suite que nunca falha. Uma taxa de aprovação mantida em 100% durante um mês significa que o conjunto deixou de acompanhar o produto. Obtenha 10 traces recentes, encontre aqueles que o agente tratou mal e adicione-os.
FAQ
De quantos casos precisa um conjunto de avaliações de um agente de IA?
Comece com 40 a 80 casos e aumente o conjunto a partir de falhas reais. Abaixo de cerca de 20 casos, um resultado instável altera a taxa de aprovação em 5 pontos, pelo que essa taxa deixa de transmitir informação útil. Acima de algumas centenas, cada execução custa dinheiro e tempo reais, enquanto cada caso adicional acrescenta pouca cobertura. A medida relevante não é a contagem: é a percentagem dos tipos de falha conhecidos em produção que aparecem pelo menos uma vez no conjunto.
Posso confiar num avaliador LLM para classificar o meu agente?
Só depois de o medir com as suas próprias classificações. Mantenha 30 casos classificados manualmente e compare o avaliador com esses casos sempre que alterar o modelo ou o prompt do avaliador. Os avaliadores apresentam viés de comprimento, em que respostas mais longas são aprovadas com maior frequência, e autopreferência, em que as respostas da sua própria família de modelos são classificadas de forma mais favorável. Ambos podem ser testados: aumente artificialmente uma resposta reprovada e volte a classificá-la, ou classifique as mesmas respostas com um avaliador de outra família. Se o avaliador discordar das suas classificações em mais de um caso em dez, o rubric é demasiado vago para ser utilizado.
Que modelo deve classificar as avaliações?
Comece pelo mais barato e escale quando necessário. As asserções determinísticas não têm custo, por isso são executadas primeiro em todos os casos. Um modelo pequeno trata das aprovações claras. Apenas as reprovações e os veredictos com baixa confiança são encaminhados para um modelo de fronteira. A preços de tabela em agosto de 2026, classificar 1.000 casos custa cerca de 1.80 dólares americanos com Claude Haiku 4.5 e cerca de 9.00 com Claude Opus 5. Como as execuções das avaliações são assíncronas, a Batch API reduz para metade qualquer um destes valores.
As avaliações substituem a monitorização em produção?
Não, porque respondem a perguntas diferentes. Um conjunto de avaliações indica se uma alteração que está prestes a lançar torna melhor ou pior um conjunto fixo de casos. O tracing e a monitorização indicam o que os utilizadores reais estão a utilizar neste momento, incluindo entradas que nenhum caso cobre. Alimentam-se mutuamente: os traces fornecem casos novos, e o conjunto de avaliações determina se a sua correção funcionou efetivamente.