Naar inhoud springen
16/30Hoofdstuk 16 van 30

De context window, tokens en de rekening, gemeten

Een gemeten gesprek van 40 beurten kost 22× zijn eigen lengte aan input tokens. Caching verlaagt dat 68%; één timestamp verkeerd kost 20%.

Op deze pagina

Hier is een supportgesprek van veertig beurten, per beurt gefactureerd. Niets eraan is ongewoon: een developer die iets vraagt over een API, een assistant die antwoordt in een alinea of twee. De hele uitwisseling is 5.090 tokens tekst — ongeveer acht pagina’s.

beurtprompt tokensnieuwe tekstoutputkosten van deze beurtlopend totaal
121318183$0.002622$0.002622
589214123$0.003260$0.014354
101,65619114$0.004680$0.035530
202,86818103$0.006972$0.094426
303,9412099$0.009070$0.174170
404,94717142$0.011598$0.274386

Lees de tweede en derde kolom samen. Bij beurt 40 typte de gebruiker zeventien tokens en werd er afgerekend voor 4.947. De vraag was niet moeilijker dan de eerste; hij was korter. Wat veranderde, is dat het verzoek het hele gesprek opnieuw meedroeg, voor de veertigste keer.

Totaal aantal gefactureerde input tokens over die veertig calls: 112.617. Het gesprek is 5.090 tokens lang. Je betaalde ervoor tweeëntwintig keer opnieuw.

Dit hoofdstuk gaat over waarom dat gebeurt, hoe het op de factuur van elke provider heet, en aan welke van de vijf dingen waarvoor je betaalt je iets kunt doen.

Details tonen

Wat dit hoofdstuk nodig heeft uit deel II.

  • Hoofdstuk 7 bouwde de tokenizer. Een token is hier ook de eenheid — dezelfde eenheid, nu met een prijs.
  • Hoofdstuk 9 leidde self-attention en de O(n2)O(n^2)-kosten ervan af, in het vak over asymptotische notatie. Die kosten zijn waarom er überhaupt een limiet bestaat, en we linken er hier naar in plaats van het opnieuw uit te leggen.
  • Hoofdstuk 13 mat prefill tegenover decode en berekende wat een KV cache inneemt. Die twee fasen zijn wat de input- en outputkolommen hierboven werkelijk inkopen.

Al het andere is TypeScript, want dit is boekhouding voor een externe call en geen wiskunde over een model.

De duurste misvatting in deze business is dat een model een gesprek onthoudt.

Dat doet het niet, en het mechanisme uit hoofdstuk 13 zegt precies waarom. De toestand van een transformer tijdens generatie is de KV cache: de keys en values die voor elke token in de sequentie zijn berekend. Die cache leeft zolang één request duurt. Wanneer het request eindigt, is het proces dat hem vasthield vrij om iemand anders te bedienen, en is de cache weg. Er is aan de andere kant geen opslag per gebruiker, en geen sessie.

Dus het volgende request moet alles meebrengen wat het model geacht wordt te weten, en het model bouwt die toestand opnieuw op door een forward pass over de hele prompt te draaien voordat het één nieuwe token uitstuurt. Hoofdstuk 15 noemde de prompt "de volledige toestand". Dit is de fysieke reden: de prompt is de volledige toestand omdat niets anders de call overleeft.

De context window is de maximale lengte van die prompt plus het antwoord. Het is een plafond op hoeveel toestand je kunt herbouwen, geen container die tussen requests iets vasthoudt. Hem "het geheugen van het model" noemen draait de causaliteit om — je vult geen geheugen, je betaalt om er een opnieuw op te bouwen.

Daar komt die tweeëntwintig vandaan. Beurt nn draagt alle n1n-1 vorige beurten mee, dus de totale input over een gesprek van nn beurten is de som van een groeiende reeks, en die is kwadratisch:

total input  =  i=1n(s+hi)  =  Θ(n2)\text{total input} \;=\; \sum_{i=1}^{n} \big(s + h_i\big) \;=\; \Theta(n^2)

waarbij ss de system prompt is en hih_i de geschiedenis bij beurt ii. De gemeten cumulatieve input fitten op an2+bnan^2 + bn over de veertig beurten geeft 60.22n2+432.25n60.22\,n^2 + 432.25\,n, wat 113.645 tokens voorspelt bij beurt 40 tegenover 112.617 gemeten. De kwadratische term domineert en de lineaire term is wat de gebruiker daadwerkelijk typte.

De consequentie is de zin om uit dit hoofdstuk mee te nemen: je rekening groeit met het kwadraat van het gesprek, niet met de laatste vraag. Dezelfde veertig vragen zonder enige geschiedenis kosten $0.066036. De geschiedenis behouden kostte $0.274386. Geschiedenis vermenigvuldigde de rekening met 4,2, en blijft dat doen, want de vermenigvuldiger is de gesprekslengte.

