Skip to content

Guias

Agentes e ferramentas

O Oloproof não conduz um agente. Seu agente 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, então o registro é toda a integração.

examples/support_agent/ é o projeto que esta página executa: quarenta pedidos de reembolso, um agente determinístico e nenhuma credencial de provedor.

Registrando uma trajetória

Dentro do sistema, monte os passos à medida que acontecem e entregue-os ao registrador do caso:

from oloproof import AgentConstraintCheck, AgentStep, AgentTrajectory, current_case, system


@system(name="support-agent", version="slice-e-example", records=("agent_trajectory/v1",))
def run(case):
    steps = []
    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=...),
            ),
        )
    )
    return {"answer": "refunded" if refunded else "unresolved"}

Para que serve cada parte:

CampoO que registra
AgentStep.kindmessage, tool_call, tool_result, observation, decision ou final
AgentStep.tool_name, arguments, resulta chamada e o que voltou
terminal_statussuccess, failure ou unknown
truncated, step_limitque o agente atingiu seu limite e o traço termina antes do fim
constraintsverificações feitas pelo seu ambiente, cada uma com o passo que observou
checkpointspontos a partir dos quais uma reprodução poderia ser retomada, como AgentCheckpoint

O sistema declara que registra a trajetória, no decorador e em oloproof.yaml:

system:
  name: support-agent
  version: slice-e-example
  callable: app:run
  records: [agent_trajectory/v1]

Sem records:, os avaliadores de agente se recusam a executar em vez de contar todos os casos como ausentes.

O que um caso declara

Um caso que deve chamar determinadas ferramentas, em determinada ordem, informa isso em expected.tools:

{"id": "case_001", "input": {"order_id": "ord-002", "behaviour": "clean"}, "expected": {"answer": "refunded", "tools": ["lookup_order", "issue_refund"]}, "metadata": {"surface": "chat", "behaviour": "clean"}}

expected.tools: [] diz que o caso não espera nenhuma chamada de ferramenta, e ele é avaliado com base nisso. Omitir a chave diz que a escolha de ferramentas não se aplica ao caso: agent_tool_sequence e agent_no_undeclared_tool o excluem com no_declared_tools. Ele sai do denominador em vez de contar como aprovado, porque uma taxa inflada com casos que nunca foram medidos não é uma taxa. Um valor que não seja uma lista de nomes de ferramentas é recusado antes da execução.

Os 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
TipoPassa quandoRecebe
agent_tool_calleda ferramenta foi chamada pelo menos min_calls vezestool_name, min_calls (padrão 1)
agent_no_tool_loopnenhuma chamada idêntica, mesma ferramenta e argumentos, se repete mais de max_repeats vezes seguidasmax_repeats (padrão 2)
agent_tool_sequenceas ferramentas chamadas correspondem a expected.toolsordered (padrão true)
agent_no_undeclared_toolnenhuma ferramenta fora de expected.tools foi chamadanada
agent_constraints_satisfiedtoda restrição indicada que o ambiente registrou foi satisfeitaconstraints
agent_max_stepso traço levou no máximo max_steps passosmax_steps

agent_steps e agent_tool_calls são as distribuições por trás do último, como métricas de quantil.

oloproof run
│ 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 │
│ no-deletion         │ agent_constraints_satisfied │ FAIL                  │ observed_failures_exceed_limit │

Uma restrição faz gate como qualquer outro critério. no-deletion é uma regra de contagem observada com max_failures: 0: três casos chamaram delete_customer, e "isso não pode acontecer na suíte que executamos" não precisa de intervalo para ser decidido.

Um traço que termina antes do fim

Um caso atinge o limite de passos do agente, então seu traço é registrado como truncated. Toda contagem sobre um traço truncado é um limite inferior, e um limite inferior resolve algumas questões e outras não:

CritérioO caso truncadoPor quê
agent_steps_le_10falhaos passos registrados já provam que um limite de dez foi ultrapassado
agent_tool_lookup_order_calledausentea chamada pode estar na parte que não foi registrada
agent_no_tool_loopausenteuma repetição que chega ao fim do traço pode continuar além dele
steps_p95, tool_calls_p50ausenteum quantil sobre limites inferiores não é um quantil

