Handleidingen
Tutorial: een agent evalueren
Een uitvoerbare doorloop voor een agent die tools gebruikt en voor een team van agents: leg vast wat de agent deed als traject, controleer zijn toolgebruik, beperkingen, stappen, routering, rechten en overdrachten, laat een release ervan afhangen, en vergelijk een kandidaatwijziging met de baseline. Beide voorbeelden draaien lokaal zonder providergegevens.
De referentie voor elk veld en elke evaluator is Agents en tools; de termen case, evaluator, metriek, interval en gate staan in Kernbegrippen. Deze pagina is de praktische route erdoorheen.
Wat Oloproof hier wel en niet doet
Oloproof stuurt je agent niet aan. Je applicatie draait haar eigen lus, roept haar eigen tools aan, en legt vast wat er gebeurde als een agent_trajectory/v1-artefact. Elke agentmetriek wordt uit dat record gelezen.
Je applicatie beheert ook alles wat de tools aanraken. Oloproof biedt geen sandbox, geen gesimuleerde tools en geen reset tussen cases: als een tool tijdens een evaluatie naar een database schrijft, een e-mail stuurt of een kaart belast, gebeurt dat echt. Laat de agent werken met testaccounts, gestubde tools of een wegwerpomgeving, en zet de toestand tussen cases zelf terug, voordat je een evaluatie draait.
Houd twee soorten vragen uit elkaar:
| Vraag | Gecontroleerd door | Voorbeeld |
|---|---|---|
| Kreeg de gebruiker de juiste uitkomst? (taaksucces) | Een outputcontrole zoals contains, of een judge | answer_correct |
| Gedroeg de agent zich onderweg zoals toegestaan? | Trajectcontroles: toolkeuze, volgorde, lussen, beperkingen, stappen, routering, rechten, overdrachten | agent_constraints_satisfied, agent_route |
Ze zijn het op nuttige manieren oneens. In beide voorbeelden hieronder antwoorden sommige cases correct en breken ze toch een regel, en alleen een trajectcontrole ziet dat. Een geslaagde trajectcontrole zegt evenmin iets over of de taak slaagde.
Vereisten
- Python 3.11 of later, en Oloproof geïnstalleerd (pip install oloproof).
- De voorbeeldprojecten, die met het pakket worden meegeleverd: support_agent (één agent) en triage_agents (drie). Kopieer er een naar een nieuwe map en werk daar:
oloproof init --example support_agent my-agent
cd my-agentElk commando hieronder draait vanuit de gekopieerde map. Bewijs wordt daar opgeslagen in .oloproof/.
Deel 1: één agent die tools gebruikt
De bestanden
| Bestand | Wat het is |
|---|---|
| app.py | De agent: Tools, een plan dat de beslissingen van het model vervangt, en run(case), zijn lus, die het traject vastlegt |
| data/refunds.jsonl | 40 terugbetalingsverzoeken, elk met het verwachte antwoord en, voor de meeste, de verwachte tools |
| data/orders.jsonl | De bestellingen die de tool lookup_order leest |
| oloproof.yaml | De suite: dataset, systeem, evaluators, verdelingsmetrieken, slices |
| release.yaml | Het releasebeleid |
Het traject vastleggen
run is het hele integratieoppervlak. Het roept elke tool aan, voegt een AgentStep toe voor de aanroep en een voor het resultaat, legt de controles op beperkingen vast die de eigen omgeving deed, en geeft het traject aan de caserecorder:
@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"}De vorm van het artefact:
| Veld | Wat het vastlegt |
|---|---|
| steps | Elke AgentStep: index, kind (message, tool_call, tool_result, observation, decision, final of handoff), tool_name, arguments, result, en voor teams agent en to_agent |
| terminal_status | success, failure of unknown, zoals de agent het zag |
| truncated, step_limit | Dat de lus zijn grens bereikte en het record te vroeg stopt |
| constraints | AgentConstraintCheck(name, passed, step_index): controles die je omgeving deed, zoals "er is geen klant verwijderd" |
| checkpoints | AgentCheckpoints waarvandaan een replay zou kunnen hervatten (zie Beperkingen) |
Om je eigen agent te gebruiken, houd je het vastleggen en vervang je de lus: roep je framework aan in run, en vertaal zijn stappen naar AgentStep terwijl ze gebeuren. Het systeem declareert records: [agent_trajectory/v1] in oloproof.yaml; zonder dat weigeren de agentevaluators te draaien in plaats van elke case als ontbrekend te tellen.
Wat een case declareert
{"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 is voor de taakcontrole. expected.tools is de toolvolgorde die de case moet volgen; laat je die weg, dan gelden controles op de toolvolgorde niet voor de case (hij verlaat hun noemer in plaats van te slagen). behaviour is hoe dit deterministische voorbeeld kiest wat zijn vervangende agent doet; jouw cases bevatten alleen echte inputs.
Evaluators kiezen
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 is de taakcontrole.
- agent_tool_called vraagt om een vereiste tool; agent_tool_sequence vergelijkt de aanroepen met expected.tools; agent_no_tool_loop markeert dezelfde aanroep die vaker dan max_repeats keer achter elkaar wordt herhaald. Deze beschrijven toolgebruik, geen succes.
- agent_constraints_satisfied leest de controles die je omgeving vastlegde. Oloproof neemt neveneffecten niet zelf waar, dus een beperking die je applicatie niet vastlegt kan niet worden gecontroleerd.
- agent_max_steps begrenst elke run; de twee kwantielmetrieken tonen de verdeling, zodat een wijziging die elke run langer maakt zichtbaar is voordat een enkele run de grens raakt.
Het releasebeleid
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 is een observed-count-regel: "dit mag niet gebeuren in de suite die we draaiden" heeft geen interval nodig. Zie Gating.
Draai het
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 missZo lees je het:
- Exit 1: een regel kreeg FAIL. Drie cases riepen delete_customer aan, en de omgeving legde de beperking vast als gebroken.
- answer-floor is INSUFFICIENT_EVIDENCE hoewel 82,5% boven 70% ligt: met 40 cases reikt het interval nog tot 67,2%.
- 1 missing: één case raakte de stapgrens, dus zijn trace is afgekapt. Een afgekapte trace bewijst sommige dingen (hij overschreed wel degelijk 10 stappen) en laat andere open (een vereiste tool kan in het niet vastgelegde deel zitten), dus die criteria tellen hem als ontbrekend, en het interval laat beide uitkomsten toe.
De mislukkingen bekijken
oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case case_035Het tweede commando print één case volledig. Ingekort:
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: passedDe klant kreeg de terugbetaling (taaksucces) van een agent die onderweg een klant verwijderde (een gebroken beperking). Geen van beide resultaten impliceert het andere. Het volledige traject, elke stap met zijn argumenten en resultaat, staat in de geëxporteerde bundel (oloproof export RUN_ID) en in de caseweergave van de workbench. De zinvolle volgende stap ligt in de applicatie: voorkom dat de lus een tool aanroept die hij nooit mag aanroepen.
Een kandidaatwijziging maken en vergelijken
Laat in app.py de lus de verboden tool weigeren:
for name, arguments in plan(case):
if name == FORBIDDEN:
continue # the candidate: the loop refuses the forbidden toolSchrijf een vergelijkingsbeleid, 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.yamlDe kandidaatrun op zichzelf: no-deletion krijgt nu PASS, agent_constraints_satisfied leest 40 / 40, en de gate blokkeert met exit 3 omdat de twee ondergrenzen nog INSUFFICIENT_EVIDENCE zijn. De vergelijking:
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)Lees de twee helften apart. Op deze suite haalde de wijziging elke waargenomen verwijdering weg, wat de observed-count-regel van de enkele run al beslist. Of de kandidaat in het algemeen niet slechter is dan de baseline is een andere vraag, en 40 gepaarde cases kunnen dat nog niet vaststellen binnen een marge van 5 punten; de planningsregel zegt ongeveer hoeveel meer dat wel zouden doen. Geen antwoord veranderde, dus het taaksucces blijft door de fix onaangeroerd.
Deel 2: een team van agents
oloproof init --example triage_agents my-team
cd my-teamDe bestanden en het vastleggen
app.py draait drie agents in één lus: triage geeft elk verzoek aan billing of tech, elke specialist roept zijn eigen tools aan, en een terugbetaling die billing niet mag uitvoeren wordt aan een mens overgedragen. Elke stap noemt de agent die hem zette, en elke overdracht van controle is een handoff-stap:
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"))Een traject noemt de agent van elke stap of van geen enkele; een traject dat er maar enkele noemt wordt geweigerd. Een case declareert de route die hij moet nemen:
{"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"}}Evaluators en beleid
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 vergelijkt de agents die de controle hadden (herhalingen samengevoegd, de ontvanger van een overdracht inbegrepen) met expected.route. Een routeringscontrole, geen succescontrole.
- agent_tool_permissions controleert elke aanroep tegen een gesloten map: een agent die de map niet noemt mag geen enkele tool aanroepen.
- agent_max_handoffs begrenst hoe vaak de controle van hand wisselde.
release.yaml heeft answer-floor (min: 0.80), routing-floor (min: 0.70) en no-overreach, een observed-count-regel met max_failures: 0 op agent_tool_permissions.
Het team draaien
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 en case_020: tech voerde een terugbetaling uit, een tool die alleen billing heeft. Beide antwoordden correct. Taaksucces, recht gebroken.
- case_005 en twee andere gingen eerst naar de verkeerde specialist en kwamen terug via triage: het antwoord klopt, de route en de overdrachtsgrens niet.
- case_030 kaatste heen en weer tussen billing en tech tot de grens van de lus. Zijn afgekapte trace bewijst de route- en overdrachtsmislukkingen al, en kan de rechten niet beslissen, dus dat criterium is voor hem ontbrekend in plaats van geslaagd.
Niets in de output zegt welke agent de schuld heeft. Een routeafwijking zegt waar twee routes uiteengaan; dat een agent een mislukking veroorzaakte is een bewering over wat er gebeurd zou zijn als hij anders had gehandeld, en die doet geen enkele controle hier.
Het team wijzigen en vergelijken
De zinvolle volgende stap voor de rechtenmislukkingen: tech draagt een terugbetaling over aan billing in plaats van hem zelf uit te voeren. 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"]))Met een compare.yaml die answers-not-worse op answer_correct en routing-not-worse op agent_route bevat, beide non_inferiority met margin: 0.05:
oloproof run
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlDe kandidaatrun op zichzelf:
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 │en de vergelijking:
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)Drie dingen om mee te nemen:
- Geen waargenomen aanroep brak een recht, maar no-overreach is nu INSUFFICIENT_EVIDENCE in plaats van PASS: de afgekapte case_030 kan een overtreding verbergen in het niet vastgelegde deel (missing_could_change_outcome). Die lus repareren, niet de rechtenmap, is wat de regel zou beslissen.
- De fix veranderde de route van de twee cases in triage > tech > billing, wat hun expected.route niet declareert, dus agent_route en agent_handoffs_le_2 daalden. Of die route nu correct is, is een productbeslissing: als dat zo is, werk dan de expected.route van de cases bij; een routeringscontrole meet overeenstemming met wat je declareerde, niet kwaliteit.
- De planningsregel zegt dat meer cases routing-not-worse richting FAIL zouden bewegen, niet richting PASS. De vergelijking vertelt je dat de kandidaat zoals hij geschreven is routering inruilt voor rechten.
Problemen oplossen
| Symptoom | Oorzaak | Oplossing |
|---|---|---|
| Agentevaluators weigeren te draaien | records: [agent_trajectory/v1] ontbreekt op het systeem | Declareer het in oloproof.yaml en op @system |
| Een traject wordt geweigerd | Sommige stappen noemen een agent en andere niet | Noem de agent van elke stap, of van geen enkele |
| Veel cases missing op een criterium | Afgekapte traces: de lus raakte zijn grens | Verhoog de grens, of repareer de lus; ontbrekende cases maken het interval breder in plaats van te slagen |
| agent_tool_sequence heeft een kleine noemer | Cases zonder expected.tools | Declareer de volgorde waar die ertoe doet; [] betekent "verwacht geen tool" |
| Een beperkingsmetriek faalt nooit | De applicatie legt die controle niet vast | Leg een AgentConstraintCheck vast waar je omgeving het waarneemt |
| Resultaten verschillen tussen runs van dezelfde versie | Tools lezen of schrijven gedeelde toestand | Zet die toestand in je applicatie terug voor elke case; Oloproof doet dat niet |
| Een agent die aan het team is toegevoegd faalt meteen op rechten | De rechtenmap is gesloten | Declareer wat de nieuwe agent mag aanroepen |
Beperkingen
- Oloproof stuurt een agent niet aan, sandboxt hem niet en zet hem niet terug. Neveneffecten van tools, sessies, toestand en het terugzetten ervan horen bij je applicatie.
- Elke controle leest het vastgelegde traject. Wat de applicatie niet vastlegt kan niet worden gemeten, en een afgekapte trace telt als ontbrekend waar zijn begin de vraag niet beslist.
- Trajectcontroles zijn deterministische regels. Er is geen controle op trajectkwaliteit die door een LLM wordt beoordeeld.
- Geen enkele output schrijft een mislukking toe aan een stap of een agent. Agent-replay, die een case opnieuw draait vanaf een vastgelegd checkpoint met een weggelaten stap om die als nodig of onnodig te labelen, bestaat alleen in de Python-SDK (replay_case), voor een systeem dat replay vanaf zijn checkpoints implementeert; er is geen CLI-commando voor, en niets hierboven gebruikt het.
- Gesprekken met meerdere beurten zijn een ander oppervlak (alleen SDK); zie Wat vandaag werkt.
- De velden plan en behaviour van de voorbeelden vervangen de beslissingen van een model zodat de runs reproduceerbaar zijn. Een live model in je lus roept een provider aan, heeft inloggegevens nodig en kost geld per case.