De window is eindig om twee redenen die dezelfde kant op trekken. De eerste is die van hoofdstuk 9: attention vergelijkt elke token met elke andere token, dus het werk van die laag groeit met het kwadraat van de sequentielengte. De tweede is geheugen: de KV cache groeit lineair met de sequentielengte, en hoofdstuk 13 deed die rekensom — bij lange sequenties is hij groter dan de weights.

Beide limieten zijn aangevallen en geen van beide is verdwenen. FlashAttention1 reorganiseert de berekening zodat er veel minder naar high-bandwidth memory wordt gelezen en geschreven, waardoor lange sequenties praktisch worden zonder de asymptotische kosten te veranderen. Position Interpolation2 en YaRN3 vergroten de bruikbare window van een getraind model door de positional encodings uit hoofdstuk 9 te herschalen in plaats van opnieuw te trainen. Samen verklaren ze waarom windows in vijf jaar van 2K naar 1M gingen.

Wat ze niet deden, is lange contexts gratis maken. Ze maakten het plafond hoger en de helling zachter. De helling is er nog steeds, en dat is wat de prijstiers later in dit hoofdstuk meten.

Bijna elke kostencalculator op internet modelleert een API-call als input tokens keer een inputprijs plus output tokens keer een outputprijs. Dat klopte in 2023. Nu is het fout op een manier die rekeningen in beide richtingen met een factor twee of meer laat afwijken.

Er zijn vijf factureerbare token-categorieën:

bucketwat het istypische prijs, relatief tot input
uncached inputprompt tokens die het model vers moest verwerken
cache readprompt tokens geleverd uit een opgeslagen prefix0,1×
cache writeprompt tokens die bij deze call in de cache worden opgeslagen1,25× tot 2×
outputtokens die het model genereerde en naar je stuurde5× tot 6×
reasoningtokens die het model genereerde en niet naar je stuurdeoutput-tarief

Drie van die vijf bestonden twee jaar geleden niet als aparte regels, en de twee cacheregels zijn degene die mensen verkeerd begrijpen, omdat een cache write meer kost dan gewone input, niet minder. Je betaalt een premie om iets op te slaan zodat je korting kunt betalen om het terug te lezen, en of dat goed uitpakt hangt volledig af van hoe vaak je het leest.

De reasoning-bucket is die van hoofdstuk 12, nu met een prijs erop, en bevat een detail dat het waard is om expliciet te zeggen: Google's documentatie zegt dat pricing "is based on the full thought tokens the model needs to generate, despite only the summary being output from the API."4 Je wordt gefactureerd voor tokens die nooit naar je worden verzonden. Het is de enige bucket waarvan je de inhoud niet kunt tellen, inspecteren of verifiëren.

Nu het deel dat dit een normalisatieprobleem maakt in plaats van een vermenigvuldigingsprobleem. Elke provider rapporteert deze buckets onder andere namen, en — dit is de valkuil — twee van hen gebruiken hetzelfde woord voor twee verschillende hoeveelheden.

Neem één call: 4.837 tokens gelezen uit cache, 110 vers, 142 zichtbare output tokens, 300 reasoning tokens.

three usage payloads, one callJSON
// OpenAI-compatible
{ "usage": { "prompt_tokens": 4947,
             "prompt_tokens_details": { "cached_tokens": 4837 },
             "completion_tokens": 442,
             "completion_tokens_details": { "reasoning_tokens": 300 } } }

// Anthropic
{ "usage": { "input_tokens": 110,
             "cache_read_input_tokens": 4837,
             "cache_creation_input_tokens": 0,
             "output_tokens": 442 } }

// Gemini
{ "usageMetadata": { "promptTokenCount": 4947,
                     "cachedContentTokenCount": 4837,
                     "candidatesTokenCount": 142,
                     "thoughtsTokenCount": 300 } }

Kijk naar prompt_tokens: 4947 en input_tokens: 110. Beide velden zijn het input token-aantal voor dezelfde prompt. Dat van OpenAI bevat de gecachte tokens; dat van Anthropic sluit ze uit — de documentatie noemt de identiteit expliciet, total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens.5 Anthropic's input_tokens betekent "de tokens na je laatste cache-breakpoint".

En kijk naar de output. OpenAI en Anthropic rapporteren allebei 442, waar de 300 reasoning tokens al in zitten. Gemini rapporteert 142 en zet de 300 in een eigen veld. Hoofdstuk 12 markeerde dit als een incompatibiliteit tussen twee manieren om hetzelfde werk te tellen; hier is wat het kost.

Een normalizer is dertig regels en is niet optioneel:

normalise.tsTS
export interface Usage {
  promptTokens?: number;        // input, NOT cached
  cachedInputTokens?: number;   // read from cache
  cacheWriteTokens?: number;    // written to cache on this call
  completionTokens?: number;    // output
  reasoningTokens?: number;     // billed apart from output (Gemini only)
}

const num = (v: unknown) => (typeof v === "number" && isFinite(v) ? v : 0);

