Ga naar inhoud

Een modelaanroep doen

Stuur POST /responses om een beurt met een taalmodel uit te voeren. Een beurt omvat de modelaanroep en eventuele toolaanroepen die nodig zijn voor het antwoord. Deze pagina beschrijft het verzoek, het antwoord, streamen en de fouten die je tegenkomt; elke andere pagina onder Core aanroepen bouwt hierop voort.

Je hebt een API-sleutel nodig en een model-id dat de policy van die sleutel toestaat. Heb je nog geen verzoek gestuurd, begin dan bij Je eerste aanroep.

Verzoekvelden

{
  "model": "<een model-id>",
  "instructions": "Je antwoordt in één zin.",
  "input": [{ "role": "user", "content": "Wat is een bearer-token?" }],
  "temperature": 0.2,
  "max_output_tokens": 400
}
Veld Beschrijving
model Model-id. GET /models toont de modellen die je sleutel mag gebruiken.
input Een string of een lijst met items. Een string wordt behandeld als één user-bericht.
instructions Instructies voor deze beurt, met developer-autoriteit.
temperature, max_output_tokens Optionele instellingen die worden doorgegeven aan het model. Een model dat een sampling-parameter weigert, laat de beurt mislukken in plaats van hem te negeren: de huidige Claude-modellen weigeren temperature, dus laat die daar weg. Gemini-modellen accepteren hem, behalve gemini-3.5-flash-lite en gemini-3.6-flash: die hebben vaste sampling en negeren hem.
text Uitvoerformaat, bijvoorbeeld {"format": {"type": "json_schema", "name": "...", "schema": {...}}}.
reasoning Optioneel. Wat deze beurt van het denkwerk van het model vraagt: {"effort": "low" \| "medium" \| "high" \| "xhigh" \| "max", "summary": "auto" \| "concise" \| "detailed" \| "none"}. Beide onderdelen zijn optioneel. Een effort die je weglaat wordt niet verstuurd, dus het model houdt zijn eigen standaard aan. summary vraagt het denkwerk in leesbare vorm terug; laat je het weg, dan beslist de instelling van je sleutel of organisatie (Antwoordvelden). "summary": "none" vraagt bij deze beurt om geen samenvatting, wat die instellingen ook zeggen, en elk model accepteert het: het model denkt nog steeds na op zijn effort en de redeneertokens tellen nog steeds mee in usage. Een model dat wordt bereikt via een OpenAI-compatibele chat-completions-API heeft geen manier om een samenvatting te vragen, en wat er terugkomt hangt af van de server van het model (Antwoordvelden); een model waarvan de client er nooit een kan teruggeven, een Azure-deployment op chat completions, weigert een verzoek dat erom vraagt met een 400 die reasoning.summary noemt. Een model dat een effort weigert, laat de beurt falen, net als bij een sampling-parameter die het weigert.
agent Optioneel. Noemt een opgeslagen agent waarop deze beurt is gebouwd: zijn model, instructie, vastgezette prompt, skills en tools. Zijn skills en tools komen bij die van het verzoek; elk ander veld dat hij vastlegt en dat het verzoek óók meegeeft, wordt geweigerd met 400, met dat veld in error.param. Legt de agent een model vast, dan mag model weg.
timezone Optioneel, een uitbreiding van Core. Een IANA-zonenaam zoals Europe/Amsterdam: de tijdzone waarin de ingebouwde datum- en tijdwaarden van een prompt voor deze beurt worden gelezen. Laat je hem weg, dan geldt de tijdzone van de planning die de beurt afvuurt, anders die van je organisatie, anders UTC. Een zone die Core niet kent, geeft 400 met timezone in error.param.
tool_search Optioneel, een uitbreiding van Core. {"mode": "router"} biedt het model twee tools aan, builtin__tool_search en builtin__tool_run, in plaats van de tools die Core voor deze beurt uitvoert en je eigen functietools, zodat een beurt veel meer tools kan bereiken dan een model in één aanroep accepteert; {"mode": "direct"} biedt elke tool zoals gewoonlijk aan. top_k (1 tot 20) en budget_tokens bepalen hoeveel tools één zoekopdracht teruggeeft en hoe groot het antwoord mag zijn. Laat je het weg, dan vraagt defer_loading: true op een tool of het item {"type": "tool_search"} van OpenAI om router (coding agents), en anders geldt de keuze van de agent, daarna die van je sleutel. tooling.tool_search.enabled van je sleutel moet aan staan en je organisatie moet tool search toestaan, anders geeft router 400 met tool_search.mode in error.param. Zie De toolrouter.
helper Optioneel, een uitbreiding van Core. Noemt een van je eigen helpers, zoals acme.summary, waarvan deze beurt het werk is: elke verbruiksrij die de beurt schrijft, draagt de naam, en zonder model (en zonder agent die er een vastlegt) draait de beurt op het hulpmodel van je organisatie. Twee tot vier delen in kleine letters met punten ertussen, hoogstens 64 tekens; namen onder assist. zijn van Core zelf. Een naam buiten die regels geeft 400 met helper in error.param. Het antwoord herhaalt hem niet. Zie Je eigen helper bouwen.
metadata Maximaal 16 sleutel/waarde-paren van strings, die ongewijzigd worden opgeslagen en teruggegeven.
tool_variables Optioneel, een uitbreiding van Core. Waarden voor de aanroepvariabelen die de tools van de beurt declareren, als een plat object van namen naar strings, getallen of booleans, zoals het account-id van je klant of zijn eigen token voor jouw API. Het model ziet ze nooit en het antwoord herhaalt ze niet; elke tool krijgt alleen de waarden die hij declareert, en een naam die geen tool declareert wordt genegeerd. Een verplichte die ontbreekt, of een waarde die de declaratie niet toelaat, geeft 400 met tool_variables.<naam> in error.param. Zie Een waarde van je klant aan een tool meegeven.
hosting Optioneel. Eist dat het model dat deze beurt beantwoordt op een genoemde plek draait, bijvoorbeeld {"region": "eu"}. region is een jurisdictie (eu, us, in-cluster, global) of een landcode van twee letters. Alleen een exacte match met de regio of het land van het model voldoet: nl voldoet niet aan eu, en eu voldoet niet aan nl. De eis geldt ook voor elk ander model dat voor deze beurt wordt aangeroepen: het model dat de oudere berichten van de thread samenvat, het embedding-model waarmee een kenniszoekopdracht zoekt, het model van een afbeeldings- of transcriptietool en de modelstappen van een kennistool. Draait zo'n model elders, dan faalt de beurt niet: de berichten van de thread gaan onsamengevat mee, een kenniszoekopdracht die het embedding-model nodig heeft is niet beschikbaar, en een tool antwoordt Denied by policy. Het audiomodel dat een input_audio-deel transcribeert, is de uitzondering: draait er geen audiomodel in de regio, dan wordt het verzoek geweigerd met 403.

