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:
| Domanda | Verificata da | Esempio |
|---|---|---|
| L'utente ha ottenuto l'esito giusto? (successo del compito) | Un controllo sull'output come contains, o un giudice | answer_correct |
| L'agente si è comportato come consentito lungo il percorso? | Controlli sulla traiettoria: scelta degli strumenti, ordine, cicli, vincoli, passi, instradamento, permessi, passaggi di consegne | agent_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-agentOgni 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
| File | Che cos'è |
|---|---|
| app.py | L'agente: Tools, un plan che fa le veci delle decisioni del modello, e run(case), il suo ciclo, che registra la traiettoria |
| data/refunds.jsonl | 40 richieste di rimborso, ciascuna con la risposta attesa e, per la maggior parte, gli strumenti attesi |
| data/orders.jsonl | Gli ordini che lo strumento lookup_order legge |
| oloproof.yaml | La suite: dataset, sistema, valutatori, metriche di distribuzione, slice |
| release.yaml | La 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:
| Campo | Che cosa registra |
|---|---|
| steps | Ogni 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_status | success, failure o unknown, come l'ha visto l'agente |
| truncated, step_limit | Che il ciclo ha raggiunto il suo limite e il record si interrompe prima |
| constraints | AgentConstraintCheck(name, passed, step_index): controlli fatti dal tuo ambiente, come "nessun cliente è stato eliminato" |
| checkpoints | Gli 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: 0no-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 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 missCome 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_035Il 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: passedIl 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 toolScrivi 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.05oloproof run
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlL'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-teamI 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 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 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.yamlL'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
| Sintomo | Causa | Soluzione |
|---|---|---|
| I valutatori di agente si rifiutano di girare | records: [agent_trajectory/v1] mancante sul sistema | Dichiaralo in oloproof.yaml e su @system |
| Una traiettoria viene rifiutata | Alcuni passi nominano un agent e altri no | Nomina l'agente di ogni passo, o di nessuno |
| Molti casi missing su un criterio | Tracce troncate: il ciclo ha raggiunto il suo limite | Alza il limite, o correggi il ciclo; i casi mancanti allargano l'intervallo invece di passare |
| agent_tool_sequence ha un denominatore piccolo | Casi senza expected.tools | Dichiara la sequenza dove conta; [] significa "non si aspetta alcuno strumento" |
| Una metrica di vincolo non fallisce mai | L'applicazione non registra quel controllo | Registra un AgentConstraintCheck dove il tuo ambiente lo osserva |
| I risultati differiscono tra esecuzioni della stessa versione | Gli strumenti leggono o scrivono uno stato condiviso | Reimposta quello stato prima di ogni caso nella tua applicazione; Oloproof non lo fa |
| Un agente aggiunto al team fallisce subito i permessi | La mappa dei permessi è chiusa | Dichiara 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.