Ga naar inhoud

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