Invoeritems ondersteunen de rollen user, developer, system, delegate en assistant. Zet applicatie-instructies in instructions of een developer-bericht. Zet inhoud van eindgebruikers in een user-bericht, ook als die als opdracht is geformuleerd. Core labelt deze inhoud als onvertrouwd en stuurt haar nooit als systeeminstructie; een sleutel met trust_envelope vertelt het model ook dat het gegevens zijn.

Een user-bericht kan ook bestanden, afbeeldingen en opnames meedragen als inhoudsdelen: input_file en input_image (Bestanden), en input_audio, een opname die Core transcribeert voordat het model haar leest, met het audiomodel dat het deel in model noemt of anders het eerste dat je sleutel toelaat (Audio transcriberen).

Een model kiezen

GET /models toont de chatmodellen die je sleutel mag aanroepen. Het antwoord bevat dezelfde modellen twee keer, voor twee soorten lezers:

Veld Wat erin staat
items, next_cursor de eigen lijst van Core, met cursor gepagineerd, met het volledige item dat hieronder staat
object, data, has_more, first_id, last_id de lijst die de SDK's van OpenAI en Anthropic lezen, zodat client.models.list() met allebei werkt. data bevat elk chatmodel dat je sleutel mag aanroepen in één pagina, dus has_more is altijd false

Een item van data is kort: id, object en type (allebei model), owned_by, display_name (nogmaals het id), en created en created_at, die altijd 0 en het Unix-epoch zijn omdat Core voor een model geen aanmaaktijd bijhoudt. Lees items voor al het andere. Met ?kind=image, ?kind=embedding of ?kind=audio (hieronder) is data leeg en staan die modellen alleen in items.

Elk item van items bevat waar het model draait, welk lab het heeft getraind, de keten die je beurten erop volgen bij uitwijken (uitwijken), en wat het je organisatie op dit moment kost:

{
  "id": "gpt-5.6-sol",
  "object": "model",
  "owned_by": "azure-openai-eu",
  "region": "eu",
  "country": "nl",
  "lab": "openai",
  "fallbacks": ["claude-sonnet-5"],
  "price": {
    "input_eur_per_million_tokens": "2.50",
    "output_eur_per_million_tokens": "10.00",
    "cached_input_eur_per_million_tokens": "0.25",
    "cache_write_eur_per_million_tokens": null,
    "eur_per_unit": null,
    "effective_from": "2026-09-01T00:00:00Z",
    "is_tenant_override": false,
    "listed": {
      "currency": "EUR",
      "input_per_million_tokens": "2.50",
      "output_per_million_tokens": "10.00",
      "cached_input_per_million_tokens": "0.25",
      "cache_write_per_million_tokens": null,
      "per_unit": null
    },
    "exchange_rate": null
  },
  "deprecation": null
}

