Guias
Tutorial: avaliar um agente
Um passo a passo executável para um agente que usa ferramentas e para uma equipe de agentes: registre o que o agente fez como uma trajetória, verifique o uso de ferramentas, as restrições, os passos, o roteamento, as permissões e as transferências, condicione um lançamento a elas e compare uma mudança candidata com a linha de base. Os dois exemplos rodam localmente, sem credenciais de provedor.
A referência de cada campo e avaliador está em Agentes e ferramentas; os termos caso, avaliador, métrica, intervalo e gate estão em Conceitos básicos. Esta página é o caminho prático por eles.
O que o Oloproof faz e não faz aqui
O Oloproof não conduz o seu agente. Sua aplicação executa o próprio loop, chama as próprias ferramentas e registra o que aconteceu como um artefato agent_trajectory/v1. Toda métrica de agente é lida desse registro.
Sua aplicação também é dona de tudo o que as ferramentas tocam. O Oloproof não fornece sandbox, nem ferramentas simuladas, nem reinicialização entre casos: se uma ferramenta grava em um banco de dados, envia um e-mail ou cobra um cartão durante uma avaliação, ela realmente faz isso. Aponte o agente para contas de teste, ferramentas stub ou um ambiente descartável, e reinicie o estado entre os casos você mesmo, antes de executar uma avaliação.
Mantenha separados dois tipos de pergunta:
| Pergunta | Verificada por | Exemplo |
|---|---|---|
| O usuário obteve o resultado certo? (sucesso da tarefa) | Uma verificação da saída, como contains, ou um juiz | answer_correct |
| O agente se comportou como permitido no caminho? | Verificações de trajetória: escolha de ferramenta, ordem, loops, restrições, passos, roteamento, permissões, transferências | agent_constraints_satisfied, agent_route |
Elas divergem de maneiras úteis. Nos dois exemplos abaixo, alguns casos respondem corretamente e mesmo assim violam uma regra, e só uma verificação de trajetória percebe isso. Uma verificação de trajetória que passa também não diz nada sobre se a tarefa teve sucesso.
Pré-requisitos
- Python 3.11 ou posterior, e o Oloproof instalado (pip install oloproof).
- Os projetos de exemplo, que vêm com o pacote: support_agent (um agente) e triage_agents (três). Copie um para um novo diretório e trabalhe lá:
oloproof init --example support_agent my-agent
cd my-agentTodos os comandos abaixo rodam de dentro do diretório copiado. A evidência é armazenada em .oloproof/ ali.
Parte 1: um agente que usa ferramentas
Os arquivos
| Arquivo | O que é |
|---|---|
| app.py | O agente: Tools, um plan que faz as vezes das decisões do modelo, e run(case), o seu loop, que registra a trajetória |
| data/refunds.jsonl | 40 pedidos de reembolso, cada um com a resposta esperada e, para a maioria, as ferramentas esperadas |
| data/orders.jsonl | Os pedidos que a ferramenta lookup_order lê |
| oloproof.yaml | A suíte: dataset, sistema, avaliadores, métricas de distribuição, segmentos |
| release.yaml | A política de lançamento |
Registrando a trajetória
run é toda a superfície de integração. Ele chama cada ferramenta, acrescenta um AgentStep para a chamada e outro para o seu resultado, registra as verificações de restrição que o próprio ambiente fez e entrega a trajetória ao registrador do caso:
@system(name="support-agent", version="slice-e-example", records=("agent_trajectory/v1",))
def run(case):
for name, arguments in plan(case):
steps.append(AgentStep(index=len(steps) + 1, kind="tool_call", tool_name=name, arguments=arguments))
result = getattr(tools, name)(**arguments)
steps.append(AgentStep(index=len(steps) + 1, kind="tool_result", tool_name=name, result=result))
...
current_case().agent_trajectory(
AgentTrajectory(
steps=tuple(steps),
terminal_status="success" if refunded else "failure",
truncated=truncated,
step_limit=STEP_LIMIT if truncated else None,
constraints=(AgentConstraintCheck(name="no_deletion", passed=deletion is None, step_index=...),),
checkpoints=tuple(checkpoints),
)
)
return {"answer": "refunded" if refunded else "unresolved"}A forma do artefato:
| Campo | O que registra |
|---|---|
| steps | Cada AgentStep: index, kind (message, tool_call, tool_result, observation, decision, final ou handoff), tool_name, arguments, result e, para equipes, agent e to_agent |
| terminal_status | success, failure ou unknown, como o agente viu |
| truncated, step_limit | Que o loop atingiu o seu limite e o registro para antes do fim |
| constraints | AgentConstraintCheck(name, passed, step_index): verificações que o seu ambiente fez, como "nenhum cliente foi excluído" |
| checkpoints | AgentCheckpoints a partir dos quais um replay poderia retomar (veja Limitações) |
Para usar o seu próprio agente, mantenha o registro e substitua o loop: chame o seu framework em run e traduza os passos dele em AgentStep à medida que acontecem. O sistema declara records: [agent_trajectory/v1] em oloproof.yaml; sem isso, os avaliadores de agente se recusam a rodar em vez de contar todo caso como ausente.
O que um caso declara
{"id": "case_001", "input": {"order_id": "ord-002", "behaviour": "clean"}, "expected": {"answer": "refunded", "tools": ["lookup_order", "issue_refund"]}, "metadata": {"surface": "chat", "behaviour": "clean"}}expected.answer serve à verificação da tarefa. expected.tools é a sequência de ferramentas que o caso deve seguir; omita-a e as verificações de sequência de ferramentas não se aplicam ao caso (ele sai do denominador delas em vez de passar). behaviour é como este exemplo determinístico escolhe o que o seu agente substituto faz; os seus casos carregam apenas entradas reais.
Escolhendo avaliadores
evaluators:
- {type: contains, criterion: answer_correct, field: answer, expected_field: answer}
- {type: agent_tool_called, tool_name: lookup_order}
- {type: agent_no_tool_loop, max_repeats: 2}
- {type: agent_tool_sequence}
- {type: agent_constraints_satisfied, constraints: [no_deletion]}
- {type: agent_max_steps, max_steps: 10}
metrics:
- {id: steps_p95, type: quantile, source: agent_steps, quantile: 0.95}
- {id: tool_calls_p50, type: quantile, source: agent_tool_calls, quantile: 0.5}
slices: [metadata.surface, first_tool, repeated_action, "trajectory_length:4,8"]
min_slice_support: 3- answer_correct é a verificação da tarefa.
- agent_tool_called exige uma ferramenta obrigatória; agent_tool_sequence compara as chamadas com expected.tools; agent_no_tool_loop sinaliza a mesma chamada repetida mais de max_repeats vezes seguidas. Elas descrevem o uso de ferramentas, não o sucesso.
- agent_constraints_satisfied lê as verificações que o seu ambiente registrou. O Oloproof não observa efeitos colaterais por conta própria, então uma restrição que a sua aplicação não registra não pode ser verificada.
- agent_max_steps limita cada execução; as duas métricas de quantil mostram a distribuição, de modo que uma mudança que torna toda execução mais longa fica visível antes que qualquer execução atinja o limite.
A política de lançamento
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- id: answer-floor
metric: answer_correct
min: 0.70
- id: tool-sequence-floor
metric: agent_tool_sequence
min: 0.70
- id: no-deletion
metric: agent_constraints_satisfied
kind: observed_count
max_failures: 0no-deletion é uma regra de contagem observada: "isto não pode acontecer na suíte que executamos" não precisa de intervalo. Veja Gating.
Execute
oloproof runRun run_01M4FCF6544JRDB16NJ1ZFPVRZ [DECIDED/COMPLETE]
Gate: BLOCK (exit 1)
│ answer-floor │ answer_correct │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ tool-sequence-floor │ agent_tool_sequence │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ no-deletion │ agent_constraints_satisfied │ FAIL │ observed_failures_exceed_limit │
│ answer_correct │ 82.5% │ [67.2%, 92.7%] │ 33 / 40 observed · 0 missing · 0 excluded │
│ agent_tool_lookup_order_called │ 100.0% │ [86.8%, 100.0%] │ 39 / 39 observed · 1 missing · 0 excluded │
│ agent_no_tool_loop │ 74.4% │ [56.1%, 87.4%] │ 29 / 39 observed · 1 missing · 0 excluded │
│ agent_tool_sequence │ 60.0% │ [43.3%, 75.2%] │ 24 / 40 observed · 0 missing · 0 excluded │
│ agent_constraints_satisfied │ 92.5% │ [79.6%, 98.5%] │ 37 / 40 observed · 0 missing · 0 excluded │
│ agent_steps_le_10 │ 97.5% │ [86.8%, 100.0%] │ 39 / 40 observed · 0 missing · 0 excluded │
│ steps_p95 │ 10 steps │ [10, no bound] steps │ p95 of 39 observed · 1 missing · 0 excluded │
│ tool_calls_p50 │ 2 calls │ [2, 3] calls │ p50 of 39 observed · 1 missing · 0 excluded │
Cache: execution 0 hit/40 miss; judgment 0 hit/240 missComo ler:
- Exit 1: uma regra deu FAIL. Três casos chamaram delete_customer, e o ambiente registrou a restrição como violada.
- answer-floor é INSUFFICIENT_EVIDENCE embora 82.5% esteja acima de 70%: com 40 casos o intervalo ainda chega a 67.2%.
- 1 missing: um caso atingiu o limite de passos, então o seu trace está truncado. Um trace truncado prova algumas coisas (ele de fato passou de 10 passos) e deixa outras em aberto (uma ferramenta obrigatória pode estar na parte não registrada), então esses critérios o contam como ausente, e o intervalo admite que ele tenha ido para qualquer lado.
Inspecione as falhas
oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case case_035O segundo imprime um caso completo. Resumido:
case case_035
input: {
"order_id": "ord-036",
"behaviour": "violates"
}
output: {
"answer": "refunded"
}
judgments:
answer_correct: passed
agent_tool_lookup_order_called: passed
agent_no_tool_loop: passed
agent_tool_sequence: failed
agent_constraints_satisfied: failed
agent_steps_le_10: passedO cliente recebeu o reembolso (sucesso da tarefa) de um agente que excluiu um cliente no caminho (uma restrição violada). Nenhum dos resultados implica o outro. A trajetória completa, cada passo com os seus argumentos e resultado, está no bundle exportado (oloproof export RUN_ID) e na visão do caso no workbench. A próxima ação significativa está na aplicação: impedir que o loop chame uma ferramenta que ele nunca deve chamar.
Faça uma mudança candidata e compare
Em app.py, faça o loop recusar a ferramenta proibida:
for name, arguments in plan(case):
if name == FORBIDDEN:
continue # the candidate: the loop refuses the forbidden toolEscreva uma política de comparação, compare.yaml:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- id: answers-not-worse
metric: answer_correct
kind: non_inferiority
margin: 0.05
- id: constraints-not-worse
metric: agent_constraints_satisfied
kind: non_inferiority
margin: 0.05oloproof run
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlA execução candidata sozinha: no-deletion agora dá PASS, agent_constraints_satisfied mostra 40 / 40, e o gate bloqueia com exit 3 porque os dois pisos ainda são INSUFFICIENT_EVIDENCE. A comparação:
Comparison sha256:72836e90… of run_01M4FCFH0K2RRCN3ARAEN189X7 against run_01M4FCF6544JRDB16NJ1ZFPVRZ · 40 paired cases
answer_correct: +0.0 points [-12.7, +12.7] · 40 paired · 0 missing · 0 excluded
agent_tool_lookup_order_called: +0.0 points [-17.7, +17.7] · 39 paired · 1 missing · 0 excluded
agent_no_tool_loop: +0.0 points [-17.7, +17.7] · 39 paired · 1 missing · 0 excluded
agent_tool_sequence: +7.5 points [-7.8, +26.1] · 40 paired · 0 missing · 0 excluded
agent_constraints_satisfied: +7.5 points [-7.8, +26.1] · 40 paired · 0 missing · 0 excluded
agent_steps_le_10: +0.0 points [-12.7, +12.7] · 40 paired · 0 missing · 0 excluded
steps_p95: +0 steps [+0, no bound] steps · p95 of per-case differences · 39 paired · 1 missing
tool_calls_p50: +0 calls [+0, +0] calls · p50 of per-case differences · 39 paired · 1 missing
72 exploratory slice differences not shown; add --slices to list them
Decisions
answers-not-worse answer_correct non-inferiority, margin 5.0 points INSUFFICIENT_EVIDENCE interval_overlaps_margin
constraints-not-worse agent_constraints_satisfied non-inferiority, margin 5.0 points INSUFFICIENT_EVIDENCE interval_overlaps_margin
about 10 more paired cases would decide it, if the difference holds (50 in total at 8% discordance)
Gate: BLOCK (exit 3)Leia as duas metades separadamente. Nesta suíte, a mudança eliminou toda exclusão observada, o que a regra de contagem observada de uma única execução já resolve. Se a candidata não é pior que a linha de base em geral é outra pergunta, e 40 casos pareados ainda não conseguem estabelecer isso dentro de uma margem de 5 pontos; a linha de planejamento diz aproximadamente quantos casos a mais conseguiriam. Nenhuma resposta mudou, então o sucesso da tarefa não foi afetado pela correção.
Parte 2: uma equipe de agentes
oloproof init --example triage_agents my-team
cd my-teamOs arquivos e o registro
app.py executa três agentes em um único loop: triage entrega cada pedido a billing ou tech, cada especialista chama as próprias ferramentas, e um reembolso que billing não pode emitir é entregue a uma pessoa. Cada passo nomeia o agente que o executou, e cada transferência de controle é um passo handoff:
steps.append(AgentStep(index=1, kind="message", agent="triage", arguments={"request": request}))
steps.append(AgentStep(index=2, kind="handoff", agent="triage", to_agent="billing"))
steps.append(AgentStep(index=3, kind="tool_call", agent="billing", tool_name="lookup_order"))Uma trajetória nomeia o agente de todos os passos ou de nenhum; uma que nomeia apenas alguns é recusada. Um caso declara a rota que deve seguir:
{"id": "case_009", "input": {"topic": "tech", "request": "Two-factor codes are rejected", "order_id": "ord-009", "behaviour": "overreach"}, "expected": {"answer": "fixed", "route": ["triage", "tech"]}, "metadata": {"topic": "tech", "behaviour": "overreach"}}Avaliadores e política
evaluators:
- {type: contains, criterion: answer_correct, field: answer, expected_field: answer}
- {type: agent_route}
- type: agent_tool_permissions
permissions:
triage: []
billing: [lookup_order, issue_refund]
tech: [search_kb]
- {type: agent_max_handoffs, max_handoffs: 2}
slices: [route]
min_slice_support: 3- agent_route compara os agentes que tiveram o controle (repetições colapsadas, o receptor de uma transferência incluído) com expected.route. Uma verificação de roteamento, não de sucesso.
- agent_tool_permissions verifica cada chamada contra um mapa fechado: um agente que o mapa não lista não pode chamar nenhuma ferramenta.
- agent_max_handoffs limita quantas vezes o controle trocou de mãos.
release.yaml tem answer-floor (min: 0.80), routing-floor (min: 0.70) e no-overreach, uma regra de contagem observada com max_failures: 0 sobre agent_tool_permissions.
Execute a equipe
oloproof runGate: BLOCK (exit 1)
│ answer-floor │ answer_correct │ PASS │ lower_bound_meets_minimum │
│ routing-floor │ agent_route │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ no-overreach │ agent_tool_permissions │ FAIL │ observed_failures_exceed_limit │
│ answer_correct │ 96.7% │ [82.7%, 100.0%] │ 29 / 30 observed · 0 missing · 0 excluded │
│ agent_route │ 86.7% │ [69.2%, 96.3%] │ 26 / 30 observed · 0 missing · 0 excluded │
│ agent_tool_permissions │ 93.1% │ [73.4%, 99.2%] │ 27 / 29 observed · 1 missing · 0 excluded │
│ agent_handoffs_le_2 │ 86.7% │ [69.2%, 96.3%] │ 26 / 30 observed · 0 missing · 0 excluded │oloproof inspect RUN_ID --failures6 of 30 cases failed, errored or did not finish
case_005
output: {"answer": "refunded"}
agent_route: failed
agent_handoffs_le_2: failed
case_009
output: {"answer": "fixed"}
agent_tool_permissions: failed
...
case_030
output: {"answer": "unresolved"}
answer_correct: failed
agent_route: failed
agent_tool_permissions: error: MissingFieldError: truncated_trajectory: the trace stops before whether an agent called a tool it was not given is settled
agent_handoffs_le_2: failed- case_009 e case_020: tech emitiu um reembolso, uma ferramenta que só billing tem. Os dois responderam corretamente. Sucesso da tarefa, permissão violada.
- case_005 e outros dois foram primeiro ao especialista errado e voltaram por triage: a resposta está certa, a rota e o limite de transferências não.
- case_030 ficou indo e voltando entre billing e tech até o limite do loop. O seu trace truncado já prova as falhas de rota e de transferência, e não consegue resolver as permissões, então esse critério fica ausente para ele em vez de aprovado.
Nada na saída diz qual agente é o culpado. Uma divergência de rota diz onde duas rotas se separam; dizer que um agente causou uma falha é uma afirmação sobre o que teria acontecido se ele tivesse agido de outra forma, e nenhuma verificação aqui faz essa afirmação.
Mude a equipe e compare
A próxima ação significativa para as falhas de permissão: tech entrega um reembolso a billing em vez de emiti-lo. Em app.py, em tech:
# The candidate: tech hands the refund to billing, the agent allowed to issue it.
trace.hand_off("tech", "billing", "a goodwill refund")
trace.call("billing", "issue_refund", order_id=str(case["order_id"]))Com um compare.yaml contendo answers-not-worse sobre answer_correct e routing-not-worse sobre agent_route, ambos non_inferiority com margin: 0.05:
oloproof run
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlA execução candidata sozinha:
Gate: BLOCK (exit 3)
│ answer-floor │ answer_correct │ PASS │ lower_bound_meets_minimum │
│ routing-floor │ agent_route │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ no-overreach │ agent_tool_permissions │ INSUFFICIENT_EVIDENCE │ missing_could_change_outcome │
│ agent_route │ 80.0% │ [61.4%, 92.3%] │ 24 / 30 observed · 0 missing · 0 excluded │
│ agent_tool_permissions │ 100.0% │ [82.7%, 100.0%] │ 29 / 29 observed · 1 missing · 0 excluded │e a comparação:
answer_correct: +0.0 points [-16.5, +16.5] · 30 paired · 0 missing · 0 excluded
agent_route: -6.7 points [-28.5, +12.4] · 30 paired · 0 missing · 0 excluded
agent_tool_permissions: +6.9 points [-18.9, +33.5] · 29 paired · 1 missing · 0 excluded
agent_handoffs_le_2: -6.7 points [-28.5, +12.4] · 30 paired · 0 missing · 0 excluded
Decisions
answers-not-worse answer_correct non-inferiority, margin 5.0 points INSUFFICIENT_EVIDENCE interval_overlaps_margin
routing-not-worse agent_route non-inferiority, margin 5.0 points INSUFFICIENT_EVIDENCE interval_overlaps_margin
no sample size would make this PASS: the difference itself (-6.7 points) is outside the margin, so more cases would move it toward FAIL
Gate: BLOCK (exit 3)Três coisas a tirar daí:
- Nenhuma chamada observada violou uma permissão, mas no-overreach agora é INSUFFICIENT_EVIDENCE em vez de PASS: o case_030 truncado pode esconder uma violação na parte não registrada (missing_could_change_outcome). Corrigir esse loop, e não o mapa de permissões, é o que resolveria a regra.
- A correção mudou a rota dos dois casos para triage > tech > billing, que o expected.route deles não declara, então agent_route e agent_handoffs_le_2 caíram. Se essa rota agora está correta é uma decisão de produto: se estiver, atualize o expected.route dos casos; uma verificação de roteamento mede a conformidade com o que você declarou, não a qualidade.
- A linha de planejamento diz que mais casos levariam routing-not-worse para FAIL, não para PASS. A comparação está dizendo que a candidata, como está escrita, troca roteamento por permissões.
Solução de problemas
| Sintoma | Causa | Correção |
|---|---|---|
| Os avaliadores de agente se recusam a rodar | records: [agent_trajectory/v1] ausente no sistema | Declare-o em oloproof.yaml e em @system |
| Uma trajetória é recusada | Alguns passos nomeiam um agent e outros não | Nomeie o agente de todos os passos, ou de nenhum |
| Muitos casos missing em um critério | Traces truncados: o loop atingiu o seu limite | Aumente o limite ou corrija o loop; casos ausentes alargam o intervalo em vez de passar |
| agent_tool_sequence tem um denominador pequeno | Casos sem expected.tools | Declare a sequência onde ela importa; [] significa "não espera nenhuma ferramenta" |
| Uma métrica de restrição nunca falha | A aplicação não registra essa verificação | Registre um AgentConstraintCheck onde o seu ambiente a observa |
| Os resultados diferem entre execuções da mesma versão | As ferramentas leem ou gravam estado compartilhado | Reinicie esse estado antes de cada caso na sua aplicação; o Oloproof não faz isso |
| Um agente adicionado à equipe falha nas permissões de imediato | O mapa de permissões é fechado | Declare o que o novo agente pode chamar |
Limitações
- O Oloproof não conduz, não isola em sandbox nem reinicia um agente. Efeitos colaterais das ferramentas, sessões, estado e a sua reinicialização pertencem à sua aplicação.
- Toda verificação lê a trajetória registrada. O que a aplicação não registra não pode ser medido, e um trace truncado conta como ausente sempre que o seu prefixo não resolve a pergunta.
- As verificações de trajetória são regras determinísticas. Não há verificação de qualidade da trajetória julgada por LLM.
- Nenhuma saída atribui uma falha a um passo ou a um agente. O replay de agente, que reexecuta um caso a partir de um checkpoint registrado com um passo removido para rotulá-lo como necessário ou desnecessário, existe apenas no SDK Python (replay_case), para um sistema que implementa replay a partir dos seus checkpoints; não há comando de CLI para ele, e nada acima o usa.
- Conversas de vários turnos são outra superfície (apenas SDK); veja O que funciona hoje.
- Os campos plan e behaviour dos exemplos fazem as vezes das decisões de um modelo para que as execuções sejam reproduzíveis. Um modelo real no seu loop chama um provedor, precisa de credenciais e custa dinheiro por caso.