export const fromOpenAI = (raw: any): Usage => {
  const u = raw.usage ?? {}, d = u.prompt_tokens_details ?? {};
  const cached = num(d.cached_tokens), write = num(d.cache_write_tokens);
  return {
    promptTokens: Math.max(0, num(u.prompt_tokens) - cached - write), 
    cachedInputTokens: cached,
    cacheWriteTokens: write,
    completionTokens: num(u.completion_tokens),   // reasoning already inside
    reasoningTokens: 0,
  };
};

export const fromAnthropic = (raw: any): Usage => {
  const u = raw.usage ?? {};
  return {
    promptTokens: num(u.input_tokens),            // already excludes cache
    cachedInputTokens: num(u.cache_read_input_tokens),
    cacheWriteTokens: num(u.cache_creation_input_tokens),
    completionTokens: num(u.output_tokens),
    reasoningTokens: 0,
  };
};

export const fromGemini = (raw: any): Usage => {
  const m = raw.usageMetadata ?? {}, cached = num(m.cachedContentTokenCount);
  return {
    promptTokens: Math.max(0, num(m.promptTokenCount) - cached),
    cachedInputTokens: cached,
    cacheWriteTokens: 0,
    completionTokens: num(m.candidatesTokenCount), // EXCLUDES thinking
    reasoningTokens: num(m.thoughtsTokenCount),    // billed at output rate
  };
};

Haal de drie payloads hierboven door de drie readers en alle drie leveren dezelfde Usage op, en dus hetzelfde getal: $0.006491. Die overeenstemming is het hele punt van die laag schrijven.

Doe je het fout, dan kost dat op dezelfde call dit:

foutgefactureerdafwijking
cached_tokens behandelen als extra bovenop prompt_tokens$0.0161652,49× — je rekent de prompt twee keer af
cache reads als gratis behandelen in plaats van 0,1×$0.0055240,85× — je slikt 15%
candidatesTokenCount lezen en thoughtsTokenCount negeren$0.00289155% van de call verdwijnt

De derde is de gevaarlijke, omdat hij stil faalt in de richting van goed nieuws. Je dashboard laat zien dat een reasoning-model minder dan de helft kost van wat het werkelijk kost, en nergens gaat er een fout af.

Met de buckets genormaliseerd is de kostenfunctie kort. Het enige niet voor de hand liggende deel is de tier lookup, die de volgende sectie uitlegt:

cost.tsTS
export interface Tier { maxPromptTokens: number | null; price: number }
export interface Pricing {
  input: Tier[]; output: Tier[];
  cachedInput?: Tier[]; cacheWrite?: Tier[]; reasoning?: Tier[];
}

const tierPrice = (tiers: Tier[] | undefined, contextSize: number, fallback?: Tier[]) => {
  const table = tiers ?? fallback;
  if (!table?.length) return 0;
  const sorted = [...table].sort(
    (a, b) => (a.maxPromptTokens ?? Infinity) - (b.maxPromptTokens ?? Infinity));
  for (const t of sorted)
    if (t.maxPromptTokens === null || contextSize <= t.maxPromptTokens) return t.price;
  return sorted[sorted.length - 1].price;
};

export function computeCost(pricing: Pricing, usage: Usage): number {
  const fresh = usage.promptTokens ?? 0;
  const read  = usage.cachedInputTokens ?? 0;
  const write = usage.cacheWriteTokens ?? 0;
  const out   = usage.completionTokens ?? 0;
  const think = usage.reasoningTokens ?? 0;
  const contextSize = fresh + read + write;   // the tier depends on the WHOLE prompt
  return fresh * tierPrice(pricing.input, contextSize)
       + read  * tierPrice(pricing.cachedInput, contextSize, pricing.input)
       + write * tierPrice(pricing.cacheWrite,  contextSize, pricing.input)
       + out   * tierPrice(pricing.output, contextSize)
       + think * tierPrice(pricing.reasoning, contextSize, pricing.output);
}

Twee ontwerpkeuzes daar zijn het verdedigen waard. De fallbacks — cacheprijzen die terugvallen op input, reasoning op output — coderen wat een ontbrekende tabel betekent: reasoning tokens op Gemini worden gefactureerd tegen het output-tarief, dus een ontbrekende reasoning-prijs is niet nul, maar de outputprijs. En contextSize telt alle drie input-buckets op in plaats van alleen de verse, omdat de tier wordt gekozen op basis van hoe lang de prompt is, niet op basis van hoeveel ervan tegen volle prijs werd afgerekend.

Een prompt cache slaat de berekende toestand van het model op voor een prefix van je prompt, zodat een later request met dezelfde prefix het opnieuw berekenen overslaat. Uit het woord "prefix" volgen vier eigenschappen, en alle vier verrassen mensen.

De cache matcht vanaf het begin van de gerenderde prompt naar voren, en stopt bij de eerste byte die verschilt. Er is geen gedeeltelijke credit voor content die later in een andere volgorde verschijnt. OpenAI zegt het plat: "cache reuse requires the entire rendered prefix to match."6