lab is het lab dat het model heeft getraind — anthropic, openai, google, meta, mistral, perplexity, black_forest of artific — zoals je operator het vastlegt, en null als er geen is vastgelegd. Het zegt niet waar het model draait: dat is region, en owned_by is de aanbieder die het serveert.

price is null als niets het model beprijst; verbruik op zo'n model wordt nog steeds vastgelegd en kost simpelweg niets tot er een prijs is (verbruik). Bedragen zijn in euro als exacte decimale strings. cached_input_eur_per_million_tokens is het tarief voor invoer die de provider uit zijn promptcache las en cache_write_eur_per_million_tokens het tarief voor invoer die hij erin schreef (promptcaching); null betekent geen apart tarief, dus die tokens kosten het gewone invoertarief. eur_per_unit staat in plaats van de tokenbedragen bij een model dat per eenheid wordt afgerekend: per afbeelding bij een afbeeldingsmodel (?kind=image, hieronder), per minuut audio bij een audiomodel (?kind=audio). is_tenant_override is true als je organisatie een eigen prijs betaalt in plaats van die van de omgeving, en effective_from is het moment waarop de geldende prijs inging — een prijs die voor een toekomstige datum is aangekondigd, staat hier pas als die geldt.

listed is dezelfde prijs in de valuta waarin hij is vastgelegd, de valuta waarin de leverancier van het model factureert, met dezelfde bedragen zonder eur_ in de naam. exchange_rate is null bij een prijs in euro. Bij een prijs in een andere valuta noemt het de koers waarmee Core omrekende: de nieuwste referentiekoers van de Europese Centrale Bank en de dag waarop die werd gepubliceerd.

"listed": {
  "currency": "USD",
  "input_per_million_tokens": "3.00",
  "output_per_million_tokens": "15.00",
  "cached_input_per_million_tokens": "0.30",
  "cache_write_per_million_tokens": "3.75",
  "per_unit": null
},
"exchange_rate": { "units_per_euro": "1.1650", "rate_date": "2026-10-08" }

units_per_euro is hoeveel eenheden van de valuta één euro koopt. Elk bedrag in euro is het bedrag in listed gedeeld door die koers, zonder afronding: hier 3.00 / 1.1650 voor de invoer. Het bedrag in euro verandert dus wanneer de ECB een nieuwe koers publiceert. Hoe dat doorwerkt in wat een maand kost, staat bij Een prijs in een andere valuta.

deprecation is null, tenzij je operator het model uitfaseert; zie hieronder.

Een uitgefaseerd model

Een model dat wordt uitgefaseerd, draagt in items de datum en de vervanger:

"deprecation": {
  "effective_date": "2026-12-01",
  "replacement": "claude-sonnet-5",
  "in_effect": false
}

effective_date is de dag (in UTC) vanaf wiens begin het model niet meer als zichzelf antwoordt. replacement is het model dat dan zijn plaats inneemt, en in_effect zegt of die dag al is begonnen op het moment dat je de lijst las.

Elk antwoord op een verzoek dat zo'n model noemt, vóór de datum en erna, draagt de header X-Core-Model-Deprecation, ook een weigering:

X-Core-Model-Deprecation: "gpt-5.6-sol"; effective="2026-12-01"; replacement="claude-sonnet-5"; rerouted

rerouted staat erbij als de beurt al op de vervanger draaide. Noemde het verzoek meer dan één uitgefaseerd model, dan staan ze na elkaar, gescheiden door komma's. Een gestreamd antwoord draagt de header vanaf het begin van de stream, en een achtergrondrun op het antwoord dat hem aanmaakt. Een beurt zonder eigen HTTP-antwoord, zoals die van een schema dat afgaat, draagt geen header; daar zie je de omleiding aan model van het opgeslagen antwoord. Een webpagina kan deze header niet lezen; lees deprecation in GET /models.

Vanaf de datum hangt het af van de policy van je organisatie en je sleutel (Uitgefaseerde modellen):

  • Omleiden (de standaard): de beurt draait op de vervanger, alsof je die had genoemd. model in het antwoord noemt de vervanger, en het verbruik wordt tegen diens prijs berekend. Weigert je sleutel de vervanger, dan krijg je dezelfde status als wanneer je hem zelf had genoemd, met een melding die beide modellen noemt: The model 'gpt-5.6-sol' was retired on 2026-12-01 and its replacement 'claude-sonnet-5' cannot take this request: ....
  • Blokkeren: op POST /responses 400 met model in error.param en de melding The model 'gpt-5.6-sol' was retired on 2026-12-01. Use 'claude-sonnet-5' instead. Noem de vervanger in je verzoek.

Dat geldt voor elke manier waarop een beurt start: POST /responses, POST /messages, een schema dat afgaat, een agent die een andere agent aanroept en een taak. Een achtergrondrun wordt omgeleid op het moment dat hij wordt aangenomen, niet wanneer hij wordt opgepakt: een achtergrondrun die vóór de datum is aangenomen en pas daarna wordt opgepakt, roept nog het uitgefaseerde model aan. Zet vlak voor de datum geen achtergrondruns meer in de wachtrij op dat model; noem de vervanger.

