Guías
Tutorial: evaluar un agente
Un recorrido ejecutable para un agente que usa herramientas y para un equipo de agentes: registre lo que hizo el agente como una trayectoria, compruebe su uso de herramientas, restricciones, pasos, enrutamiento, permisos y traspasos, use todo ello como gate de una publicación, y compare un cambio candidato con la línea base. Ambos ejemplos se ejecutan en local sin credenciales de proveedor.
La referencia de cada campo y evaluador es Agentes y herramientas; los términos caso, evaluador, métrica, intervalo y gate están en Conceptos básicos. Esta página es el camino práctico a través de ellos.
Lo que Oloproof hace y no hace aquí
Oloproof no dirige a su agente. Su aplicación ejecuta su propio bucle, llama a sus propias herramientas y registra lo ocurrido como un artefacto agent_trajectory/v1. Cada métrica de agente se lee de ese registro.
Su aplicación también es dueña de todo lo que tocan las herramientas. Oloproof no proporciona aislamiento, ni herramientas simuladas, ni reinicio entre casos: si una herramienta escribe en una base de datos, envía un correo o cobra una tarjeta durante una evaluación, lo hace de verdad. Apunte el agente a cuentas de prueba, herramientas simuladas o un entorno desechable, y reinicie usted mismo el estado entre casos, antes de ejecutar una evaluación.
Mantenga separados dos tipos de pregunta:
| Pregunta | Se comprueba con | Ejemplo |
|---|---|---|
| ¿Obtuvo el usuario el resultado correcto? (éxito de la tarea) | Una comprobación de la salida como contains, o un juez | answer_correct |
| ¿Se comportó el agente como se permitía por el camino? | Comprobaciones de trayectoria: elección de herramientas, orden, bucles, restricciones, pasos, enrutamiento, permisos, traspasos | agent_constraints_satisfied, agent_route |
Discrepan de formas útiles. En los dos ejemplos de abajo, algunos casos responden correctamente y aun así rompen una regla, y solo una comprobación de trayectoria lo ve. Que una comprobación de trayectoria pase tampoco dice nada sobre si la tarea tuvo éxito.
Requisitos previos
- Python 3.11 o posterior, y Oloproof instalado (pip install oloproof).
- Los proyectos de ejemplo, que vienen con el paquete: support_agent (un agente) y triage_agents (tres). Copie uno en un directorio nuevo y trabaje allí:
oloproof init --example support_agent my-agent
cd my-agentCada comando de abajo se ejecuta desde dentro del directorio copiado. La evidencia se guarda allí, en .oloproof/.
Parte 1: un agente que usa herramientas
Los archivos
| Archivo | Qué es |
|---|---|
| app.py | El agente: Tools, un plan que sustituye a las decisiones del modelo, y run(case), su bucle, que registra la trayectoria |
| data/refunds.jsonl | 40 solicitudes de reembolso, cada una con la respuesta esperada y, en la mayoría, las herramientas esperadas |
| data/orders.jsonl | Los pedidos que lee la herramienta lookup_order |
| oloproof.yaml | La suite: conjunto de datos, sistema, evaluadores, métricas de distribución, segmentos |
| release.yaml | La política de publicación |
Registrar la trayectoria
run es toda la superficie de integración. Llama a cada herramienta, añade un AgentStep por la llamada y otro por su resultado, registra las comprobaciones de restricciones que hizo su propio entorno y entrega la trayectoria al registrador del 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"}La forma del artefacto:
| Campo | Qué registra |
|---|---|
| steps | Cada AgentStep: index, kind (message, tool_call, tool_result, observation, decision, final o handoff), tool_name, arguments, result y, para equipos, agent y to_agent |
| terminal_status | success, failure o unknown, tal como lo vio el agente |
| truncated, step_limit | Que el bucle alcanzó su límite y el registro se corta antes |
| constraints | AgentConstraintCheck(name, passed, step_index): comprobaciones que hizo su entorno, como "no se borró ningún cliente" |
| checkpoints | AgentCheckpoints desde los que una repetición podría reanudar (vea Limitaciones) |
Para usar su propio agente, conserve el registro y sustituya el bucle: llame a su framework en run y traduzca sus pasos a AgentStep a medida que ocurren. El sistema declara records: [agent_trajectory/v1] en oloproof.yaml; sin ello, los evaluadores de agente se niegan a ejecutarse en lugar de contar cada caso como ausente.
Lo que declara un caso
{"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 es para la comprobación de la tarea. expected.tools es la secuencia de herramientas que debería seguir el caso; si se omite, las comprobaciones de secuencia de herramientas no se aplican al caso (sale de su denominador en lugar de aprobar). behaviour es cómo este ejemplo determinista elige lo que hace su agente sustituto; sus casos solo llevan entradas reales.
Elegir los evaluadores
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 es la comprobación de la tarea.
- agent_tool_called exige una herramienta obligatoria; agent_tool_sequence compara las llamadas con expected.tools; agent_no_tool_loop señala la misma llamada repetida más de max_repeats veces seguidas. Estas describen el uso de herramientas, no el éxito.
- agent_constraints_satisfied lee las comprobaciones que registró su entorno. Oloproof no observa por sí mismo los efectos secundarios, así que una restricción que su aplicación no registra no puede comprobarse.
- agent_max_steps limita cada ejecución; las dos métricas de cuantil muestran la distribución, de modo que un cambio que alarga todas las ejecuciones se ve antes de que ninguna alcance el límite.
La política de publicación
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 es una regla de recuento observado: "esto no debe ocurrir en la suite que ejecutamos" no necesita intervalo. Vea Gates.
Ejecútelo
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 missCómo leerla:
- Salida 1: una regla dio FAIL. Tres casos llamaron a delete_customer, y el entorno registró la restricción como incumplida.
- answer-floor está en INSUFFICIENT_EVIDENCE aunque el 82.5% supera el 70%: con 40 casos el intervalo aún llega hasta el 67.2%.
- 1 missing: un caso alcanzó el límite de pasos, así que su traza está truncada. Una traza truncada demuestra algunas cosas (sí superó los 10 pasos) y deja otras abiertas (una herramienta obligatoria puede estar en la parte no registrada), así que esos criterios la cuentan como ausente, y el intervalo admite que haya ido en cualquier sentido.
Inspeccionar los fallos
oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case case_035El segundo imprime un caso completo. Recortado:
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: passedEl cliente obtuvo el reembolso (éxito de la tarea) de un agente que borró un cliente por el camino (una restricción incumplida). Ninguno de los dos resultados implica el otro. La trayectoria completa, cada paso con sus argumentos y su resultado, está en el paquete exportado (oloproof export RUN_ID) y en la vista del caso en el workbench. La siguiente acción con sentido está en la aplicación: impedir que el bucle llame a una herramienta que nunca debe llamar.
Hacer un cambio candidato y comparar
En app.py, haga que el bucle rechace la herramienta prohibida:
for name, arguments in plan(case):
if name == FORBIDDEN:
continue # the candidate: the loop refuses the forbidden toolEscriba una política de comparación, 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.yamlLa ejecución candidata por sí sola: no-deletion ahora da PASS, agent_constraints_satisfied marca 40 / 40, y el gate bloquea con salida 3 porque los dos mínimos siguen en INSUFFICIENT_EVIDENCE. La comparación:
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)Lea las dos mitades por separado. En esta suite, el cambio eliminó todos los borrados observados, lo que ya zanja la regla de recuento observado de una sola ejecución. Si el candidato no es peor que la línea base en general es otra pregunta, y 40 casos emparejados aún no pueden establecerlo dentro de un margen de 5 puntos; la línea de planificación dice aproximadamente cuántos más lo harían. Ninguna respuesta cambió, así que la corrección no toca el éxito de la tarea.
Parte 2: un equipo de agentes
oloproof init --example triage_agents my-team
cd my-teamLos archivos y el registro
app.py ejecuta tres agentes en un bucle: triage entrega cada solicitud a billing o a tech, cada especialista llama a sus propias herramientas, y un reembolso que billing no puede emitir se entrega a una persona. Cada paso nombra al agente que lo dio, y cada transferencia de control es un paso 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"))Una trayectoria nombra al agente de cada paso o de ninguno; una que solo nombra algunos se rechaza. Un caso declara la ruta que debería 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"}}Evaluadores y 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 los agentes que tuvieron el control (repeticiones colapsadas, incluido el receptor de un traspaso) con expected.route. Una comprobación de enrutamiento, no de éxito.
- agent_tool_permissions comprueba cada llamada frente a un mapa cerrado: un agente que el mapa no enumera no puede llamar a ninguna herramienta.
- agent_max_handoffs limita cuántas veces cambió de manos el control.
release.yaml tiene answer-floor (min: 0.80), routing-floor (min: 0.70) y no-overreach, una regla de recuento observado con max_failures: 0 sobre agent_tool_permissions.
Ejecutar el equipo
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 y case_020: tech emitió un reembolso, una herramienta que solo tiene billing. Ambos respondieron correctamente. Éxito de la tarea, permiso incumplido.
- case_005 y otros dos fueron primero al especialista equivocado y volvieron a través de triage: la respuesta es correcta, la ruta y el límite de traspasos no.
- case_030 rebotó entre billing y tech hasta el límite del bucle. Su traza truncada ya demuestra los fallos de ruta y de traspasos, y no puede zanjar los permisos, así que ese criterio queda ausente para él en lugar de aprobado.
Nada en la salida dice qué agente tiene la culpa. Una divergencia de ruta dice dónde se separan dos rutas; que un agente causó un fallo es una afirmación sobre lo que habría ocurrido si hubiera actuado de otro modo, y ninguna comprobación de aquí la hace.
Cambiar el equipo y comparar
La siguiente acción con sentido para los fallos de permisos: tech entrega un reembolso a billing en lugar de emitirlo. En app.py, en 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"]))Con un compare.yaml que contiene answers-not-worse sobre answer_correct y routing-not-worse sobre agent_route, ambos non_inferiority con margin: 0.05:
oloproof run
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlLa ejecución candidata por sí sola:
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 │y la comparación:
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)Tres cosas que sacar de ello:
- Ninguna llamada observada incumplió un permiso, pero no-overreach está ahora en INSUFFICIENT_EVIDENCE en lugar de PASS: el case_030 truncado podría ocultar una infracción en la parte no registrada (missing_could_change_outcome). Lo que zanjaría la regla es corregir ese bucle, no el mapa de permisos.
- La corrección cambió la ruta de los dos casos a triage > tech > billing, que su expected.route no declara, así que agent_route y agent_handoffs_le_2 bajaron. Si esa ruta es ahora correcta es una decisión de producto: si lo es, actualice el expected.route de los casos; una comprobación de enrutamiento mide la conformidad con lo que usted declaró, no la calidad.
- La línea de planificación dice que más casos llevarían routing-not-worse hacia FAIL, no hacia PASS. La comparación le está diciendo que el candidato, tal como está escrito, cambia enrutamiento por permisos.
Solución de problemas
| Síntoma | Causa | Solución |
|---|---|---|
| Los evaluadores de agente se niegan a ejecutarse | Falta records: [agent_trajectory/v1] en el sistema | Declárelo en oloproof.yaml y en @system |
| Se rechaza una trayectoria | Algunos pasos nombran un agent y otros no | Nombre el agente de cada paso, o de ninguno |
| Muchos casos missing en un criterio | Trazas truncadas: el bucle alcanzó su límite | Suba el límite o corrija el bucle; los casos ausentes ensanchan el intervalo en lugar de aprobar |
| agent_tool_sequence tiene un denominador pequeño | Casos sin expected.tools | Declare la secuencia donde importe; [] significa "no espera ninguna herramienta" |
| Una métrica de restricción nunca falla | La aplicación no registra esa comprobación | Registre un AgentConstraintCheck donde su entorno la observe |
| Los resultados difieren entre ejecuciones de la misma versión | Las herramientas leen o escriben estado compartido | Reinicie ese estado antes de cada caso en su aplicación; Oloproof no lo hace |
| Un agente añadido al equipo falla los permisos de inmediato | El mapa de permisos es cerrado | Declare lo que el nuevo agente puede llamar |
Limitaciones
- Oloproof no dirige, aísla ni reinicia un agente. Los efectos secundarios de las herramientas, las sesiones, el estado y su reinicio corresponden a su aplicación.
- Cada comprobación lee la trayectoria registrada. Lo que la aplicación no registra no se puede medir, y una traza truncada cuenta como ausente allí donde su prefijo no zanja la pregunta.
- Las comprobaciones de trayectoria son reglas deterministas. No hay comprobación de calidad de trayectoria juzgada por un LLM.
- Ninguna salida atribuye un fallo a un paso o a un agente. La repetición de agentes, que vuelve a ejecutar un caso desde un punto de control registrado con un paso eliminado para etiquetarlo como necesario o innecesario, existe solo en el SDK de Python (replay_case), para un sistema que implementa la repetición desde sus puntos de control; no hay comando de CLI para ella, y nada de lo anterior la usa.
- Las conversaciones de varios turnos son otra superficie (solo SDK); vea Lo que funciona hoy.
- Los campos plan y behaviour de los ejemplos sustituyen a las decisiones de un modelo para que las ejecuciones sean reproducibles. Un modelo real en su bucle llama a un proveedor, necesita credenciales y cuesta dinero por caso.