Vai al contenuto

Guide

Tutorial: valutare un agente

Un percorso eseguibile per un agente che usa strumenti e per un team di agenti: registra ciò che l'agente ha fatto come traiettoria, controlla il suo uso degli strumenti, i vincoli, i passi, l'instradamento, i permessi e i passaggi di consegne, fai da gate a un rilascio su questi controlli, e confronta una modifica candidata con la baseline. Entrambi gli esempi girano in locale senza credenziali di un provider.

Il riferimento per ogni campo e valutatore è Agenti e strumenti; i termini caso, valutatore, metrica, intervallo e gate sono in Concetti fondamentali. Questa pagina è il percorso pratico attraverso di essi.

Che cosa fa e che cosa non fa Oloproof qui

Oloproof non pilota il tuo agente. La tua applicazione esegue il proprio ciclo, chiama i propri strumenti e registra ciò che è accaduto come artefatto agent_trajectory/v1. Ogni metrica di agente viene letta da quel record.

La tua applicazione possiede anche tutto ciò che gli strumenti toccano. Oloproof non fornisce alcuna sandbox, nessuno strumento simulato e nessuna reimpostazione tra un caso e l'altro: se uno strumento scrive in un database, invia un'email o addebita una carta durante una valutazione, lo fa davvero. Fai puntare l'agente ad account di test, strumenti fittizi o un ambiente usa e getta, e reimposta tu lo stato tra un caso e l'altro, prima di eseguire una valutazione.

Tieni separati due tipi di domanda:

DomandaVerificata daEsempio
L'utente ha ottenuto l'esito giusto? (successo del compito)Un controllo sull'output come contains, o un giudiceanswer_correct
L'agente si è comportato come consentito lungo il percorso?Controlli sulla traiettoria: scelta degli strumenti, ordine, cicli, vincoli, passi, instradamento, permessi, passaggi di consegneagent_constraints_satisfied, agent_route

Divergono in modi utili. In entrambi gli esempi qui sotto alcuni casi rispondono correttamente e violano comunque una regola, e solo un controllo sulla traiettoria lo vede. Neanche il superamento di un controllo sulla traiettoria dice qualcosa sul successo del compito.

Prerequisiti

  • Python 3.11 o successivo, e Oloproof installato (pip install oloproof).
  • I progetti di esempio, distribuiti con il pacchetto: support_agent (un agente) e triage_agents (tre). Copiane uno in una nuova directory e lavora lì:
oloproof init --example support_agent my-agent
cd my-agent

Ogni comando qui sotto si esegue dall'interno della directory copiata. L'evidenza viene memorizzata lì, in .oloproof/.

Parte 1: un agente che usa strumenti

I file

FileChe cos'è
app.pyL'agente: Tools, un plan che fa le veci delle decisioni del modello, e run(case), il suo ciclo, che registra la traiettoria
data/refunds.jsonl40 richieste di rimborso, ciascuna con la risposta attesa e, per la maggior parte, gli strumenti attesi
data/orders.jsonlGli ordini che lo strumento lookup_order legge
oloproof.yamlLa suite: dataset, sistema, valutatori, metriche di distribuzione, slice
release.yamlLa policy di rilascio

Registrare la traiettoria

run è l'intera superficie di integrazione. Chiama ogni strumento, aggiunge un AgentStep per la chiamata e uno per il suo risultato, registra i controlli sui vincoli fatti dal suo ambiente e consegna la traiettoria al recorder 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 dell'artefatto:

CampoChe cosa registra
stepsOgni AgentStep: index, kind (message, tool_call, tool_result, observation, decision, final o handoff), tool_name, arguments, result, e per i team agent e to_agent
terminal_statussuccess, failure o unknown, come l'ha visto l'agente
truncated, step_limitChe il ciclo ha raggiunto il suo limite e il record si interrompe prima
constraintsAgentConstraintCheck(name, passed, step_index): controlli fatti dal tuo ambiente, come "nessun cliente è stato eliminato"
checkpointsGli AgentCheckpoint da cui una riesecuzione potrebbe ripartire (vedi Limitazioni)

Per usare il tuo agente, conserva la registrazione e sostituisci il ciclo: chiama il tuo framework in run, e traduci i suoi passi in AgentStep mentre avvengono. Il sistema dichiara records: [agent_trajectory/v1] in oloproof.yaml; senza di esso, i valutatori di agente si rifiutano di girare invece di contare ogni caso come mancante.

Che cosa dichiara 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 serve al controllo del compito. expected.tools è la sequenza di strumenti che il caso dovrebbe seguire; se la ometti, i controlli sulla sequenza di strumenti non si applicano al caso (esce dal loro denominatore invece di passare). behaviour è il modo in cui questo esempio deterministico sceglie che cosa fa il suo agente sostitutivo; i tuoi casi portano solo input reali.