Modellen die je sleutel bereikt zonder ze te noemen

Een beurt kan meer modellen aanroepen dan het model in model: een afbeeldingsmodel via zijn tool, een audiomodel dat een opname transcribeert, het embeddingmodel dat in je kennis, geheugen en skills zoekt, en het model dat een lange thread samenvat. Elk daarvan wordt aan de regio's van je sleutel getoetst, dus elk staat ook met zijn region en country in een lijst.

GET /models?kind=image toont de afbeeldingsmodellen, elk met de tool_name waaronder het wordt aangeboden (builtin__generate_image__...). GET /models?kind=audio toont de audiomodellen, elk met de tool_name van zijn transcriptietool (builtin__transcribe_audio__...); dezelfde modellen bedienen POST /audio/transcriptions en input_audio (Audio transcriberen). GET /models?kind=embedding toont de embeddingmodellen. Anders dan de standaard kind=chat tonen deze elk model dat je organisatie heeft, en allowed zegt of het modelbeleid van je sleutel het nu toelaat; false betekent dat een aanroep erop wordt geweigerd. Voor een afbeeldingsmodel is dat alleen de helft van het model: de tool moet ook aanstaan en toegestaan zijn (afbeeldingen genereren). Elk chatitem heeft kind: "chat" en allowed: true, omdat een chatmodel dat je sleutel niet mag aanroepen niet in de lijst staat.

GET /model-roles noemt de modellen die Core voor je kiest:

{
  "items": [
    {"object": "model_role", "role": "embedding", "model": "text-embedding-eu",
     "region": "eu", "country": "nl", "allowed": true},
    {"object": "model_role", "role": "summariser", "model": null,
     "region": null, "country": null, "allowed": null}
  ],
  "next_cursor": null
}

summariser staat erbij als je sleutel compaction heeft en samenvat (elke modus behalve window). Zonder eigen compaction.model noemt het het samenvattingsmodel dat in jouw omgeving is ingesteld, als dat er is; een model van null betekent dat het model van de beurt zelf samenvat, en dat wordt met de beurt beslist. Zodra de maandlimiet van je sleutel of van je organisatie is bereikt, worden er geen nieuwe samenvattingen gemaakt en gaan de oudere beurten van de thread ongewijzigd mee. embedding staat erbij als de omgeving een embeddingmodel voor je organisatie heeft. Geen van deze lijsten houdt rekening met hosting.region van een verzoek: dat beperkt één beurt.

Antwoordvelden

{
  "id": "resp_0KQ2mR...",
  "object": "response",
  "status": "completed",
  "model": "<een model-id>",
  "output": [
    {
      "type": "message",
      "role": "assistant",
      "content": [{ "type": "output_text", "text": "..." }]
    }
  ],
  "usage": {
    "input_tokens": 31,
    "input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
    "output_tokens": 22,
    "output_tokens_details": { "reasoning_tokens": 0 },
    "total_tokens": 53
  },
  "conversation": { "id": "thr_0KQ2mR..." }
}

Er is geen veld output_text: de OpenAI-SDK berekent dat uit output, en dat kun jij ook. conversation is de thread waarop de items van een opgeslagen beurt zijn geschreven, en null bij een beurt zonder opslag. Het id van het verzoek dat het antwoord maakte staat in de response-header X-Request-Id.

Verwerk output als een lijst. Deze kan assistentberichten bevatten, function_call-items die je applicatie moet uitvoeren, mcp_call-items voor een tool die Core voor je uitvoerde, en een redeneeritem als het denkwerk van het model met het antwoord is meegekomen. Een achtergrondrun die op goedkeuring wacht toont elke wachtende aanroep als een mcp_approval_request-item, en een aanroep waarover na het wachten is beslist, noemt zijn verzoek in approval_request_id. Dat item bevat tekst als er om een samenvatting is gevraagd en het model daar een leesbare vorm voor heeft. Core vraagt er bij elke beurt om, tenzij je verzoek, je sleutel of je organisatie iets anders zegt. Zonder tekst is summary gelijk aan [], wat zegt dat het model heeft nagedacht en dat voor zich heeft gehouden:

{ "id": "item_0KQ2mR...", "type": "reasoning", "summary": [] }

Modellen die je installatie via een OpenAI-compatibele chat-completions-API bereikt, gedragen zich anders, omdat die API geen manier heeft om een samenvatting te vragen:

  • Een server die het denkwerk van zijn model teruggeeft, stuurt het mee of iemand erom vroeg of niet. Core zet het op de plek van een samenvatting: één summary_text van het redeneeritem, dezelfde streamgebeurtenissen, en een thinking-blok op POST /messages. Het is het eigen denkwerk van het model, woordelijk en zo lang als het duurde, geen samenvatting die voor een lezer is geschreven. Core geeft het alleen terug bij een beurt die om een samenvatting vraagt; bij elke andere beurt wordt het weggelaten, al tellen de tokens ervan nog wel mee in usage.
  • Een server die geen denkwerk teruggeeft, geeft Core niets om het item van te maken. output bevat dan helemaal geen redeneeritem, ook geen item met een lege samenvatting.

