Live checks via de API¶
Het concept achter live checks staat in Live checks.
Op deze pagina geef je een agentversie live checks via de API, lees je wat een tegengehouden beurt
je applicatie vertelt, en lees je de uitkomsten en slagingspercentages terug. Een live check is een
regel waaraan elke echte beurt van de agent wordt gehouden: op de vraag van de beller (input) of
op het antwoord van het model (output), beslissend over de beurt (block) of achteraf op een
steekproef (background).
Je sleutel heeft de capability agents nodig om de checks te schrijven. Voor de uitkomsten van één
beurt is threads nodig, net als voor het lezen van de beurt; voor de slagingspercentages van een
agent evaluations. Hetzelfde doe je met de muis in Live checks in de console.
Checks aan een agentversie toevoegen¶
Live checks zijn onderdeel van een agentversie en schrijf je met de rest ervan, op POST /agents of
POST /agents/{agent_id}/versions:
curl -s -X POST "$CORE_URL/agents/agt_5Nj2.../versions" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"model": "<een model-id>",
"instructions": "Beantwoord vragen over bestellingen in twee zinnen.",
"blocked_reply": "Daar kan ik je niet mee helpen. Deel nooit een wachtwoord in dit gesprek.",
"recover_attempts": 2,
"live_checks": [
{"name": "geen-wachtwoorden", "stage": "input", "kind": "not_contains",
"expected": "wachtwoord"},
{"name": "geen-iban", "stage": "output", "kind": "matches_pattern",
"pattern": "NL\\d{2}[A-Z]{4}\\d{10}", "max_match": 18, "recover": true,
"description": "Noem nooit een rekeningnummer."},
{"name": "beleefd", "stage": "output", "kind": "polite_tone",
"mode": "background", "sample": 0.2,
"alert": {"failure_rate": 0.3, "over_last": 50}}
]}'
Deze versie weigert een vraag waarin een wachtwoord staat met het standaardantwoord, laat een antwoord nooit een Nederlandse IBAN bevatten (en vraagt het model opnieuw als dat toch gebeurt), en laat een beoordelaar de toon van één op de vijf beurten scoren, met een melding zodra 30% van de laatste 50 scores faalt.
Een versie is de hele definitie: een versie die je zonder live_checks schrijft, heeft geen checks,
en aanroepen die geen versie noemen, draaien haar meteen: een versie die je via de API schrijft, is
gepubliceerd. Sla haar eerst als
concept op in de console als je haar met een evaluatie wilt meten voordat je bellers haar krijgen
(Concepten).
De velden van de versie¶
| Veld | Wat het doet |
|---|---|
live_checks |
hooguit 16 checks; namen zijn uniek binnen de lijst |
blocked_reply |
het standaardantwoord, hooguit 1000 tekens, waarmee een beurt antwoordt als een blokkerende check op de invoer faalt. Verplicht zodra één check op de invoer blokkeert |
recover_attempts |
hoe vaak een antwoord dat faalde voor een check met recover opnieuw wordt gevraagd, 1 tot 3; 2 als je het weglaat |
De velden van een check¶
| Veld | Wat het doet |
|---|---|
name |
kleine letters, cijfers, - en _, beginnend met een letter of cijfer, hooguit 64 tekens. Uitkomsten, slagingspercentages en meldingen noemen de check zo, en de beller van een beurt die hij tegenhoudt, ziet hem in X-Core-Check; zet er dus niets geheims in |
description |
optioneel, hooguit 300 tekens: je eigen zin voor de regel. Wordt een antwoord opnieuw gevraagd, dan krijgt het model te horen dat het deze regel brak; zonder omschrijving noemt Core de verwachte tekst van een beoordeelde check, of de naam van de check |
stage |
input (de tekst van de beller, vóór elke modelaanroep) of output (het antwoord) |
kind |
welke regel, zie de soorten |
mode |
block (standaard): beslis over de beurt en houd hem tegen bij een mislukking. background: geef het antwoord na de beurt een score, op een steekproef |
expected |
de tekst waarmee de regel vergelijkt, zie de soorten |
pattern, max_match |
alleen matches_pattern: de reguliere expressie, en de langste treffer die hij kan hebben, 1 tot 200 tekens |
threshold |
alleen beoordeelde soorten: de score van 0 tot 1 vanaf waar de check slaagt; 0,7 als je hem weglaat |
judge_model |
alleen beoordeelde soorten: het model dat deze check beoordeelt; zonder dit veld beoordeelt het model waarop de beurt draait |
recover |
true vraagt het model opnieuw als zijn antwoord voor deze check faalt; alleen een blokkerende check op de uitvoer |
on_error |
alleen blokkerende beoordeelde checks: "block" (als je het weglaat) houdt de beurt tegen als de beoordelaar niet kan beslissen; "pass" laat de beurt doorgaan alsof de check slaagde, met de uitkomst toch errored |
sample |
alleen checks die achteraf beoordelen: het aandeel beurten dat wordt beoordeeld, 0 tot 1; 0,1 als je het weglaat |
alert |
optioneel: {"failure_rate": …, "over_last": …}, zie Meldingen |
De soorten¶
kind |
Faalt wanneer | expected |
Stage | Achteraf |
|---|---|---|---|---|
contains_text |
de tekst expected niet bevat, hoofdletterongevoelig |
verplicht | beide | nee |
not_contains |
de tekst expected bevat, hoofdletterongevoelig |
verplicht | beide | nee |
matches_pattern |
de tekst een treffer van pattern bevat |
niet gebruikt | beide | nee |
must_use_tool |
de beurt de tool uit expected niet aanriep, of helemaal geen tool als expected leeg is |
optioneel | uitvoer | nee |
correct_format |
een beoordelaar vindt dat het antwoord niet het formaat uit expected volgt |
verplicht | beide | ja |
polite_tone |
een beoordelaar de toon niet professioneel, behulpzaam en beleefd vindt | niet gebruikt | beide | ja |
no_hallucination |
een beoordelaar iets verzonnen vindt | niet gebruikt | beide | ja |
llm_judge |
een beoordelaar vindt dat de tekst niet voldoet aan de regel in expected |
verplicht | beide | ja |
Dit zijn de soorten die een evaluatie gebruikt, zodat een regel in
een suite en op een echte beurt hetzelfde betekent. Bij een beoordeelde soort is expected de
regel, zoals "de vraag gaat over onze producten", niet een antwoord dat je verwacht.
response_time wordt geweigerd met 422, omdat het nog niet wordt ondersteund.
Een patroon heeft een grens nodig. pattern is RE2-syntaxis, hooguit 500 tekens, zonder
terugverwijzingen of lookaround. Het wordt geweigerd als de langste treffer langer kan zijn dan
max_match, zoals bij .* of \w+, of als het helemaal niets kan treffen. Die grens houdt een
gestreamd antwoord maar een paar tekens achter het model: Core houdt max_match tekens achter
voordat het ze verstuurt. NL\d{2}[A-Z]{4}\d{10} treft precies 18 tekens, dus max_match: 18
past.
Een beoordelingsmodel moet een model uit de catalogus van je organisatie zijn, nooit een router.
Is het eigen model van de agent een router, dan noemt elke beoordeelde check een judge_model,
omdat een router per beurt een ander model kiest. Een agent zonder eigen model, aangeroepen met
een router als model, heeft er ook een nodig: een beoordeelde check zonder judge_model kan dan
niet beslissen (errored), en een blokkerende houdt de beurt tegen met check_errored, tenzij hij
"on_error": "pass" zegt. In de
console heet de beoordelaar de rechter. De tokens van de beoordelaar tellen als
model_tokens op de sleutel waarmee de beurt draaide.
Wat je applicatie ontvangt¶
Een beurt die een check tegenhield, houdt de vorm van de leverancier, en de header X-Core-Check
zegt welke check hem tegenhield:
X-Core-Check: stage=input, check="geen-wachtwoorden", code=input_check_failed
De header is een gestructureerd woordenboek (RFC 8941) met stage, check en code. Hij noemt de
eerste blokkerende check die niet slaagde, en ontbreekt bij elke beurt die geen check tegenhield, ook
bij een herstelde. Hij bevat nooit een patroon, verwachte tekst, drempel, score of reden.
code |
Wat er gebeurde | Het antwoord |
|---|---|---|
input_check_failed |
een blokkerende check op de invoer faalde; er is geen model aangeroepen | completed: één assistentbericht waarvan het enige inhoudsdeel een refusal met blocked_reply is |
output_check_failed |
een blokkerende check op de uitvoer faalde en het antwoord werd niet opnieuw gevraagd | incomplete, incomplete_details.reason: "content_filter" |
recovery_exhausted |
het antwoord werd opnieuw gevraagd en de laatste poging faalde nog steeds | incomplete, content_filter |
check_errored |
een blokkerende check kon niet beslissen, dus de beurt werd tegengehouden; nooit een check met "on_error": "pass" |
geweigerd of achtergehouden, naar de stage van de check |
Lees het deel refusal zoals je de weigering van een model leest: toon het aan de persoon. Op
POST /messages zijn een geweigerde vraag en een achtergehouden antwoord allebei
stop_reason: "refusal", met dezelfde header.
Een gestreamde beurt. Een check op de invoer draait voordat de stream begint, dus zijn header komt
ook bij een gestreamde beurt aan. Een check op de uitvoer beslist nadat de headers al zijn verstuurd:
de stream eindigt met response.incomplete en content_filter, en
GET /responses/{id}/checks noemt de check. Zijn de blokkerende checks op de uitvoer
van de agent allemaal patronen, dan streamt het antwoord een paar tekens achter het model aan en
stopt het waar een treffer begint; met een andere blokkerende check op de uitvoer komt het antwoord
in één keer, zodra het is geslaagd.
Een herstelde beurt. Faalt het antwoord voor een check met recover, dan vraagt Core het model
opnieuw zonder het afgewezen antwoord te laten zien, en het antwoord bevat alleen wat slaagde. Elke
poging is een modelaanroep die op je sleutel wordt gefactureerd en in usage meetelt; vóór elke
poging worden de deadline van de beurt en je maandlimiet gecontroleerd. Bij een gestreamde beurt
waarvan het antwoord je al bereikte, wordt geen herstel geprobeerd: daar wordt het antwoord
afgekapt.
Een beurt die Core zelf in de rij zet. Een workflowstap of een taak die op de achtergrond start,
wordt geweigerd als een check op de invoer faalt, omdat niemand op het standaardantwoord wacht: de
workflowstap eindigt policy_refused, en de taak start niet. Een verzoek dat je met
background: true stuurt, krijgt meteen het standaardantwoord.
De uitkomsten van één beurt lezen¶
curl -s "$CORE_URL/responses/resp_01M3.../checks" \
-H "Authorization: Bearer $CORE_KEY"
{
"items": [
{"object": "response.check", "id": "rck_…", "response_id": "resp_01M3…",
"name": "geen-wachtwoorden", "stage": "input", "kind": "not_contains", "mode": "block",
"attempt": 1, "outcome": "failed", "score": null,
"reason": "The answer contains text it had to avoid.", "judge_model": null,
"passed_on_error": false, "created_at": "2026-10-16T09:14:03Z"}
],
"next_cursor": null
}
Elke uitkomst van elke check op de beurt, in de volgorde waarin ze werden beslist, gepagineerd met
een cursor (Paginering met cursors).
outcome is passed, failed of errored; passed_on_error is true bij een errored uitkomst
die de "on_error": "pass" van zijn check doorliet. attempt telt de antwoorden: 1 is het eerste, 2 het
eerste antwoord dat opnieuw werd gevraagd. reason is een vaste zin bij een regel en bij een check
die niet kon beslissen, en de eigen zin van de beoordelaar bij een beoordeelde check; hij herhaalt
nooit de tekst die de check las. De vaste zinnen zijn die van een evaluatie, in het Engels, en
spreken ook bij een check op de invoer van "the answer". De uitkomst van een check die achteraf
beoordeelt, verschijnt zodra de beoordelaar hem heeft gescoord, kort na de beurt.
Je leest de uitkomsten van een beurt overal waar je de beurt zelf leest met GET /responses/{id};
een beurt die niet werd opgeslagen, geeft 404.
De slagingspercentages van een agent lezen¶
curl -s "$CORE_URL/agents/agt_5Nj2.../checks/summary?since=2026-10-09T00:00:00Z" \
-H "Authorization: Bearer $CORE_KEY"
{
"since": "2026-10-09T00:00:00Z",
"items": [
{"name": "beleefd", "passed": 79, "failed": 2, "errored": 1},
{"name": "geen-iban", "passed": 412, "failed": 3, "errored": 0},
{"name": "geen-wachtwoorden", "passed": 409, "failed": 6, "errored": 0}
]
}
Per checknaam hoeveel uitkomsten van de beurten van de agent slaagden, faalden of niet konden
beslissen sinds since: zeven dagen terug als je het weglaat, hooguit negentig. De aantallen tellen
alle versies van de agent mee, en bevatten nooit een reden of tekst.
Meldingen¶
Een check met alert geeft een melding als te veel van zijn laatste uitkomsten niet slaagden:
"alert": {"failure_rate": 0.3, "over_last": 50}
Zodra de check minstens over_last uitkomsten heeft (1 tot 1000), en het aandeel daarvan dat faalde
of niet kon beslissen gelijk is aan of hoger dan failure_rate (boven 0, hooguit 1), krijgen de
beheerders van de organisatie één melding Live check faalt per mail of Slack. Die noemt de
agent, de check en het aantal, nooit een vraag of een antwoord, en komt hooguit één keer per dag per
check. De uitkomsten van alle versies van de agent tellen mee. Een organisatie die de soort
check.failing heeft uitgezet, krijgt geen melding; zie
Meldingen.
Events¶
Elke uitkomst die niet slaagde, in beide modes, is een event response.check_failed op je
eventfeed en webhooks, met de name, stage, kind en outcome van de
check en de response_id van de beurt. Gebruik het om mislukkingen te volgen terwijl ze gebeuren,
in plaats van slagingspercentages te lezen.
Fouten¶
| Status | Oorzaak |
|---|---|
422 op live_checks |
meer dan 16 checks, twee met dezelfde naam, een veld dat de soort niet gebruikt of een veld dat hij nodig heeft ontbreekt, een invoer-stage bij must_use_tool, een check die achteraf beoordeelt maar geen beoordeelde check op de uitvoer is, recover op iets anders dan een blokkerende check op de uitvoer, "on_error": "pass" op iets anders dan een blokkerende beoordeelde check, sample op een blokkerende check, een beoordelingsmodel dat een router is of niet in je catalogus staat, of een beoordeelde check zonder judge_model bij een agent met een router als model |
422 op live_checks |
een patroon dat RE2 weigert, dat niets kan treffen, of waarvan de langste treffer langer kan zijn dan max_match |
422 op live_checks |
kind: "response_time", dat bij een live check nog niet wordt ondersteund |
422 op blocked_reply |
een check op de invoer blokkeert en de versie heeft geen standaardantwoord |
422 op recover_attempts |
een getal buiten 1 tot 3 |
422 op since |
meer dan negentig dagen terug |
404 |
geen beurt of agent met dat id, of een beurt die niet werd opgeslagen — of threads (uitkomsten van één beurt) of evaluations (slagingspercentages) werkt niet voor je sleutel |