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:
| Campo | O que registra |
|---|---|
| AgentStep.kind | message, tool_call, tool_result, observation, decision ou final |
| AgentStep.tool_name, arguments, result | a chamada e o que voltou |
| terminal_status | success, failure ou unknown |
| truncated, step_limit | que o agente atingiu seu limite e o traço termina antes do fim |
| constraints | verificações feitas pelo seu ambiente, cada uma com o passo que observou |
| checkpoints | pontos 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| Tipo | Passa quando | Recebe |
|---|---|---|
| agent_tool_called | a ferramenta foi chamada pelo menos min_calls vezes | tool_name, min_calls (padrão 1) |
| agent_no_tool_loop | nenhuma chamada idêntica, mesma ferramenta e argumentos, se repete mais de max_repeats vezes seguidas | max_repeats (padrão 2) |
| agent_tool_sequence | as ferramentas chamadas correspondem a expected.tools | ordered (padrão true) |
| agent_no_undeclared_tool | nenhuma ferramenta fora de expected.tools foi chamada | nada |
| agent_constraints_satisfied | toda restrição indicada que o ambiente registrou foi satisfeita | constraints |
| agent_max_steps | o traço levou no máximo max_steps passos | max_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ério | O caso truncado | Por quê |
|---|---|---|
| agent_steps_le_10 | falha | os passos registrados já provam que um limite de dez foi ultrapassado |
| agent_tool_lookup_order_called | ausente | a chamada pode estar na parte que não foi registrada |
| agent_no_tool_loop | ausente | uma repetição que chega ao fim do traço pode continuar além dele |
| steps_p95, tool_calls_p50 | ausente | um 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]| Tipo | Passa quando | Recebe |
|---|---|---|
| agent_route | os agentes que detiveram o controle, com repetições colapsadas, são o expected.route do caso | nada |
| agent_tool_permissions | toda chamada de ferramenta foi feita por um agente que o mapa autoriza a chamar essa ferramenta | permissions |
| agent_max_handoffs | o controle mudou de mãos no máximo max_handoffs vezes | max_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
- Registrando o que um sistema fez trata dos outros artefatos tipados.
- Segmentos trata do suporte de segmentos e de por que segmentos nunca fazem gate.