Een Azure-deployment die op chat completions is ingesteld (de Azure-provider van een deployment die de Responses API niet aanzet) is geen van beide: het stuurt geen samenvatting en geen denkwerk, dus een verzoek dat daar om een samenvatting vraagt wordt geweigerd met een 400 die reasoning.summary noemt (thinking op POST /messages), in plaats van zonder beantwoord te worden. De samenvatting die Core uit zichzelf vraagt, wanneer je sleutel of organisatie dat zegt, wordt nooit geweigerd, en "summary": "none" evenmin.

Om een samenvatting vragen garandeert er geen. OpenAI schrijft redeneersamenvattingen alleen voor een account dat het heeft geverifieerd. Op een installatie waarvan het OpenAI-account dat niet is, krijgt een beurt die bij een OpenAI-model om een samenvatting vraagt nog steeds een 200, zonder de tekst van de samenvatting.

Wil je bij één beurt geen samenvatting, stuur dan "reasoning": {"summary": "none"} op POST /responses, of "thinking": {"type": "adaptive", "display": "omitted"} op POST /messages. Beide gaan boven de instelling van je sleutel en je organisatie. Bij een agent die reasoning vastlegt, geldt de waarde van de agent en noemt het antwoord reasoning als overschreven.

Waar de bronnen van een antwoord staan

Heeft een beurt een kennistool of builtin__web_fetch gebruikt, dan komen de bronnen op twee plekken terug.

Het resultaat van de tool noemt alles wat die heeft gelezen, of het model het nu aanhaalt of niet:

{
  "type": "function_call_output",
  "call_id": "call_0KQ...",
  "name": "knowledge__handboek",
  "output": "[1] …",
  "citations": [{ "kind": "file", "ref": "chk_0KQ…", "title": "Handboek" }]
}

kind is url voor een pagina die het model heeft opgehaald -- ref is dan een adres dat je kunt openen -- en file voor een passage uit je eigen geïndexeerde inhoud, waar ref het id is van het tekstblok waar die uit komt. Een tool die geen bronnen leest, antwoordt [].

Dat item staat in het gesprek, niet in het antwoord: lees het terug met GET /threads/{thread_id}/items (Een gesprek voortzetten). Het antwoord zelf draagt de ronde als één mcp_call — het item waarmee de Responses-API een tool aanduidt die de server voor je uitvoerde — en een mcp_call heeft geen plek voor een bronnenlijst. Daarom zijn de annotaties hieronder het eigen verslag van het antwoord van wat het aanhaalde.

De tekst van de assistent zegt waar die ze aanhaalt, als annotations bij het tekstdeel:

{
  "type": "output_text",
  "text": "Verlenging is jaarlijks [1].",
  "annotations": [
    { "type": "file_citation", "file_id": "chk_0KQ…", "filename": "Handboek", "index": 24 }
  ]
}

De posities zijn posities in diezelfde text: index bij een file_citation, start_index en end_index bij een url_citation, die ook url en title draagt. Ze beslaan de markering [1] zelf, zodat je er een link van kunt maken zonder de tekst te doorzoeken. De posities tellen Unicode-codepunten, geen UTF-8-bytes en niet de UTF-16-eenheden waarmee een JavaScript-string telt: knip in JavaScript in Array.from(text) in plaats van in text zelf wanneer het antwoord een emoji of een ander teken buiten het Basic Multilingual Plane kan bevatten.

Zoekt een beurt meer dan eens, dan loopt de nummering door: een tweede zoekopdracht na drie passages geeft het model [4] en [5], zodat een antwoord dat beide zoekopdrachten aanhaalt bij elke markering zijn eigen bron krijgt. Een beurt die op de achtergrond loopt (background: true) draagt geen annotaties; de bronnen staan bij elk resultaat.

Core gokt nooit. Een markering die een passage noemt die de beurt nooit heeft opgehaald, krijgt geen annotatie en blijft in de tekst staan zoals het model die schreef. Een pagina die het model wel las maar niet aanhaalde, staat bij het resultaat en in geen enkele annotatie.

Promptcaching

Een modelprovider kan het begin van een verzoek dat hij net verwerkte bewaren. Begint het volgende verzoek met precies dezelfde inhoud, dan leest de provider dat deel uit zijn cache in plaats van het opnieuw te verwerken; dat is sneller en goedkoper. Het opslaan in de cache kan extra kosten.