Scegliere i valutatori

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 è il controllo del compito.
  • agent_tool_called richiede uno strumento obbligatorio; agent_tool_sequence confronta le chiamate con expected.tools; agent_no_tool_loop segnala la stessa chiamata ripetuta più di max_repeats volte di fila. Questi descrivono l'uso degli strumenti, non il successo.
  • agent_constraints_satisfied legge i controlli registrati dal tuo ambiente. Oloproof non osserva da sé gli effetti collaterali, quindi un vincolo che la tua applicazione non registra non può essere verificato.
  • agent_max_steps limita ogni esecuzione; le due metriche di quantile mostrano la distribuzione, così una modifica che allunga ogni esecuzione è visibile prima che una singola esecuzione raggiunga il limite.

La policy di rilascio

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 è una regola a conteggio osservato: "questo non deve accadere nella suite che abbiamo eseguito" non ha bisogno di un intervallo. Vedi Gate.

Eseguila

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

Come leggerlo:

  • Uscita 1: una regola è in FAIL. Tre casi hanno chiamato delete_customer, e l'ambiente ha registrato il vincolo come violato.
  • answer-floor è INSUFFICIENT_EVIDENCE anche se 82.5% è sopra 70%: con 40 casi l'intervallo arriva ancora a 67.2%.
  • 1 missing: un caso ha raggiunto il limite di passi, quindi la sua traccia è troncata. Una traccia troncata dimostra alcune cose (ha davvero superato 10 passi) e ne lascia aperte altre (uno strumento obbligatorio potrebbe trovarsi nella parte non registrata), quindi quei criteri la contano come mancante, e l'intervallo ammette che sia andata in un senso o nell'altro.

Esamina i fallimenti

oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case case_035

Il secondo stampa un caso per intero. Ridotto:

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

Il cliente ha ottenuto il rimborso (successo del compito) da un agente che lungo il percorso ha eliminato un cliente (un vincolo violato). Nessuno dei due risultati implica l'altro. La traiettoria completa, ogni passo con i suoi argomenti e il suo risultato, è nel bundle esportato (oloproof export RUN_ID) e nella vista del caso nel workbench. La prossima azione sensata è nell'applicazione: impedire al ciclo di chiamare uno strumento che non deve mai chiamare.

Fai una modifica candidata e confronta

In app.py, fai in modo che il ciclo rifiuti lo strumento vietato:

    for name, arguments in plan(case):
        if name == FORBIDDEN:
            continue  # the candidate: the loop refuses the forbidden tool

Scrivi una policy di confronto, 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

L'esecuzione candidata da sola: no-deletion ora è in PASS, agent_constraints_satisfied riporta 40 / 40, e il gate blocca con uscita 3 perché le due soglie minime sono ancora INSUFFICIENT_EVIDENCE. Il confronto:

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)

Leggi separatamente le due metà. Su questa suite, la modifica ha eliminato ogni cancellazione osservata, cosa che la regola a conteggio osservato sulla singola esecuzione già stabilisce. Se il candidato non sia peggiore della baseline in generale è una domanda diversa, e 40 casi appaiati non possono ancora stabilirlo entro un margine di 5 punti; la riga di pianificazione dice all'incirca quanti altri servirebbero. Nessuna risposta è cambiata, quindi il successo del compito non è toccato dalla correzione.

Parte 2: un team di agenti

oloproof init --example triage_agents my-team
cd my-team

I file e la registrazione

app.py esegue tre agenti in un unico ciclo: triage passa ogni richiesta a billing o a tech, ciascuno specialista chiama i propri strumenti, e un rimborso che billing non può emettere viene passato a una persona. Ogni passo nomina l'agente che lo ha compiuto, e ogni trasferimento di controllo è un 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"))

Una traiettoria nomina l'agente di ogni passo oppure di nessuno; una che ne nomina solo alcuni viene rifiutata. Un caso dichiara l'instradamento che dovrebbe seguire:

{"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"}}

Valutatori e policy

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 confronta gli agenti che hanno avuto il controllo (ripetizioni compresse, destinatario di un passaggio di consegne incluso) con expected.route. Un controllo di instradamento, non di successo.
  • agent_tool_permissions verifica ogni chiamata rispetto a una mappa chiusa: un agente che la mappa non elenca non può chiamare alcuno strumento.
  • agent_max_handoffs limita quante volte il controllo è passato di mano.

release.yaml ha answer-floor (min: 0.80), routing-floor (min: 0.70) e no-overreach, una regola a conteggio osservato con max_failures: 0 su agent_tool_permissions.

