Ga naar de inhoud

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:

VraagGecontroleerd doorVoorbeeld
Kreeg de gebruiker de juiste uitkomst? (taaksucces)Een outputcontrole zoals contains, of een judgeanswer_correct
Gedroeg de agent zich onderweg zoals toegestaan?Trajectcontroles: toolkeuze, volgorde, lussen, beperkingen, stappen, routering, rechten, overdrachtenagent_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-agent

Elk commando hieronder draait vanuit de gekopieerde map. Bewijs wordt daar opgeslagen in .oloproof/.

Deel 1: één agent die tools gebruikt

De bestanden

BestandWat het is
app.pyDe agent: Tools, een plan dat de beslissingen van het model vervangt, en run(case), zijn lus, die het traject vastlegt
data/refunds.jsonl40 terugbetalingsverzoeken, elk met het verwachte antwoord en, voor de meeste, de verwachte tools
data/orders.jsonlDe bestellingen die de tool lookup_order leest
oloproof.yamlDe suite: dataset, systeem, evaluators, verdelingsmetrieken, slices
release.yamlHet 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:

VeldWat het vastlegt
stepsElke 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_statussuccess, failure of unknown, zoals de agent het zag
truncated, step_limitDat de lus zijn grens bereikte en het record te vroeg stopt
constraintsAgentConstraintCheck(name, passed, step_index): controles die je omgeving deed, zoals "er is geen klant verwijderd"
checkpointsAgentCheckpoints 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: 0

no-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 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

Zo 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_035

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

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

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

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

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

De 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

SymptoomOorzaakOplossing
Agentevaluators weigeren te draaienrecords: [agent_trajectory/v1] ontbreekt op het systeemDeclareer het in oloproof.yaml en op @system
Een traject wordt geweigerdSommige stappen noemen een agent en andere nietNoem de agent van elke stap, of van geen enkele
Veel cases missing op een criteriumAfgekapte traces: de lus raakte zijn grensVerhoog de grens, of repareer de lus; ontbrekende cases maken het interval breder in plaats van te slagen
agent_tool_sequence heeft een kleine noemerCases zonder expected.toolsDeclareer de volgorde waar die ertoe doet; [] betekent "verwacht geen tool"
Een beperkingsmetriek faalt nooitDe applicatie legt die controle niet vastLeg een AgentConstraintCheck vast waar je omgeving het waarneemt
Resultaten verschillen tussen runs van dezelfde versieTools lezen of schrijven gedeelde toestandZet die toestand in je applicatie terug voor elke case; Oloproof doet dat niet
Een agent die aan het team is toegevoegd faalt meteen op rechtenDe rechtenmap is geslotenDeclareer 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.