Hoe dat gaat, hangt af van het model:

  • Claude-modellen. Core vraagt de provider voor jou om het begin van het verzoek te cachen, wanneer een latere aanroep het waarschijnlijk terugleest: een beurt die tools aanbiedt (elke toolronde verstuurt het hele verzoek opnieuw), elke modelaanroep na de eerste binnen één beurt, en elke beurt van een opgeslagen gesprek. Eén aanroep zonder tools in een beurt zonder opslag wordt niet gecachet. Een cache-item blijft ongeveer vijf minuten na de laatste keer dat het gelezen werd.
  • Andere modellen (OpenAI, ook via Azure OpenAI, Gemini en Scaleway). De provider cachet zelf, waar hij caching ondersteunt; jij en Core sturen daar niets voor mee.

Een verzoek dat korter is dan het minimum van de provider wordt helemaal niet gecachet, en kost ook niets extra.

Wat het antwoord je vertelt

usage.input_tokens_details zegt wat caching voor deze beurt deed. Beide getallen zitten al in input_tokens:

Veld Betekenis
cached_tokens invoer die de provider uit zijn cache las, gerekend tegen het cachetarief van het model
cache_write_tokens invoer die de provider in zijn cache schreef, gerekend boven het gewone invoertarief; alleen Claude-modellen melden dit, andere modellen geven 0

Dezelfde getallen worden vastgelegd als de verbruikssoorten cached_tokens en cache_write_tokens, en geprijsd met cached_input_eur_per_million_tokens en cache_write_eur_per_million_tokens van het model (wat je verbruik kostte). Bij Claude kost het schrijven van een cache-item ongeveer een kwart meer dan dezelfde invoer één keer versturen, en kost het teruglezen een fractie daarvan. Caching loont dus zodra een item minstens één keer wordt teruggelezen.

Meer uit de cache halen

De cache herkent alleen inhoud die vanaf het allereerste begin van het verzoek gelijk is, in de volgorde tools, instructies, berichten. Verandert er vroeg in het verzoek iets, dan begint de cache opnieuw voor alles wat erna komt. Houd tussen de beurten van één gesprek deze dingen gelijk:

  • instructions;
  • de agent en zijn versie, als je er een noemt;
  • de tools en skills die de beurt aanbiedt, die ook een beleidsregel kan veranderen;
  • reasoning.

Sommige dingen laten de cache opnieuw beginnen, wat je ook stuurt:

Antwoordstatus

Controleer de status van het antwoord, ook als het HTTP-verzoek slaagt:

status Betekenis
completed De beurt is afgerond. Is het laatste uitvoeritem een function_call, dan voert je applicatie die tool uit en stuurt het resultaat in de volgende beurt.
failed De modelaanroep is mislukt of heeft de time-out bereikt. error.message bevat een vaste melding van Core, niet de fouttekst van de leverancier.
incomplete De beurt stopte voordat ze af was: het model bereikte zijn uitvoerlimiet, de beurt bereikte de limiet voor toolrondes, een live check hield het antwoord achter, of een achtergrondrun bereikte zijn kostenlimiet. incomplete_details.reason zegt welke (hieronder).
requires_action Een achtergrondrun pauzeert tot je de toolresultaten instuurt, of tot je beslist over elke aanroep die hij laat wachten op goedkeuring.
queued, in_progress Een achtergrondrun wacht of wordt uitgevoerd.
cancelled Een achtergrondrun of een gestreamde beurt is geannuleerd.

Een modelfout staat in het antwoordobject omdat Core het verzoek heeft geaccepteerd en de beurt heeft uitgevoerd. Validatiefouten en ontbrekende rechten geven HTTP-foutcodes.

Een afgekapt antwoord

Een antwoord met incomplete bevat incomplete_details.reason:

reason Wat er gebeurde Wat je kunt doen
max_output_tokens Het model stopte bij zijn uitvoerlimiet: jouw max_output_tokens, of het eigen maximum van het model. De tekst die er al stond, staat in output, in een berichtitem met status: "incomplete". Een toolaanroep die halverwege is afgekapt, komt niet terug. Vraag om een korter antwoord, verhoog max_output_tokens, of ga verder vanaf de tekst die je hebt.
max_tool_rounds De beurt heeft al zijn toolrondes gebruikt. Verhoog max_tool_rounds, of stuur een vervolgbeurt.
usage_cap_exceeded De maandlimiet van je sleutel of van je organisatie werd bereikt terwijl de beurt liep. Wat hij tot dan toe opleverde staat in output en wordt in rekening gebracht. Wacht tot de maand omslaat of laat de limiet verhogen; tot die tijd antwoordt het volgende verzoek met 429.
cost_limit Een achtergrondrun, de agenttaken die hij startte en hun runs bereikten samen de kostenlimiet van de run. Wat tot dan toe is opgeleverd staat in output en wordt in rekening gebracht. Stuur een nieuwe run met een hogere cost_limit_eur, of ga verder met wat je hebt.
content_filter Een live check van de agent hield het antwoord achter. Zijn alle blokkerende checks op de uitvoer van de agent patronen, dan stopt een gestreamd antwoord waar de verboden tekst begint; anders staat er, gestreamd of niet, geen antwoord in output. De header X-Core-Check, of bij een gestreamde beurt GET /responses/{id}/checks, noemt de check. Laat de persoon zien dat er geen antwoord gegeven kan worden. Vraag de beheerder van je organisatie naar de check als hij antwoorden tegenhoudt die hij niet zou moeten tegenhouden.

