Alejandro Rioja.
AI Agents Operations

Prompt caching met de Claude API: verlaag je invoerkosten zonder van model te wisselen

Alejandro Rioja
Alejandro Rioja
8 min lezen
TL;DR

Prompt caching verlaagt de kosten van grote, stabiele invoer — je systeemprompt, tooldefinities, few-shot-voorbeelden — tot ongeveer 10% van het normale invoertarief bij herhaalde verzoeken. Het mechanisme is een prefix-match: plaats een cache_control-marker aan het eind van je stabiele content en houd alles wat verandert daarna. De fout die je cache-hitratio om zeep helpt, is een timestamp of UUID in de prefix laten sluipen.

Gratis nieuwsbrief

Elke woensdag. 28.400+ operators. Geen opvulling.

Table of contents

Open Table of contents

Wat prompt caching werkelijk doet

Elke aanroep naar de Claude API verstuurt tokens. Zonder caching wordt elk token in je verzoek — systeemprompt, tooldefinities, few-shot-voorbeelden en het gebruikersbericht — afgerekend tegen het normale invoertarief. Met caching wordt na het eerste verzoek een prefix van die tokens opgeslagen op de servers van Anthropic. Bij volgende verzoeken die diezelfde exacte prefix delen, betaal je een cache-read-prijs in plaats van ze opnieuw vanaf nul te verwerken.

Het kostenverschil is reëel:

Zodra je voorbij het break-evenpunt bent — wat snel gebeurt bij elke agent die meer dan een paar keer per dag draait — levert elke extra cache-hit een korting van ~90% op die tokens op.

Het prefix-match-principe

Dit is de ene regel waaruit al het andere volgt: de cachesleutel is een prefix-match van je gerenderde prompt.

De servers van Anthropic slaan de gerenderde content op vanaf het begin van je prompt tot aan de cache_control-marker. Voor een cache-hit bij het volgende verzoek moet elk token vanaf het begin van de prompt tot aan die marker identiek zijn — byte voor byte.

De rendervolgorde voor prefix-matching is: tools → system → messages. Je tools-array wordt dus eerst gehasht, daarna het system-blok, en vervolgens de messages op volgorde.

Wat dit in de praktijk betekent: stabiele content moet als eerste komen. Als je systeemprompt naar iets dynamisch verwijst — een huidige datum, een gebruikers-ID, een trace-ID van een verzoek — en dat vóór de cache_control-marker staat, mist de cache bij elk verzoek omdat de prefix steeds verandert.

Waar je een cache-marker op zet

De doelwitten met de meeste hefboom zijn:

1. Je systeemprompt

Systeemprompts zijn meestal het grootste stabiele blok. Een gedetailleerde agent-persona, een lijst met gedragsregels, een set instructies voor het uitvoerformaat — dit alles is identiek bij elke aanroep van dezelfde agent. Markeer het:

typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1024,
  system: [
    {
      type: "text",
      text: `You are a content operations agent for alejandrorioja.com.
Your job is to draft blog posts in Alejandro's voice: direct, practitioner, 
first-person, numbered lists, honest caveats. No hedging. No filler. 
Every section must earn its place.

[... 2000 more tokens of stable instructions ...]`,
      cache_control: { type: "ephemeral" },
    },
  ],
  messages: [
    {
      role: "user",
      content: "Draft a post about prompt caching.",
    },
  ],
});

De cache_control: { type: "ephemeral" } op het system-blok vertelt Claude om alles tot en met dat blok te cachen. De messages-array is veranderlijk — anders bij elk verzoek — en blijft buiten de cachegrens.

2. Tooldefinities

Als je agent tools gebruikt, kunnen die definities aanzienlijk zijn. Een goed gedocumenteerd toolschema met beschrijving, parameternamen en enum-waarden kan oplopen tot 500–1.000 tokens per tool. Met 5 tools is dat tot 5.000 tokens die je bij elke aanroep opnieuw moet betalen om te verwerken:

typescript
const response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1024,
  tools: [
    {
      name: "search_airtable",
      description: "Search the Airtable content queue...",
      input_schema: { type: "object", properties: { query: { type: "string" } } },
    },
    // ... more tools ...
    {
      name: "post_to_kit",
      description: "Schedule a broadcast via the Kit API...",
      input_schema: { /* ... */ },
      // Mark the last tool to cache the entire tools array
    } as Anthropic.Tool & { cache_control: { type: "ephemeral" } },
  ],
  system: "...",
  messages: [...],
});

Markeer de laatste tool in de array. De prefix-match dekt vanaf dat punt de volledige tools-array.

3. Few-shot-voorbeelden in messages

Als je statische few-shot-voorbeelden als vroege berichten in de messages-array meegeeft, kunnen die ook gecachet worden. Structureer ze als de eerste N berichten en markeer de laatste voorbeeldbeurt:

typescript
const messages: Anthropic.MessageParam[] = [
  {
    role: "user",
    content: [
      {
        type: "text",
        text: "Here are examples of posts in my voice:\n\n[Example 1...]\n\n[Example 2...]",
        cache_control: { type: "ephemeral" },
      } as Anthropic.TextBlockParam & { cache_control: { type: "ephemeral" } },
    ],
  },
  {
    role: "assistant",
    content: "Understood. I'll follow that voice.",
  },
  // The actual user turn follows — this is volatile, no cache marker
  {
    role: "user",
    content: actualUserRequest,
  },
];

Wat je NIET moet cachen (stille cache-brekers)

Dit zijn de dingen die er stabiel uitzien maar dat niet zijn — en ze helpen je hitratio stilletjes om zeep. De API waarschuwt je niet. Je ziet gewoon cache_creation_input_tokens bij elk verzoek en vraagt je af waarom.

Timestamps in de systeemprompt. De allergrootste klassieker:

typescript
// This invalidates the cache on every request
const system = `You are an agent. Current time: ${new Date().toISOString()}`;

Verplaats timestamps naar het gebruikersbericht, waar ze thuishoren:

typescript
// Stable system prompt — cacheable
const system = `You are an agent. Use the current time provided by the user.`;

// Volatile user message — not cached
const userMessage = `Current time: ${new Date().toISOString()}. Run the daily brief.`;

Willekeurige UUID’s en trace-ID’s. Hetzelfde probleem. Als je een trace-ID in het system-blok injecteert voor logging, krijgt elk verzoek een verse prefix.

Niet-deterministische JSON-serialisatie. Als je een object in de systeemprompt serialiseert en de volgorde van de sleutels niet gegarandeerd is, kan de gerenderde string verschillen, zelfs als de onderliggende data hetzelfde is. Serialiseer met een stabiele sleutelvolgorde of gebruik een template-string.

Dynamische few-shot-selectie. Als je few-shot-voorbeelden kiest op basis van de huidige query en ze in de gecachete prefix plaatst, heb je de “stabiele” prefix query-afhankelijk gemaakt. Kies óf voor vaste voorbeelden voor de cachelaag, óf verplaats dynamische voorbeelden naar de niet-gecachete berichtbeurt.

Je cache-hitratio verifiëren

Elke respons bevat usage-metadata. Controleer die:

typescript
const response = await client.messages.create({ /* ... */ });

console.log({
  inputTokens: response.usage.input_tokens,
  cacheRead: response.usage.cache_read_input_tokens,
  cacheWrite: response.usage.cache_creation_input_tokens,
  outputTokens: response.usage.output_tokens,
});

Bij het eerste verzoek: cache_creation_input_tokens is niet nul, cache_read_input_tokens is 0. Dat is de write.

Bij een cache-hit: cache_read_input_tokens is niet nul, cache_creation_input_tokens is 0. Dat is de read.

Als je bij elk verzoek cache_creation_input_tokens ziet, verandert je prefix. Voeg een logregel toe die de eerste 200 tekens van je gerenderde systeemprompt afdrukt vóór elke aanroep — een rondzwervende timestamp valt dan meteen op.

De TTL van 1 uur: wanneer de extra writekosten de moeite waard zijn

De standaard-TTL is 5 minuten. Als je agent met lage frequentie draait — minder dan eens per 5 minuten — betaal je bij de meeste verzoeken writekosten zonder reads te krijgen.