Daaronder wordt niets gecachet en komt er geen fout terug. Bij OpenAI is het minimum 1.024 tokens voor GPT-5.6 en later en 2.048 voor oudere modellen. Bij Anthropic varieert het van 512 tot 4.096 afhankelijk van het model — 1.024 voor Claude Sonnet 4.5, 4.096 voor Claude Haiku 4.5. Als beide cachevelden nul terugkomen, is dat meestal de reden.

Schrijven kost meer dan lezen, en meer dan niet cachen

Link naar de sectie: Schrijven kost meer dan lezen, en meer dan niet cachen

Bij OpenAI en Anthropic is een cache write 1,25× het uncached input-tarief voor de kortlevende cache, en Anthropic's cache van één uur is 2×. Een read is 0,1×. Google rekent niets voor schrijven maar verhuurt de opslag: $4.50 per miljoen tokens per uur op Gemini 2.5 Pro.

Anthropic's standaardentry leeft vijf minuten, gratis ververst bij elke hit. Die van OpenAI is minstens dertig minuten na de laatste write of reuse. En OpenAI merkt op dat gecachte toestanden op afzonderlijke machines leven, dus een request hit alleen als het wordt gerouteerd naar de machine die de entry vasthoudt — en dat is waar prompt_cache_key invloed op heeft, zonder garantie.

Het break-evenpunt is klein genoeg om te onthouden, en OpenAI's documentatie doet de rekensom: een prefix één keer schrijven en één keer hergebruiken kost 1,35× de gewone inputkosten, tegenover 2× om hem twee keer uncached te verwerken; over tien requests kosten één write en negen reads 2,15× tegenover 10×. Eén reuse betaalt de write terug. Anthropic komt op dezelfde plek uit: één read voor de cache van vijf minuten, twee voor de cache van één uur.

Nu opnieuw het gesprek van veertig beurten, met caching aan en een stabiele prefix:

uncached inputcache readscache writestotaal
geen cache112,617$0.274386
caching2,887104,7834,947$0.088250

Achtenzestig procent goedkoper, en drie getallen in die tabel verdienen aandacht.

De cache grijpt pas in bij beurt 6. De prompt haalt tot dan geen 1.024 tokens, dus de eerste vijf beurten worden precies als voorheen gefactureerd — en de zesde wordt duurder gefactureerd, met de 1,25× write-premie, omdat dit de beurt is die de cache vult. De eerste read komt bij beurt 7. De 2.887 uncached tokens in de tabel zijn de rekensom: vijf beurten waard, niet zes. Caching is een korting op lange prompts, en een kort gesprek krijgt er niets van.

De write-premie is $0.002474, oftewel 2,8% van de gecachte rekening. Elke beurt schrijft zijn nieuwe staart, veertig keer, en de hele write-premie is een afrondingsfout tegenover wat de reads bespaarden. De write charge is het waard om precies te begrijpen zodat je stopt met je er zorgen over maken.

Slechts 2.887 tokens werden tegen de volle inputprijs gefactureerd van de 112.617. Dat is de vorm van een werkende cache: bijna alles is een read.

De volgorde van de prompt bepaalt of dit überhaupt gebeurt

Link naar de sectie: De volgorde van de prompt bepaalt of dit überhaupt gebeurt

Hier is de failure die echt geld kost, en het is een bug van één regel.

Zet iets dat bij elke call verandert vooraan in de prompt — een timestamp, een request id, de naam van de gebruiker, een "vandaag is"-regel, een vers opgehaald document — en de prefix verschilt vanaf byte één. Niets matcht. Elke call is een miss. En omdat elke call een nieuwe prefix presenteert, schrijft elke call ook.

Hetzelfde gesprek, dezelfde veertig beurten, caching ingeschakeld, met een timestamp per call bovenaan de system prompt:

totaalversus
helemaal geen caching$0.274386
caching, stabiele prefix$0.088250−67,8%
caching, volatiele prefix$0.329251+20,0%

Prompt caching inschakelen maakte het gesprek twintig procent duurder dan het niet inschakelen. Je betaalde de 1,25× write-premie over 109.730 tokens en las nul terug. Er is geen fout, geen waarschuwing, en de feature staat aan.

Dus de regel, en dit is heel prompt caching in één regel: stabiele content vooraan, variabele content achteraan. System instructions, tool definitions en referentiemateriaal eerst; timestamps, gebruikersidentiteit en de huidige vraag als laatste. Anthropic maakt de hiërarchie expliciet — de cache volgt toolssystemmessages, en een wijziging op elk niveau invalideert dat niveau en alles erna, dus het bewerken van één toolbeschrijving invalideert de hele cache.5

Twee consequenties waar mensen over struikelen. Veranderen welke tools enabled zijn verandert de tool definitions, dus een feature flag die voor sommige gebruikers een tool toevoegt splitst je cache in tweeën. En bij Anthropic wijzigt het aan- of uitzetten van web search of citations de system prompt, wat de system- en message-caches invalideert zonder dat je één regel eigen tekst aanraakt.