Een gestreamde beurt eindigt met response.incomplete met hetzelfde object. Op POST /messages is een afgekapt antwoord stop_reason: "max_tokens", een achtergehouden antwoord stop_reason: "refusal", en is een beurt zonder toolrondes over of gestopt op een limiet stop_reason: "pause_turn": de leverancier heeft geen woord voor een limiet. Een kostenlimiet hoort bij een achtergrondrun, en die start POST /messages niet.

Waar de catalogus van je installatie de maximale uitvoer van een model vermeldt, wordt een hogere max_output_tokens bij de modelaanroep naar dat maximum verlaagd. Het antwoord toont nog steeds de waarde die je stuurde.

Heeft je beheerder een routeringsconfiguratie ingesteld, dan beëindigt een model dat niet beschikbaar is de beurt niet: Core voert hem uit op het volgende model en vertelt in model welk model antwoordde. Zie Terugvallen op een ander model.

Opslag en HTTP-status

Bij een verzoek zonder streaming betekent 201 met een Location-header dat het antwoord is opgeslagen. Haal het op met GET /responses/{id}. Een aanroep zonder opslag geeft 200 en bewaart geen gespreksinhoud. Opslag hangt af van de capability threads en de instelling store; zie Een gesprek voortzetten.

Het antwoord streamen

Stel "stream": true in om uitvoer te ontvangen als Server-Sent Events:

curl -N -X POST "$CORE_URL/responses" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"model": "<een model-id>", "input": "Leg bearer-tokens uit.", "stream": true}'

Elke gebeurtenis bevat een event:-regel, een data:-regel met JSON en een lege regel. De JSON bevat een sequence_number dat begint bij 0. Daarmee kun je ontbrekende gebeurtenissen herkennen.

Gebeurtenis Afhandeling
response.created Bewaar het antwoord-id.
response.in_progress Toon dat de beurt wordt uitgevoerd.
response.output_item.added Begin een item op output_index.
response.content_part.added Begin het tekstdeel van het item.
response.output_text.delta Voeg delta toe aan de ontvangen tekst.
response.output_text.annotation.added Eén bron die de afgeronde tekst aanhaalt, met de posities die haar aanhalen (hierboven).
response.output_text.done Lees de volledige tekst van het item.
response.content_part.done Rond het tekstdeel af.
response.output_item.done Verwerk het volledige item, bijvoorbeeld een function_call.
response.completed, response.incomplete, response.failed Lees het uiteindelijke antwoordobject. De stream eindigt hier. Na een annulering is het response.completed met status: cancelled.
error De beurt is uitgevoerd maar kon niet worden opgeslagen. Lees code; er is geen resultaat bewaard.

Sla commentaarregels zoals : keepalive over. Die houden de verbinding open.

Modeltekst en een samenvatting van de redenering worden in delen verstuurd. Het redeneeritem van een denkend model komt eerst: response.output_item.added, dan response.reasoning_summary_part.added, één response.reasoning_summary_text.delta per stuk terwijl het model nadenkt, dan response.reasoning_summary_text.done, response.reasoning_summary_part.done en response.output_item.done, voordat het bericht begint. Op POST /messages komt dezelfde redenering binnen als een thinking-blok dat met thinking_delta-gebeurtenissen wordt gevuld. Een model dat opnieuw nadenkt nadat het aan zijn antwoord is begonnen, krijgt een tweede redeneeritem: het bericht wordt eerst afgerond, het nieuwe redeneeritem wordt op de volgende output_index toegevoegd en op dezelfde manier gestreamd, en tekst die daarna komt is een tweede bericht. output bevat de items in die volgorde; lees dus elk message-item en voeg hun tekst samen, zoals output_text van de OpenAI-SDK doet, en niet alleen het eerste. Op POST /messages is dat een tweede thinking-blok en een tweede text-blok. Weigeringen en toolaanroepen komen als volledige items binnen, met response.output_item.added gevolgd door response.output_item.done. Als het model tijdens de uitvoer faalt, rondt Core het huidige item af met de ontvangen tekst, met status: incomplete op dat item, en eindigt de stream met response.failed.

Een gestarte stream gebruikt HTTP 200 en Content-Type: text/event-stream, ook als het antwoord wordt opgeslagen. De headers worden verstuurd voordat de beurt begint. Gebruik het id uit de eerste gebeurtenis om een opgeslagen resultaat op te halen.

Fouten die Core vóór de stream ontdekt, zoals een ongeldig verzoek of een niet-toegestaan model, geven nog steeds een gewone HTTP-status, met het foutobject van OpenAI als body. Modelfouten na de start verschijnen als response.failed.

