Pular para o conteúdo

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:

PerguntaVerificada porExemplo
O usuário obteve o resultado certo? (sucesso da tarefa)Uma verificação da saída, como contains, ou um juizanswer_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ênciasagent_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-agent

Todos 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

ArquivoO que é
app.pyO 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.jsonl40 pedidos de reembolso, cada um com a resposta esperada e, para a maioria, as ferramentas esperadas
data/orders.jsonlOs pedidos que a ferramenta lookup_order lê
oloproof.yamlA suíte: dataset, sistema, avaliadores, métricas de distribuição, segmentos
release.yamlA 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:

CampoO que registra
stepsCada 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_statussuccess, failure ou unknown, como o agente viu
truncated, step_limitQue o loop atingiu o seu limite e o registro para antes do fim
constraintsAgentConstraintCheck(name, passed, step_index): verificações que o seu ambiente fez, como "nenhum cliente foi excluído"
checkpointsAgentCheckpoints 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: 0

no-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 run
Run 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 miss

Como 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_035

O 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: passed

O 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 tool

Escreva 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.05
oloproof run
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml

A 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-team

Os 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 run
Gate: 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 --failures
6 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.yaml

A 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

SintomaCausaCorreção
Os avaliadores de agente se recusam a rodarrecords: [agent_trajectory/v1] ausente no sistemaDeclare-o em oloproof.yaml e em @system
Uma trajetória é recusadaAlguns passos nomeiam um agent e outros nãoNomeie o agente de todos os passos, ou de nenhum
Muitos casos missing em um critérioTraces truncados: o loop atingiu o seu limiteAumente o limite ou corrija o loop; casos ausentes alargam o intervalo em vez de passar
agent_tool_sequence tem um denominador pequenoCasos sem expected.toolsDeclare a sequência onde ela importa; [] significa "não espera nenhuma ferramenta"
Uma métrica de restrição nunca falhaA aplicação não registra essa verificaçãoRegistre um AgentConstraintCheck onde o seu ambiente a observa
Os resultados diferem entre execuções da mesma versãoAs ferramentas leem ou gravam estado compartilhadoReinicie 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 imediatoO mapa de permissões é fechadoDeclare 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.