De geschiedenis afkappen is niet de oplossing

Link naar de sectie: De geschiedenis afkappen is niet de oplossing

De voor de hand liggende reactie op een kwadratische rekening is stoppen met de hele geschiedenis sturen: bewaar de laatste twaalf berichten en laat de rest vallen. Het verlaagt de rekening, en het is meestal de verkeerde zet, en de meting zegt waarom.

strategietotaalversus volledige geschiedenis + cache
volledige geschiedenis, geen cache$0.274386+211%
volledige geschiedenis, caching$0.088250
laatste 12 berichten, geen cache$0.118712+35%
laatste 12 berichten, caching aan$0.122546+39%

Afkappen naar een window van twaalf berichten is 57% goedkoper dan alles uncached sturen — de vergelijking die iedereen maakt, en waarom de techniek populair is. Maar het is 39% duurder dan alles sturen met een werkende cache, en caching aanzetten naast truncation maakt het iets slechter in plaats van beter.

Het mechanisme is opnieuw de prefix. Een sliding window laat elke beurt het oudste bericht vallen, dus de prompt begint niet meer waar hij de vorige keer begon en elke beurt presenteert een nieuwe prefix. OpenAI's guidance zegt precies dit: "summarisation, compaction, or context truncation can change the prefix and reset cache reuse."6 Bij beurt 40 is de windowed prompt 813 tokens, onder het minimum van 1.024 tokens, dus hij kan helemaal niet gecachet worden.

En het geld is de goedkope helft van de kosten. Wat je liet vallen is de instructie die de gebruiker bij beurt 2 gaf en die het model bij beurt 40 nodig had. Truncation ruilt een rekening die je kunt zien voor een failure die je niet kunt zien, en het goed doen — compaction, gestructureerde notities buiten de window, geschiedenis op verzoek ophalen — is het onderwerp van hoofdstuk 24.

Een tier passeren herprijst het hele request

Link naar de sectie: Een tier passeren herprijst het hele request

Lange contexts zijn niet alleen duurder omdat ze langer zijn. Na een drempel zijn ze per token duurder, en de drempel geldt met terugwerkende kracht voor de hele prompt.

OpenAI's modelpagina voor gpt-5.6-terra zegt het in één zin: "Prompts with >272K input tokens are priced at 2x input and 1.5x output for the full request."7 Niet voor het overschot. Voor het geheel.

the most expensive token you will ever sendTEXT
prompt 271,999 + 500 output  ->  $0.5500
prompt 272,000 + 500 output  ->  $0.5500
prompt 272,001 + 500 output  ->  $1.0970

Eén token, vijfenvijftig cent. Als je service prompts bouwt uit opgehaalde documenten waarvan je de grootte niet beheerst, heb je een klif in je kostenmodel op een grens die niemand in je team heeft opgeschreven.

Google's pricing werkt op dezelfde manier met een drempel van 200.000 tokens: Gemini 2.5 Pro is $1.25 per miljoen input tokens voor prompts tot 200K en $2.50 daarboven, waarbij output van $10.00 naar $15.00 gaat.8 Anthropic ging de andere kant op — per 6 september 2026 stelt de documentatie dat Claude 4.6 en later de volledige window van één miljoen tokens tegen standaardpricing bevatten, dus "a 900k-token request is billed at the same per-token rate as a 9k-token request."9 Eerdere modellen hadden de surcharge nog.

Daarom is een prijs geen getal. Een prijs is een tabel met tiers keyed by prompt length, waarvoor Tier[] in de kostenfunctie bestaat, en daarom selecteert computeCost de tier met de hele prompt in plaats van elke bucket afzonderlijk.

Prefill, decode, en waarom output zes keer input kost

Link naar de sectie: Prefill, decode, en waarom output zes keer input kost

De vijf buckets mappen op de twee fasen van hoofdstuk 13, en zodra je de mapping ziet, lijken de prijsverhoudingen niet meer willekeurig.

Input tokens zijn prefill. De hele prompt gaat in één pass door het model, parallel verwerkt — grote matrixvermenigvuldigingen, compute-bound. De kosten per token zijn laag, en dit is de fase die time to first token bepaalt: een prompt van 4.947 tokens heeft 4.947 tokens prefill te doen voordat het eerste woord verschijnt.

Output tokens zijn decode. Ze worden één voor één geproduceerd, elk een volledige forward pass die de hele KV cache leest, terwijl de GPU vooral op geheugen wacht in plaats van rekent. Dit is de fase die tokens per second bepaalt, hij kan niet binnen één response worden geparalleliseerd, en daarom kost output ongeveer zes keer input op het hier geprijsde model: $12.00 tegenover $2.00 per miljoen tokens.