Als de verbinding wegvalt, loopt de beurt door en wordt het verbruik geregistreerd. Is opslag ingeschakeld, haal het resultaat dan later op met GET /responses/{id}. Wil je dat de beurt stopt, annuleer hem dan (hieronder). Streamen combineren met een achtergrondrun geeft 400.

Gebruik een client die Server-Sent Events ondersteunt. De gegenereerde Python-client ondersteunt alleen antwoorden zonder streaming, en de Swagger UI-verkenner toont een body pas als het antwoord klaar is. Ontvangt een SSE-client alsnog alle gebeurtenissen tegelijk, vraag je beheerder dan te controleren of een proxy ze buffert, of dat de leverancier van het model gestreamde uitvoer filtert voordat hij die vrijgeeft (Azure OpenAI doet dat, tenzij het contentfilter van de installatie in asynchrone modus draait).

Een gestreamde beurt stoppen

Een stopknop in je applicatie annuleert de beurt met het id uit response.created. Daarvoor is geen extra capability nodig: de sleutel die de beurt startte, mag hem ook stoppen. Een beurt van een andere sleutel of een achtergrondrun annuleren vraagt runs; zonder runs krijg je 404.

curl -s -X POST "$CORE_URL/responses/resp_0KQ2mR.../cancel" -H "Authorization: Bearer $CORE_KEY"

Core stopt de beurt waar hij op dat moment op wacht: het model dat nog tekst stuurt, of een tool die nog loopt. Het antwoord is het opgeslagen antwoordobject met status: cancelled. Het bevat de items die de beurt al had gemaakt. Een bericht dat het model nog aan het schrijven was, staat erin met de tekst die al was aangekomen en status: incomplete. Wie de stream nog leest, krijgt als laatste gebeurtenis response.completed met datzelfde antwoord.

Wat in rekening wordt gebracht:

  • elke ronde die al klaar was, zoals altijd;
  • voor de modelaanroep die werd afgebroken: wat de leverancier al over het verbruik had gemeld. Sommige leveranciers melden de invoertokens aan het begin, andere pas aan het eind. Meldde de leverancier nog niets, dan wordt voor die aanroep niets geregistreerd.

Nog een keer annuleren geeft hetzelfde antwoord, zonder fout. Was de beurt al klaar voordat de annulering hem bereikte, dan krijg je 409. Is de beurt niet binnen enkele seconden gestopt, dan krijg je 503; annuleer dan gewoon nog een keer. Een beurt zonder opslag heeft daarna geen uitvoer in het antwoord op de annulering, want Core bewaart die niet. De stream zelf toont hem wel.

POST /messages kent geen annulering.

Veelvoorkomende fouten

HTTP-status Oorzaak
400 Het verzoek is geweigerd: een veld dat Core niet kan inwilligen, een veld dat ontbreekt, of een veld dat een agent vastlegt. error.param noemt het. Op model kan het ook een uitgefaseerd model zijn dat je policy blokkeert.
401 De secret van de API-sleutel ontbreekt of is ongeldig.
403 Het sleutelbeleid weigert het model, de organisatie is opgeschort, of een hostingeis van het verzoek of de sleutel is niet voldaan (zie hieronder).
403 op agent Het sleutelbeleid vereist een agent en het verzoek noemt er geen. Noem een van de agents van de sleutel.
404 Een genoemd model, prompt, skill of bestand is niet beschikbaar, of de capability is uitgeschakeld.
409 De idempotentiesleutel is eerder gebruikt voor een aanroep zonder opslag.
503 Een benodigde dienst is niet beschikbaar.

Elk van deze antwoorden draagt het foutobject van OpenAI in plaats van problem+json, net als GET /responses/{id}, POST /responses/{id}/tool_outputs en POST /responses/{id}/cancel. Zie Fouten, paginering en herhalen voor de velden en het afhandelen van herhalingen.

Hostingweigeringen

Een hosting-eis, of het regions_allow-beleid van de sleutel, geeft 403 met precies een van deze vier meldingen in detail:

detail Wat je kunt aanpassen
The requested hosting region is not allowed for this key. Vraag een regio aan die in het regions_allow-beleid van je sleutel staat (Sleutels), of laat hosting weg.
Where this model runs is not known. Dit hoort niet voor te komen bij een model dat GET /models toont; meld het bij je beheerder.
The model is not hosted in a region this key allows. Kies in GET /models een model waarvan de regio of het land in de lijst van je sleutel staat, of vraag je operator die lijst te verruimen.
The model is not hosted in the requested region. Laat hosting.region weg, of kies een model dat wel in die regio draait (Modellen toont de hosting per model).

Time-outs

Een synchrone beurt kan maximaal vijf minuten duren, inclusief toolrondes. Stel de time-out van je client daarop in. Gebruik een achtergrondrun als je de verbinding niet zo lang kunt openhouden.