Esegui il team

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 ha emesso un rimborso, uno strumento che solo billing possiede. Entrambi hanno risposto correttamente. Compito riuscito, permesso violato.
  • case_005 e altri due sono andati prima dallo specialista sbagliato e sono tornati passando per triage: la risposta è giusta, l'instradamento e il limite sui passaggi di consegne no.
  • case_030 è rimbalzato tra billing e tech fino al limite del ciclo. La sua traccia troncata dimostra già i fallimenti di instradamento e di passaggio di consegne, e non può stabilire i permessi, quindi quel criterio è mancante per esso invece che passato.

Nulla nell'output dice quale agente sia da incolpare. Una divergenza di instradamento dice dove due instradamenti si separano; che un agente abbia causato un fallimento è un'affermazione su che cosa sarebbe accaduto se avesse agito diversamente, e nessun controllo qui la fa.

Modifica il team e confronta

La prossima azione sensata per i fallimenti di permesso: tech passa un rimborso a billing invece di emetterlo. In app.py, in 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 che contiene answers-not-worse su answer_correct e routing-not-worse su agent_route, entrambe non_inferiority con margin: 0.05:

oloproof run
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml

L'esecuzione candidata da 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 │

e il confronto:

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)

Tre cose da trarne:

  • Nessuna chiamata osservata ha violato un permesso, ma no-overreach ora è INSUFFICIENT_EVIDENCE invece di PASS: il case_030 troncato potrebbe nascondere una violazione nella parte non registrata (missing_could_change_outcome). È correggere quel ciclo, non la mappa dei permessi, ciò che stabilirebbe la regola.
  • La correzione ha cambiato l'instradamento dei due casi in triage > tech > billing, che il loro expected.route non dichiara, quindi agent_route e agent_handoffs_le_2 sono scesi. Se quell'instradamento sia ora corretto è una decisione di prodotto: se lo è, aggiorna l'expected.route dei casi; un controllo di instradamento misura la conformità a ciò che hai dichiarato, non la qualità.
  • La riga di pianificazione dice che più casi sposterebbero routing-not-worse verso FAIL, non verso PASS. Il confronto ti sta dicendo che il candidato, così com'è scritto, scambia l'instradamento con i permessi.

Risoluzione dei problemi

SintomoCausaSoluzione
I valutatori di agente si rifiutano di girarerecords: [agent_trajectory/v1] mancante sul sistemaDichiaralo in oloproof.yaml e su @system
Una traiettoria viene rifiutataAlcuni passi nominano un agent e altri noNomina l'agente di ogni passo, o di nessuno
Molti casi missing su un criterioTracce troncate: il ciclo ha raggiunto il suo limiteAlza il limite, o correggi il ciclo; i casi mancanti allargano l'intervallo invece di passare
agent_tool_sequence ha un denominatore piccoloCasi senza expected.toolsDichiara la sequenza dove conta; [] significa "non si aspetta alcuno strumento"
Una metrica di vincolo non fallisce maiL'applicazione non registra quel controlloRegistra un AgentConstraintCheck dove il tuo ambiente lo osserva
I risultati differiscono tra esecuzioni della stessa versioneGli strumenti leggono o scrivono uno stato condivisoReimposta quello stato prima di ogni caso nella tua applicazione; Oloproof non lo fa
Un agente aggiunto al team fallisce subito i permessiLa mappa dei permessi è chiusaDichiara che cosa può chiamare il nuovo agente

Limitazioni

  • Oloproof non pilota, non isola in una sandbox e non reimposta un agente. Effetti collaterali degli strumenti, sessioni, stato e la loro reimpostazione appartengono alla tua applicazione.
  • Ogni controllo legge la traiettoria registrata. Ciò che l'applicazione non registra non può essere misurato, e una traccia troncata conta come mancante ovunque il suo prefisso non risolva la questione.
  • I controlli sulla traiettoria sono regole deterministiche. Non esiste alcun controllo della qualità della traiettoria giudicato da un LLM.
  • Nessun output attribuisce un fallimento a un passo o a un agente. La riesecuzione dell'agente, che riesegue un caso da un checkpoint registrato con un passo eliminato per etichettarlo come necessario o non necessario, esiste solo nell'SDK Python (replay_case), per un sistema che implementa la riesecuzione dai propri checkpoint; non esiste un comando CLI per essa, e nulla di quanto sopra la usa.
  • Le conversazioni a più turni sono una superficie diversa (solo SDK); vedi Che cosa funziona oggi.
  • I campi plan e behaviour degli esempi fanno le veci delle decisioni di un modello, così che le esecuzioni siano riproducibili. Un modello reale nel tuo ciclo chiama un provider, richiede credenziali e costa denaro per caso.