Drie consequenties volgen direct. Een cache read vervangt prefill-werk, dus hij koopt latency en geld tegelijk — dezelfde korting verschijnt als een lagere rekening en een kortere wachttijd op de eerste token. Reasoning tokens zijn decode die je nooit ziet, en daarom streamt een reasoning-model enkele seconden niets en antwoordt het daarna snel: hoofdstuk 12 waarschuwde voor de interfaceconsequentie, en dit is de factuurconsequentie. En een stream afbreken stopt de generatie niethoofdstuk 14 bouwde cancellation en liet de prijs over aan dit hoofdstuk, en de prijs is de volledige output count, omdat de tokens worden geproduceerd en gefactureerd of er nu iemand luistert of niet. Hetzelfde geldt voor het antwoord dat niemand bewaart: een antwoord bij beurt 40 vijf keer regenereren kost $0.057990 voor het ene dat op het scherm blijft staan.

De tokenizer van hoofdstuk 7 was Python en bleef daar. Budgettering gebeurt in de server die het request bouwt, dus moet het hier gebeuren, en er zijn precies drie nauwkeurigheidsniveaus beschikbaar.

Niveau één: lokaal tellen. js-tiktoken levert dezelfde BPE merge tables als de Python tiktoken, dus een byte-voor-byte identieke count voor OpenAI-encodings, zonder network call:

count.tsTS
import { getEncoding } from "js-tiktoken";

const enc = getEncoding("o200k_base");
const PER_MESSAGE = 4;   // role and delimiters added by the chat template
const PER_REPLY = 3;     // priming for the assistant turn

export function promptTokens(messages: { role: string; content: string }[]) {
  return messages.reduce(
    (sum, m) => sum + enc.encode(m.content).length + PER_MESSAGE, PER_REPLY);
}

De twee constanten doen ertoe en daar gaan lokale counts afwijken. Je tekst is niet wat wordt getokenized — de chat template uit hoofdstuk 11 wikkelt eerst elk bericht in role markers, en dat zijn tokens waarvoor je betaalt. Vier per bericht en drie voor de reply priming is de conventionele benadering voor OpenAI-chatmodellen; over de eenentachtig berichten van het gesprek hierboven tellen ze op tot 324 tokens, 6,4% van de lengte. De counts hier zijn gecontroleerd tegen de Python tiktoken uit hoofdstuk 7 op alle eenentachtig strings en zijn identiek.

Niveau twee: vraag het de provider. Anthropic biedt /v1/messages/count_tokens en Google biedt count_tokens, beide accepteren dezelfde request shape als een echte call en retourneren gratis een input token count. Gebruik ze wanneer je niet lokaal kunt tellen — en je kunt niet lokaal tellen voor Anthropic, waarvan de tokenizer niet gepubliceerd is. Anthropic's documentatie is zorgvuldig over wat ze je geeft: de count "is an estimate", en "may include tokens added automatically by Anthropic for system optimizations", waarvoor "you are not billed".10

Niveau drie: lees usage in de response. Dat is de waarheid, en die arriveert nadat het geld is uitgegeven. Precies daarom bestaan de eerste twee niveaus — om te beslissen of je het request verstuurt, niet om ervoor te factureren.

De dingen waarvoor je betaalt die niemand je laat zien

Link naar de sectie: De dingen waarvoor je betaalt die niemand je laat zien

Vier line items die niet als line items verschijnen.

De system prompt, betaald bij elke call. Die hierboven is 192 tokens met zijn template-overhead. Over veertig calls is dat 7.680 tokens — 5,6% van de volledige rekening van dit gesprek, voor acht regels die één keer zijn geschreven. Hij is ook de best mogelijke cachekandidaat, omdat hij zowel stabiel als eerste is.

Tool definitions. De naam, beschrijving en het JSON-schema van elke tool gaan bij elk request mee, en providers voegen daar scaffolding bovenop toe. Anthropic publiceert het getal: tools überhaupt inschakelen voegt een verborgen system prompt van 496 tokens toe op Claude Sonnet 4.5 met tool_choice ingesteld op auto, of 588 met any of een named tool.9 Dat is vóór je eigen schema’s. Hoofdstuk 18 bouwt de catalogus; hoofdstuk 24 meet wat die opeet.

Elke generatie, inclusief degene die je weggooit. Vijf regeneraties kosten vijf keer. De chat toont er één.

Gedachten die je niet te zien krijgt. Billing is gebaseerd op de volledige thought tokens, hoewel alleen een summary wordt geretourneerd, en geen enkele boekhouding van jou kan dat getal auditen.

Eén waarschuwing om mee af te sluiten, omdat het de natuurlijke volgende gedachte is en het antwoord niet het voor de hand liggende is.

Een window van een miljoen tokens betekent niet een miljoen bruikbare tokens. Retrieval-accuracy verslechtert met positie: Liu et al. vonden dat modellen informatie betrouwbaar vinden aan het begin en het einde van een lange input, en veel minder betrouwbaar in het midden.11 Een grotere window koopt de mogelijkheid om meer te sturen, niet de zekerheid dat het wordt gelezen.

