Evaluaties via de API¶
Het concept achter agents staat in Agents.
Op deze pagina schrijf je een evaluatie met de API, start je een run en lees je de uitkomst. Een evaluatie is een suite gesprekken die je één keer opschrijft en daarna tegen een agent laat draaien. Elk gesprek is een scenario, elk scenario bestaat uit stappen, en elke stap is één bericht van de gebruiker plus de regels waaraan het antwoord moet voldoen. Een run speelt die gesprekken opnieuw af tegen een vastgepinde agentversie en legt vast wat klopte en wat niet. Gebruik het om na een wijziging aan een agent te zien of hij zijn werk nog doet.
Je sleutel heeft de capability evaluations nodig. Evaluaties gelden alleen voor agents: daarom
heeft evaluations ook agents nodig, en noemt een suite minstens één agent. Een run heeft
daarnaast threads nodig: een scenario is een gesprek, en de stappen ketenen op één thread.
Hetzelfde doe je met de muis in Evaluaties in de console.
Een suite maken¶
curl -s -X POST "$CORE_URL/evaluations" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"name": "triage-kwaliteit",
"description": "Blijft triage beleefd en noemt hij het ticketnummer?",
"judge_model": "<een model-id>",
"run_as_key_id": "key_9Tc4...",
"agents": ["agt_5Nj2..."]}'
Een geslaagd verzoek geeft 201.
| Veld | Wat het doet |
|---|---|
name |
uniek binnen je organisatie |
description |
waar deze suite over gaat; puur voor lezers |
judge_model |
het model dat de beoordeelde checks uitvoert; laat je het weg, dan gebruikt elke run het model van de agent zelf |
run_as_key_id |
de sleutel van je organisatie waaronder elke run van deze suite draait; laat je het weg, dan draait een run onder de sleutel die hem start |
agents |
de agents die deze suite meet, minstens één en hoogstens zestien |
Een agent die het beleid van je sleutel niet noemt, kun je niet koppelen: dat is 422 op agents.
Een sleutel die niet van jouw organisatie is, ook niet: dat is 422 op run_as_key_id.
PATCH /evaluations/{id} verandert wat je meegeeft; agents vervangt de hele set, en een expliciete
null op judge_model of run_as_key_id maakt dat veld weer leeg.
Onder welke sleutel een run draait¶
Elke stap van een run is een gewone beurt, en een beurt staat altijd op naam van een sleutel. Elke run legt daarom vast onder welke sleutel hij draait:
- de sleutel die de suite noemt in
run_as_key_id, als die er is; - anders de sleutel die de run start;
- anders
409— de suite moet eerst een sleutel noemen.
Dat derde geval is een beheerder in de console: een persoon heeft zelf geen sleutel om op terug te vallen. Noem je er een, dan kan de console runs gewoon starten.
Of de agent bereikbaar is, wordt getoetst aan het beleid van de sleutel waaronder de run gaat
draaien — niet aan dat van jou. Kan die sleutel de agent niet zien, dan krijg je meteen 422 in
plaats van een run die niets kan. Is de genoemde sleutel intussen verwijderd, dan is dat 422 op
run_as_key_id: er wordt nooit stilletjes teruggevallen op jouw eigen sleutel.
created_by op een run blijft wie hem gestart heeft (een sleutel-id of operator:<sub>);
run_as_key_id zegt op wiens naam de beurten staan.
Een sleutel die evaluaties mag beheren, mag hier elke sleutel van haar organisatie noemen, ook een die meer capabilities of een hogere limiet heeft dan zijzelf. De beurten van de runs van de suite vallen dan onder de policy van de genoemde sleutel en tellen mee voor de limiet per maand van die sleutel, niet voor de jouwe. Een planning werkt anders: je sleutel plant als zichzelf.
Een scenario schrijven¶
curl -s -X POST "$CORE_URL/evaluations/evl_7Qa1.../scenarios" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"title": "dubbele afschrijving",
"steps": [
{"input": "Mijn kaart is twee keer belast.",
"checks": [{"kind": "contains_text", "expected": "ticket"},
{"kind": "polite_tone", "required": false}]},
{"input": "Kun je het terugboeken?",
"checks": [{"kind": "must_use_tool", "expected": "api__refund"}]}
]}'
Een scenario wordt achteraan toegevoegd. Je schrijft een scenario altijd in zijn geheel: met
PUT /evaluations/{id}/scenarios/{scenario_id} vervang je titel, omschrijving en de hele stappenlijst
in één keer. Per stap schrijven bestaat niet, juist omdat twee mensen die tegelijk aan één scenario
werken elkaars wijzigingen anders kwijtraken.
Elke stap krijgt een evt_-id van de server. Stuur dat id mee als je de stap bewaart bij een
volgende PUT, dan blijven de resultaten van eerdere runs eraan gekoppeld. Een id dat je zelf meestuurt, moet er een zijn die de server ook had kunnen maken (evt_ plus letters en cijfers) en moet uniek zijn binnen dat scenario; twee stappen in één scenario met hetzelfde id is 422.
Een suite bevat hoogstens 32 scenario's, een scenario hoogstens 32 stappen en een stap hoogstens 16 checks.
Waarden voor de prompt van de agent¶
Zet een agent een prompt vast die variabelen declareert, dan levert het scenario die waarden.
variables is een mapping van naam naar waarde op het scenario, en elke stap van dat scenario
stuurt dezelfde waarden mee:
curl -s -X POST "$CORE_URL/evaluations/evl_7Qa1.../scenarios" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"title": "een boze vaste klant",
"variables": {"customer_name": "A. Bakker", "tier": "goud"},
"steps": [{"input": "Mijn kaart is twee keer belast.", "checks": []}]}'
Ze staan op het scenario en niet op een stap, omdat één gesprek één persoon is: de beller verandert niet halverwege.
Een suite meet meerdere agents en je schrijft één scenario voor allemaal, dus een naam die de
prompt van een agent niet declareert, blijft gewoon weg uit de beurten van die agent in plaats
van ze te laten falen — schrijf customer_name voor de agent die erom vraagt en tier voor de
agent die dat doet, in hetzelfde scenario. Een agent die helemaal geen prompt vastzet, krijgt geen
enkele waarde, wat het scenario ook zegt.
Twee dingen falen wél: een verplichte variabele zonder waarde, en een waarde die niet past bij
het type of de enum die de prompt van de agent ervoor declareert, zoals de tekst "3" voor een
number. In beide gevallen komt elke stap van die agent terug als mislukte stap met een reden die
de variabele noemt. Bij het schrijven van het scenario wordt geen van beide gecontroleerd, omdat de
agents die een suite meet ook later gekoppeld kunnen worden. Een variabele met een standaard is
niet verplicht: laat het scenario hem weg, dan vult Core bij elke stap de standaard in.
Namen moeten eruitzien als placeholders in een prompt ([A-Za-z_][A-Za-z0-9_]*), hoogstens 32
stuks. Een waarde is een JSON-tekst, -getal of -boolean -- stuur hem als het type dat het
invoerschema van de prompt declareert -- en een tekst is
hoogstens 8192 tekens; iets anders is 422 op variables. Een PUT vervangt de hele mapping,
net zoals hij de stappen vervangt.
tool_variables op het scenario werkt net zo voor de tools van de gemeten agent: elke stap stuurt
die waarden mee als de tool_variables van het verzoek, en een naam die geen tool van de beurt van
een stap declareert, wordt daar genegeerd. Elke lezer van de suite ziet ze, dus ze zijn nooit
geheim: een waarde voor een naam die een tool van je organisatie als geheim declareert, wordt
geweigerd met 422 op tool_variables.<naam>.
DELETE /evaluations/{id}/scenarios/{scenario_id} verwijdert er één. De volgorde van de rest
verandert daarbij niet: een run die al liep, heeft zijn eigen momentopname.
Versies¶
Elke wijziging van wat een suite meet -- een scenario geschreven of verwijderd, haar agents of haar beoordelaarsmodel gewijzigd -- is een genummerde versie, die meteen gepubliceerd is als een sleutel haar maakt. Je kunt de volgende versie ook in zijn geheel schrijven:
curl -s -X POST "$CORE_URL/evaluations/evl_7Qa1.../versions" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"agents": ["agt_5Nj2..."], "scenarios": [{"id": "evs_2Kd9...", "title": "Retourneren",
"steps": [{"input": "Kan ik dit retourneren?"}]}]}'
Je stuurt elk scenario mee: een met een id blijft dat scenario, een zonder is nieuw, en een dat je
weglaat verdwijnt. GET /evaluations/{id}/versions geeft de versies, oudste eerst, en
GET /evaluations/{id}/versions/{version} leest er één. Een sleutel ziet alleen gepubliceerde
versies: een persoon kan in de console een concept van een suite voorbereiden, en een suite die
alleen een concept is, bestaat voor een sleutel helemaal niet.
De checks¶
| Kind | Klopt wanneer | Extra veld |
|---|---|---|
contains_text |
het antwoord bevat expected, hoofdletterongevoelig |
— |
not_contains |
het antwoord bevat expected juist niet |
— |
matches_pattern |
het antwoord bevat geen treffer van pattern, een reguliere expressie (RE2) |
pattern en max_match (allebei verplicht) |
exact_match |
het antwoord is, ontdaan van witruimte, precies expected — hoofdletters tellen mee |
— |
close_match |
de gelijkenis van beide is minstens min_similarity (standaard 0,8) |
min_similarity |
response_time |
de beurt was binnen max_seconds klaar |
max_seconds (verplicht) |
must_use_tool |
de beurt riep expected aan, of welke tool dan ook als je geen naam noemt |
— |
tool_found |
een zoekopdracht van de toolrouter in de beurt, of de tools die hij vooraf koos, had expected (verplicht) onder de resultaten |
— |
correct_format |
een beoordelaar vindt dat het antwoord het formaat uit expected volgt |
threshold |
polite_tone |
een beoordelaar vindt de toon professioneel, behulpzaam en beleefd | threshold |
no_hallucination |
een beoordelaar vindt niets verzonnen | threshold |
llm_judge |
een beoordelaar vindt dat het antwoord voldoet aan de regel in expected |
threshold |
Elke check heeft required, standaard true. Een check weigert een veld dat bij zijn soort niet
hoort: schrijf je max_seconds op een contains_text, dan is dat 422 in plaats van een regel die
stilletjes nooit aanslaat.
De vier beoordeelde soorten zijn één modelaanroep per check. Bij llm_judge is jouw expected de
regel waaraan getoetst wordt, niet een modelantwoord: schrijf er "het antwoord biedt aan om op te
schalen", niet het antwoord dat je verwacht. De beoordelaar ziet het bericht van de stap, het
uiteindelijke antwoord van de agent en die regel — meer niet. Hij geeft een score van 0 tot 1 en één
zin met een reden; de check klopt vanaf threshold, standaard 0,7.
matches_pattern is het patroon waaraan een live check een echte beurt
houdt, zodat een suite de regel kan meten voordat de agent hem afdwingt. Een treffer is de
mislukking. max_match, 1 tot 200, is de langste treffer die het patroon kan hebben: een patroon
waarvan een treffer langer kan zijn, zoals .*, of dat niets kan treffen, is 422.
must_use_tool kijkt naar de tool-aanroepen van de beurt zelf, niet naar de tekst. Een model dat
zegt dat het gaat zoeken, heeft niet gezocht. tool_found onderscheidt een model dat een tool nooit
vond van een model dat hem vond en niet gebruikte; een beurt zonder zoekopdracht faalt erop (Een
agent met veel tools).
Een run starten¶
curl -s -X POST "$CORE_URL/evaluations/evl_7Qa1.../runs" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{}'
Dit antwoordt 202 met één run per gekoppelde agent. Geef {"agent_id": "agt_5Nj2..."} mee om er
één te starten voor die agent; die moet wel aan de suite gekoppeld zijn. Een suite zonder scenario's
is 409.
Er draait nog niets: elke run is werk in de wachtrij, opgepakt door de worker. Elke run pint de versie die de agent op dat moment als laatste had en legt de scenario's vast zoals ze er dan uitzien, dus een latere wijziging verandert niet meer wat die run gemeten heeft.
Of laat een klok hem starten. Een planning met
kind: "evaluation" noemt deze suite en zet er op zijn eigen cron een run voor in de wachtrij --
elke nacht, elke week, wat je ook schrijft -- als de sleutel die de suite noemt, en slaat een
nacht over waarin nog een run loopt. Wat zo'n planning heeft gedaan lees je in de runlijst van deze
suite, beperkt tot die planning:
curl -s "$CORE_URL/evaluations/evl_7Qa1.../runs?schedule_id=sch_3Qp8..." \
-H "Authorization: Bearer $CORE_KEY"
Elke run noemt schedule_id -- leeg bij de runs die je zelf hebt gevraagd -- en fired_for, het
moment dat aan de beurt was.
Een run voor één persoon¶
Een run kan voor één persoon werken, zodat elke stap de beurt is die je agent voor haar zou maken:
wat hij mag doorzoeken, welke tools en welk model hij mag gebruiken en wat hij over haar onthoudt,
allemaal bepaald door je regels voor haar, precies zoals bij een live beurt. Noem haar met
X-Core-Subject op het verzoek dat de run start, zoals je haar op een beurt noemt
(bouwen voor personen):
curl -s -X POST "$CORE_URL/evaluations/evl_7Qa1.../runs" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-H 'X-Core-Subject: {"id": "anna", "attributes": {"department": "payroll"}}' \
-d '{}'
Elke run die dat verzoek start, werkt voor haar en zegt dat in subject_id. De attributen die je
meestuurt gelden voor elke stap tot de run klaar is, en worden daarna nergens bewaard. De gesprekken
van de stappen zijn van haar: ze staan tussen haar threads en responses en verdwijnen met haar als je
haar wist; de run zelf blijft en noemt haar dan
niet meer. De checks worden nog steeds beoordeeld als meting van je organisatie, niet als haar beurt.
Twee weigeringen horen erbij: een suite die onder een andere sleutel dan de jouwe draait
(run_as_key_id) geeft 422 op header.X-Core-Subject, omdat een run alleen voor een persoon werkt
als de sleutel die haar noemde; en een agent die je regels haar niet laten gebruiken geeft 422 op
agents of agent_id voordat er iets draait. Een run zonder de header werkt voor niemand, zoals
altijd, en dat geldt ook voor elke run die de beheeromgeving, een planning of een verbeterproef
start. Een sleutel waarvan de policy een subject vereist, moet er hier ook een noemen, net als bij
elke andere evaluatie-aanroep.
Een run stoppen kan met POST /evaluations/{id}/runs/{run_id}/cancel. Staat hij nog in de wachtrij,
dan stopt hij meteen; draait hij al, dan stopt hij bij de volgende stap en wordt de stap die op dat
moment liep niet opgeslagen. Een run die al klaar is, is 409.
Een run lezen¶
curl -s "$CORE_URL/evaluations/evl_7Qa1.../runs/evr_3Kd8..." \
-H "Authorization: Bearer $CORE_KEY"
Vraag het gewoon opnieuw om de voortgang te zien: na elke stap staan completed_steps, score en
het resultaat van die stap erin.
| Veld | Wat het zegt |
|---|---|
status |
queued, running, passed, warnings, failed, errored of cancelled |
run_as_key_id |
de sleutel waaronder deze run draaide |
score |
geslaagde stappen gedeeld door het totaal van de momentopname, als percentage |
scenarios |
de suite zoals die was toen de run in de wachtrij kwam |
results |
per scenario de stappen, per stap de checks met outcome, score en reason |
thread_ids |
één thread per scenario, zodat je het gesprek kunt teruglezen |
expired_at |
wanneer de bewaartermijn van je organisatie scenarios, results en thread_ids heeft leeggemaakt; null zolang de run ze nog heeft |
Heeft je organisatie een bewaartermijn ingesteld, dan houdt een afgeronde run die ouder is dan die
termijn zijn status, score, kosten en tijdstippen, terwijl scenarios, results en
thread_ids leeg terugkomen met expired_at gevuld. De threads waar ze naar verwezen worden door
dezelfde termijn verwijderd. Een vergelijking van zulke runs geeft nog wel de totalen, maar geen
stappen om naast elkaar te leggen.
Een stap slaagt als elke verplichte check klopt. De run is failed zodra een stap met een
verplichte check niet slaagde, warnings als alleen optionele checks niet klopten, en passed als
alles klopte. errored en cancelled zeggen niets over de agent: dan is de run zelf niet aan een
oordeel toegekomen.
Een check kent drie uitkomsten. errored betekent dat de beoordelaar geen antwoord kon geven — een
time-out, een onbereikbaar model, een antwoord dat niet in het schema past — en telt mee als "niet
geslaagd". Zo blijft het verschil zichtbaar tussen "de agent deed het fout" en "we weten het niet".
Terugvinden welke beurt bij welke stap hoorde¶
Elke stap is een gewone beurt: hij staat op een thread, is leesbaar via
GET /responses/{response_id}, staat in je auditspoor en telt mee in je verbruik. results geeft
per stap het response_id, en thread_ids geeft per scenario de thread. De beoordeelde checks
tellen als model_tokens, net als elke andere modelaanroep.
Fouten¶
| Status | Oorzaak |
|---|---|
404 |
geen zo'n suite, scenario of run — of evaluations geldt niet voor je sleutel |
409 |
de naam is al bezet; de suite heeft geen scenario's; de run is al klaar; of een mens start een run van een suite die geen sleutel noemt |
422 op agents |
een agent bestaat niet, of het beleid van de sleutel waaronder de run draait noemt hem niet |
422 op run_as_key_id |
die sleutel bestaat niet, is niet van jouw organisatie, of is verwijderd |
422 op agent_id |
die agent is niet aan deze suite gekoppeld |
403 |
de header noemt een service account via een andere sleutel dan die van het account zelf; of het verzoek kwam met een subject-token, dat geen runs mag starten |
422 |
de policy van je sleutel vereist een subject en het verzoek noemt er geen |
422 op header.X-Core-Subject |
het verzoek noemt een persoon en de suite draait onder een andere sleutel dan de jouwe (een run voor één persoon) |
422 op steps |
een scenario zonder stappen, of meer dan tweeëndertig, of twee stappen met hetzelfde id |
422 op scenarios |
de suite heeft er al tweeëndertig |
422 op een checkveld |
die soort check gebruikt dat veld niet, of mist er juist een |
De beoordeelde checks draaien onder diezelfde sleutel: hun tokens komen op dezelfde rekening als de
beurten. Mag die sleutel het beoordelingsmodel niet gebruiken, dan is elke beoordeelde check
errored met de reden van het beleid — nooit stilletjes geslaagd.