Saltar al contenido

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:

PreguntaSe comprueba conEjemplo
¿Obtuvo el usuario el resultado correcto? (éxito de la tarea)Una comprobación de la salida como contains, o un juezanswer_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, traspasosagent_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-agent

Cada 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

ArchivoQué es
app.pyEl agente: Tools, un plan que sustituye a las decisiones del modelo, y run(case), su bucle, que registra la trayectoria
data/refunds.jsonl40 solicitudes de reembolso, cada una con la respuesta esperada y, en la mayoría, las herramientas esperadas
data/orders.jsonlLos pedidos que lee la herramienta lookup_order
oloproof.yamlLa suite: conjunto de datos, sistema, evaluadores, métricas de distribución, segmentos
release.yamlLa 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:

CampoQué registra
stepsCada 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_statussuccess, failure o unknown, tal como lo vio el agente
truncated, step_limitQue el bucle alcanzó su límite y el registro se corta antes
constraintsAgentConstraintCheck(name, passed, step_index): comprobaciones que hizo su entorno, como "no se borró ningún cliente"
checkpointsAgentCheckpoints 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: 0

no-deletion es una regla de recuento observado: "esto no debe ocurrir en la suite que ejecutamos" no necesita intervalo. Vea Gates.

Ejecútelo

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

Có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_035

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

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

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

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

Los 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 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 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.yaml

La 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íntomaCausaSolución
Los evaluadores de agente se niegan a ejecutarseFalta records: [agent_trajectory/v1] en el sistemaDeclárelo en oloproof.yaml y en @system
Se rechaza una trayectoriaAlgunos pasos nombran un agent y otros noNombre el agente de cada paso, o de ninguno
Muchos casos missing en un criterioTrazas truncadas: el bucle alcanzó su límiteSuba el límite o corrija el bucle; los casos ausentes ensanchan el intervalo en lugar de aprobar
agent_tool_sequence tiene un denominador pequeñoCasos sin expected.toolsDeclare la secuencia donde importe; [] significa "no espera ninguna herramienta"
Una métrica de restricción nunca fallaLa aplicación no registra esa comprobaciónRegistre un AgentConstraintCheck donde su entorno la observe
Los resultados difieren entre ejecuciones de la misma versiónLas herramientas leen o escriben estado compartidoReinicie ese estado antes de cada caso en su aplicación; Oloproof no lo hace
Un agente añadido al equipo falla los permisos de inmediatoEl mapa de permisos es cerradoDeclare 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.