Dat fenomeen wordt één keer in deze cursus gemeten — de retrieval rate op negen posities in dezelfde prompt van 853 tokens — en hoort thuis in hoofdstuk 24, waar het verandert wat een agent doet. Het wordt hier aangehaald omdat het verandert wat je zou moeten kopen: de goedkoopste token is degene die je niet hebt verstuurd.

Je kunt nu voorspellen wat een call zal kosten voordat je hem doet, achteraf lezen wat hij heeft gekost, en het verschil tussen die twee zien. Dat dekt alles over het request behalve het deel dat je nog niet hebt aangeraakt: de knobs.

Hoofdstuk 17 is sampling — temperature, top-p, top-k, de penalties, en de determinism die je niet hebt. Het begint met het ontmantelen van de meest wijdverspreide fout in het veld: dat temperature een creativiteitsknop is. Dat is het niet: temperature deelt de logits uit hoofdstuk 4 vóór de softmax, en hem verhogen maakt het model niet fantasierijker, het verhoogt de waarschijnlijkheid van tokens die het model zelf als slechter scoorde. Vanaf daar: waarom greedy decoding meetbaar slechtere tekst produceert dan sampling, waarom top-k en top-p falen op tegenovergestelde vormen van distributies, en het experiment dat het hoofdstuk afsluit: twintig identieke forward passes op temperature 0 komen bit-voor-bit identiek terug wanneer het model alleen draait, en dezelfde prompt in een batch zetten naast requests van iemand anders verplaatst 97% van zijn logits.

Ze matchen niet allemaal. De reden begint met het floating-point-vak uit hoofdstuk 2.


Alle prijzen, thresholds en multipliers in dit hoofdstuk zijn gelezen van de eigen pagina’s van de providers op 6 september 2026 en worden met die datum vermeld omdat ze zullen veranderen. De methode doet er meer toe dan de getallen: de buckets, de prefix-regel en de tier-rekensom zijn twee jaar stabiel geweest terwijl elk getal erin bewoog.