Ausente em vez de excluído: o caso permanece no denominador sem ser observado, e o intervalo admite que ele tenha ido para qualquer lado. É por isso que agent_tool_lookup_order_called mostra [86.8%, 100.0%] sobre 39 casos observados, em vez do intervalo mais estreito que 39 casos sozinhos dariam.

Segmentos sobre trajetórias

first_tool agrupa os casos pela ferramenta que o agente acionou primeiro, quaisquer que sejam as ferramentas que a execução chamou. repeated_action separa os casos que repetiram uma chamada idêntica dos que não repetiram. trajectory_length:4,8 agrupa os casos em 1-4, 5-8 e 9 ou mais passos, em limites que você declara, porque um limite de faixa muda o que um segmento diz.

│ first_tool=search          │ agent_no_tool_loop             │ 0.0%     │ [0.0%, 57.9%]        │ 0 / 6 observed · 1 missing · 0 excluded ·          │
│ first_tool=search          │ agent_tool_sequence            │ 0.0%     │ [0.0%, 41.0%]        │ 0 / 7 observed · 0 missing · 0 excluded ·          │
│ repeated_action=false      │ answer_correct                 │ 93.1%    │ [77.2%, 99.2%]       │ 27 / 29 observed · 0 missing · 0 excluded ·        │
│ repeated_action=true       │ answer_correct                 │ 60.0%    │ [26.2%, 87.9%]       │ 6 / 10 observed · 0 missing · 0 excluded ·         │

Um loop é um sinal, não uma explicação. Nada na saída diz que uma chamada repetida é a razão pela qual um caso falhou; só uma reprodução que a removesse poderia dizê-lo.

Vários agentes

examples/triage_agents/ executa uma equipe de três: triage entrega cada pedido a billing ou tech, e um reembolso que billing não pode emitir é entregue a uma pessoa. Em um sistema de vários agentes, cada passo indica o agente que o executou, e uma 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 indica o agente de todos os passos ou de nenhum. Um registro que indica apenas alguns é recusado, e um que não indica nenhum fica ausente para todos os critérios abaixo, em vez de passar neles. Um passo de transferência é opcional, já que o controle também muda de mãos quando o passo seguinte é executado por outro agente. É a forma de um traço registrar uma transferência para um agente que não executa nenhum passo, como uma pessoa.

Um caso declara os agentes pelos quais deve passar em expected.route:

{"id": "case_012", "input": {"topic": "billing", "request": "I was charged twice for order ord-012", "order_id": "ord-012", "behaviour": "escalated"}, "expected": {"answer": "escalated", "route": ["triage", "billing", "person"]}, "metadata": {"topic": "billing", "behaviour": "escalated"}}
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]
TipoPassa quandoRecebe
agent_routeos agentes que detiveram o controle, com repetições colapsadas, são o expected.route do casonada
agent_tool_permissionstoda chamada de ferramenta foi feita por um agente que o mapa autoriza a chamar essa ferramentapermissions
agent_max_handoffso controle mudou de mãos no máximo max_handoffs vezesmax_handoffs

A rota inclui o destinatário de uma transferência, então um traço que termina escalando para uma pessoa tem como rota a pessoa. Um caso sem expected.route não é contado por agent_route. O mapa de permissões é fechado: um agente que ele não lista não pode chamar nenhuma ferramenta.

│ 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 │

Em dois pedidos, tech emitiu um reembolso. Ambos passam em answer_correct e falham em agent_tool_permissions: o cliente recebeu a resposta certa de um sistema que violou suas próprias permissões. Um pedido é passado de um lado para o outro entre billing e tech até o limite do loop. Seu traço é truncado, então ele falha na rota e no limite de transferências, que seu prefixo já resolve, e fica ausente para as permissões, que o prefixo não resolve.

route agrupa os casos pela rota que seus agentes seguiram, escrita como triage>billing. Um traço truncado não entra em nenhum grupo de rota, porque sua rota é um prefixo de para onde quer que seus agentes tenham ido depois.

Nada na saída diz qual agente é o culpado. Uma divergência de rota diz onde duas rotas se separam. Que um agente causou uma falha é uma afirmação sobre o que teria acontecido se ele tivesse agido de outra forma, o que só uma reprodução que substituísse esse agente poderia mostrar, e o Oloproof não executa uma.

Próximos passos