typescript
// Opt into a 1-hour TTL
cache_control: { type: "ephemeral", ttl: "1h" }

De write van 1 uur kost ~2× het basisinvoertarief in plaats van 1,25×. De rekensom: als je de cache 3 of meer keer per uur raakt, bespaart de TTL van 1 uur geld. Als je agent eens per dag draait (zoals mijn daily brief), helpt zelfs de TTL van 1 uur niet — je betaalt elke keer writekosten. In dat geval is het cachevoordeel bescheiden, tenzij de systeemprompt enorm is.

Mijn daily-brief-agent heeft een systeemprompt van 3.000 tokens maar draait eens per dag. Caching helpt niet. Mijn nieuwsbriefagent draait tientallen keren per sessie tijdens het schrijven — caching bespaart aanzienlijk.

Pre-warming: het eerste verzoek goedkoop maken

Als je een bekende verkeerspiek ziet aankomen — een batchtaak, een API-launch — kun je de cache pre-warmen met een goedkoop dummyverzoek:

typescript
// Pre-warm: write the cache at near-zero output cost
await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1, // minimal output
  system: [{ type: "text", text: stableSystemPrompt, cache_control: { type: "ephemeral" } }],
  messages: [{ role: "user", content: "ping" }],
});

// Now the real requests read from cache

Dit is vooral nuttig bij batchverwerking, waarbij je veel parallelle verzoeken opstart en wilt dat elk ervan een warme cache raakt in plaats van te racen om hem te schrijven.

Prompt caching in agent-loops

In een agent-loop met meerdere beurten groeit de gespreksgeschiedenis bij elke beurt. De cache is slim genoeg om hiermee om te gaan: hij gebruikt een lookback-venster van 20 blokken en vindt de langste passende prefix binnen de laatste 20 contentblokken.

De praktische implicatie: houd je stabiele content (systeemprompt, tooldefinities) verankerd aan de bovenkant. De groeiende gespreksgeschiedenis aan het eind van de messages-array breekt de prefix-match voor de stabiele blokken niet — die staan vóór de veranderlijke content, en de prefix-match begint bovenaan.

In de praktijk structureren mijn agents de beurten zo:

code
System (cached) → Tools (cached) → Few-shot (cached) → Turn 1 → Turn 2 → ... → Current turn

De cache dekt alles tot aan de few-shot-marker. De groeiende beurtgeschiedenis daarna wordt elke keer opnieuw verwerkt, maar dat is prima — die tokens zijn sessiespecifiek en klein ten opzichte van de stabiele prefix.

Hoe het op de rekening uitpakt

Neem een agent met hoge frequentie: 100 aanroepen per dag, systeemprompt van 4.000 tokens, Sonnet-tarief.

Zonder caching:

Met caching (TTL van 5 min, uitgaande van 50 aanroepen/uur tijdens de piek):

Dat is ongeveer een reductie van 90% op die invoertokens. Op schaal — 1.000 aanroepen per dag — loopt het verschil verder op. En dit komt bovenop eventuele besparingen door modelroutering uit de Haiku-vs-Sonnet-rekensom: caching werkt op elke klasse.

De bottom line voor de operator

Prompt caching is de makkelijkste kostenoptimalisatie in de Claude API: één extra veld op de contentblokken die je toch al schrijft. De beperking is discipline rond prefix-stabiliteit — niets dynamisch vóór de cache-marker. Als je je systeemprompt, tools en eventuele statische voorbeelden vrij kunt houden van veranderlijke content, betaal je ~10% van de normale invoerkosten bij elke cache-hit. Voor agents met hoge frequentie en grote, stabiele prompts is dit een grotere hefboom dan wisselen van modelklasse.


Gerelateerd: AI-agentkostenberekening: wanneer Haiku Sonnet verslaat · Event-getriggerde versus geplande agents · De 5 AI-tools die ik echt gebruik om mijn bedrijf te runnen

Lees verder

Ontvang het AI-playbook in je inbox

Elke woensdag. 28.400+ operators. Geen opvulling.

↵ alle resultaten bekijken esc esc om te sluiten