Stanford CS336 lecture 2, Resource accounting, is de dichtstbijzijnde academische behandeling van dit materiaal en de juiste volgende tekst om te lezen: hij doet aan de trainingskant dezelfde rekensom die dit hoofdstuk aan de inference-kant doet. De token counts hier zijn geproduceerd met js-tiktoken 1.0.21 met de o200k_base- en cl100k_base-encodings, over een gesprek van veertig beurten en 5.090 tokens; de template-overhead per message is de conventionele vier-plus-driebenadering en wordt vermeld overal waar hij is meegerekend. De cache-, tier- en truncation-cijfers zijn de gedocumenteerde pricing rules toegepast op die gemeten token counts, geen observaties van live API-responses — er is geen betaalde call gedaan om dit hoofdstuk te maken, wat ook de eerlijke reden is dat de latencyclaims kwalitatief zijn en de kostclaims niet.

  1. Dao, T., Fu, D. Y., Ermon, S., Rudra, A. en Ré, C. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135 (2022). Waarom het plafond verschoof zonder dat de asymptotische kosten veranderden.

  2. Chen, S., Wong, S., Chen, L. en Tian, Y. Extending Context Window of Large Language Models via Positional Interpolation. arXiv:2306.15595 (2023).

  3. Peng, B., Quesnelle, J., Fan, H. en Shippole, E. YaRN: Efficient Context Window Extension of Large Language Models. arXiv:2309.00071 (2023).

  4. Google, Thinking, ai.google.dev/gemini-api/docs/thinking, en Token counting, ai.google.dev/gemini-api/docs/tokens, beide geraadpleegd op 2026-09-06. "Pricing is based on the full thought tokens the model needs to generate, despite only the summary being output from the API." Het usage-object rapporteert total_input_tokens, total_output_tokens, total_thought_tokens, total_cached_tokens, total_tool_use_tokens en total_tokens — zes buckets, met thoughts en tool use buiten de output count. De eerdere veldnaam voor dezelfde hoeveelheid, nog steeds geretourneerd door de generateContent-surface, is thoughtsTokenCount, gedocumenteerd op een derde pagina, ai.google.dev/gemini-api/docs/generate-content/thinking.

  5. Anthropic, Prompt caching, docs.anthropic.com/en/docs/build-with-claude/prompt-caching, geraadpleegd op 2026-09-06. Bron van de toolssystemmessages invalidatiehiërarchie en de tabel ervan; de minimale cachebare lengtes per model; de identiteit total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens; en de standaardlevensduur van vijf minuten die bij elke hit gratis wordt ververst. 2

  6. OpenAI, Prompt caching, platform.openai.com/docs/guides/prompt-caching, geraadpleegd op 2026-09-06. Bron van: de regel voor de volledige gerenderde prefix; de minimale cachebare prefix (1.024 zichtbare input tokens op GPT-5.6 en later, 2.048 eerder); de 1,25× write- en 0,1× read-multipliers, en de afwezigheid van een write charge op GPT-5.5 en eerder; de levensduur van 30 minuten; de limieten van vier writes per request en vijftig breakpoints; de machine-affinity note en prompt_cache_key; de uitgewerkte break-evenvoorbeelden van 1,35×, 2,15× en 10×; en de uitspraak dat summarisation, compaction of truncation cache reuse reset. 2

  7. OpenAI, Pricing (platform.openai.com/docs/pricing) en de modelpagina voor gpt-5.6-terra, beide geraadpleegd op 2026-09-06. gpt-5.6-terra, standard service tier, per miljoen tokens: input $2.00, cached input $0.20, cache writes $2.50, output $12.00; long context input $4.00, cached $0.40, writes $5.00, output $18.00; "prompts with >272K input tokens are priced at 2x input and 1.5x output for the full request"; context window 1.050.000 tokens met een maximum van 922.000 input tokens. Dezelfde tabel vermeldt gpt-6-astra op $10.00/$1.00/$12.50/$50.00 en gpt-5.6-luna op $0.20/$0.02/$0.25/$1.20. Elke uitgewerkte kost in dit hoofdstuk gebruikt de gpt-5.6-terra standard short-context rates.

  8. Google, Gemini Developer API pricing, ai.google.dev/gemini-api/docs/pricing, geraadpleegd op 2026-09-06. Gemini 2.5 Pro, per miljoen tokens: input $1.25 voor prompts tot 200K en $2.50 daarboven; output $10.00 en $15.00, in beide gevallen gelabeld "including thinking tokens"; context caching $0.125 en $0.25, plus opslagkosten van $4.50 per miljoen tokens per uur. Gemini 3.1 Pro Preview gebruikt dezelfde 200K-threshold op $2.00/$4.00 input en $12.00/$18.00 output.

  9. Anthropic, Pricing, docs.anthropic.com/en/docs/about-claude/pricing, geraadpleegd op 2026-09-06. Per miljoen tokens, base input / 5-minute cache write / 1-hour cache write / cache read / output: Claude Sonnet 4.5 $3 / $3.75 / $6 / $0.30 / $15; Claude Haiku 4.5 $1 / $1.25 / $2 / $0.10 / $5; Claude Opus 5 $5 / $6.25 / $10 / $0.50 / $25. Multipliers: 1,25× voor de five-minute write, 2× voor de one-hour write, 0,1× voor een read. Ook de bron van de long-context-uitspraak ("Claude 4.6 and later models... include the full 1M token context window at standard pricing"), de tool-use system prompt token counts (496 tokens op Claude Sonnet 4.5 met tool_choice van auto of none, 588 met any of een named tool), en de noot dat Claude 4.7 en later een nieuwere tokenizer gebruiken die "approximately 30 % more tokens for the same text" produceert. 2 3

  10. Anthropic, Token counting, docs.anthropic.com/en/docs/build-with-claude/token-counting, geraadpleegd op 2026-09-06. Het /v1/messages/count_tokens-endpoint neemt dezelfde inputs als een message en retourneert een input token count; de documentatie stelt dat de count een estimate is, dat hij tokens kan bevatten die Anthropic toevoegt voor system optimisations, en dat die niet worden gefactureerd.

  11. Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F. en Liang, P. Lost in the Middle: How Language Models Use Long Contexts. arXiv:2307.03172 (2023). Hier geciteerd, gemeten in hoofdstuk 24.


Gemaakt door

David Vicente Campos

Oprichter van NeuraLIA Labs en medeoprichter van MyRealFood

Ik ben informatica-ingenieur, afgestudeerd aan de Universiteit van León. Ik was medeoprichter van MyRealFood, waar ik als CTO de app bouwde die miljoenen mensen hebben gebruikt om beter te eten, en ik heb NeuraLIA Labs opgericht, waar ik AI-producten bouw. Hier schrijf ik over wat ik onderweg heb moeten begrijpen, zoals ik wou dat iemand het mij had uitgelegd.

Meer over de auteur

Gepubliceerd door NeuraLIA Labs.

Ontvang nieuwe posts in je inbox

AI-nieuws, gidsen en productupdates — een korte mail wanneer we iets publiceren dat je tijd waard is.

Cursusindex

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev12 min leestijd

Jev AI-model is gebouwd voor beslissingen, niet voor proza

TypeSafe AI’s Jev trekt aandacht omdat het software-intelligentie benadert als een waarschijnlijkheidsprobleem: kies de juiste vertakking, voeg vertrouwen toe en betaal geen LLM om tekst te schrijven wanneer code een beslissing nodig heeft.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering11 min leestijd

Context-engineering voor langlopende AI-agenten

Langlopende agenten falen niet alleen omdat het venster klein is. Ze falen wanneer bestanden, tool-outputs en verouderde geschiedenis de taak verdringen die de agent moest afronden.

Klaar om LIA te laten kiezen?

Bouw met elk AI-model op één plek — begin vandaag nog gratis.