Anthropic Prompt Caching in Rails: Claude API-kosten Verlagen met de Anthropic Ruby SDK
Een SaaS-klant belde me op een vrijdagmiddag omdat hun Anthropic-rekening die maand al op tweeënveertigduizend dollar stond — en het was pas de achttiende. Ze hadden zes weken eerder een Claude-aangedreven supportassistent uitgerold, het verkeer was verviervoudigd, en elk verzoek stuurde dezelfde systeemprompt en kennisbasis-aanloop van achttienduizend tokens mee. Die middag zetten we Anthropic prompt caching aan. De volgende rekening kwam onder de vijfduizend uit.
Na negentien jaar Rails heb ik veel “dit moet goedkoper kunnen”-engineering gedaan, en Anthropic prompt caching is veruit de optimalisatie met de hoogste hefboomwerking die ik ken voor productie-LLM-apps. Als je de Anthropic Ruby SDK gebruikt en niet caching aanzet, sta je geld te verbranden. Dit is het productiehandboek.
Wat Anthropic Prompt Caching Eigenlijk Is
Elke Claude API-call heeft een input-prompt — systeemboodschap, tooldefinities, gespreksgeschiedenis, opgehaalde documenten — en een output-completion. Het overgrote deel van die input is identiek tussen verzoeken. De systeemprompt verandert niet tussen gebruikers. De toolschema’s veranderen niet tussen calls. Het beleidsdocument van dertig pagina’s dat je in context propt, verandert een week lang niet.
Anthropic prompt caching laat je delen van je input markeren als cachebaar. Het eerste verzoek dat die delen bevat, betaalt een write-premie van 1.25x de basisinput-prijs. Elk volgend verzoek dat de cache raakt, betaalt nog maar 0.1x de basisinput-prijs voor die tokens. Dat is 90% korting op gecachete input.
De standaard cachelevensduur is vijf minuten vanaf de laatste hit, ververst bij elke read. Er is ook een caching-optie van één uur voor prompts waarvan je weet dat ze warm blijven. Voor een chatbot met stabiel verkeer dekt een TTL van vijf minuten in feite elk verzoek na het eerste.
Het mechanisme is één veld op een content block:
{
type: "text",
text: long_system_prompt,
cache_control: { type: "ephemeral" }
}
Anthropic hasht het cumulatieve prefix tot en met elke cache_control-marker. Komt de hash overeen met een levend cache-record, dan krijg je een cache hit. Zo niet, dan schrijf je een nieuw record. De cache-key bevat het model, dus van model wisselen invalideert alles.
Wanneer Prompt Caching Loont (en Wanneer Niet)
De rekensom is simpel. Cache writes kosten 1.25x base, cache reads 0.1x base. Een gecachet prefix is dus rond twee hergebruiken break-even en levert echt geld op vanaf drie. Alles wat je binnen vijf minuten meer dan drie keer hergebruikt, is een kandidaat.
In een Rails-app zijn dit de werklasten die ervan profiteren:
- Klantgerichte chatbots met een vaste systeemprompt en tooldefinities
- RAG-endpoints die per sessie een groot referentiedocument of opgehaalde context vastpinnen
- Code review- of analysebots die een repository-snapshot meesturen
- Multi-turn assistenten waarbij de gespreksgeschiedenis met elke beurt groeit
- Batch classificatie- of extractiejobs die dezelfde instructies delen over duizenden items
Waar het niet loont: one-shot calls met elke keer een unieke prompt, heel korte prompts (de minimale cachebare grootte is 1024 tokens voor Sonnet/Opus en 2048 voor Haiku), en elke workflow waarin het prefix per call wezenlijk verandert. Zit je onder het minimum, dan doet caching niets — je cache_control-markers worden in stilte genegeerd.
Anthropic Prompt Caching Opzetten in Rails met de Anthropic Ruby SDK
De officiële Anthropic Ruby SDK ondersteunt prompt caching native. Voeg toe aan je Gemfile:
# Gemfile
gem "anthropic", "~> 1.0"
Configureer de client met credentials uit Rails credentials, niet met environment variables in productiecode:
# config/initializers/anthropic.rb
ANTHROPIC = Anthropic::Client.new(
api_key: Rails.application.credentials.dig(:anthropic, :api_key)
)
Ik heb de bredere patronen voor Claude-integratie in Rails — streaming, tools, RAG — eerder behandeld. Voor context: de Rails RAG met Claude en pgvector-gids en de LLM function calling in Rails-post sluiten goed aan op dit stuk.
Productiepatroon 1: De Systeemprompt Cachen
De zet met de hoogste hefboom. Wikkel je systeemprompt in een gecachet block:
class Support::AssistantService
SYSTEM_PROMPT = File.read(Rails.root.join("config/prompts/support_v3.md")).freeze
def reply(conversation:, user_message:)
ANTHROPIC.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: [
{
type: "text",
text: SYSTEM_PROMPT,
cache_control: { type: "ephemeral" }
}
],
messages: conversation_messages(conversation, user_message)
)
end
private
def conversation_messages(conversation, user_message)
conversation.turns.map { |t| { role: t.role, content: t.content } } +
[{ role: "user", content: user_message }]
end
end
Twee niet-vanzelfsprekende punten. Eén: het system-veld accepteert een array van content blocks, niet zomaar een string — je moet de array-vorm gebruiken om cache_control mee te sturen. Twee: bevries de prompt bij het laden. Lees je het bestand binnen reply, dan wordt elke Capistrano-achtige file-mtime-eigenaardigheid of whitespace-verschil een cache miss.
Voor een bot die duizend verzoeken per uur bedient met een systeemprompt van achttienduizend tokens, brengt deze ene wijziging de inputkosten op die prompt terug van 18.000 tokens × $0,003 = $0,054 per verzoek naar $0,0054 per verzoek. Over een miljoen verzoeken is dat $48.000 verschil.
Productiepatroon 2: Lange Documenten Cachen in RAG
Retrieval-augmented generation-pipelines halen vaak dezelfde chunks herhaaldelijk op binnen een sessie — een gebruiker die drie vragen stelt over hetzelfde contract, krijgt drie identieke retrievals. Cache de opgehaalde bundel:
class Knowledge::AssistantService
def answer(query:, session_id:)
chunks = Embeddings::Retriever.new(query).top(8)
context = chunks.map { |c| "[#{c.id}] #{c.content}" }.join("\n\n")
ANTHROPIC.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 800,
system: [
{ type: "text", text: BASE_SYSTEM_PROMPT, cache_control: { type: "ephemeral" } },
{ type: "text", text: context, cache_control: { type: "ephemeral" } }
],
messages: [{ role: "user", content: query }]
)
end
end
Twee cache_control-markers betekent twee cache breakpoints. Anthropic ondersteunt er tot vier. De basisprompt blijft gecachet over de hele app. De opgehaalde context blijft gecachet over een sessie. De gebruikersvraag wordt niet gecachet — die verandert elke call.
Kanttekening: als je retrieval te goed is en bij elke vraag andere chunks teruggeeft, krijg je geen cache hit op het contextblok. In de praktijk zie ik dat sessies rond onderwerpen clusteren, dus retrievals overlappen sterk. Meet, niet aannemen.
Productiepatroon 3: Multi-Turn Gesprekken
Voor een chatbot die per beurt context laat groeien, is de truc om de laatste turn als cache breakpoint te markeren. Elke vorige turn wordt zo onderdeel van het gecachete prefix bij de volgende call:
def reply(conversation:, user_message:)
history = conversation.turns.map { |t| { role: t.role, content: t.content } }
history.last[:cache_control] = { type: "ephemeral" } if history.any?
ANTHROPIC.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: [{ type: "text", text: SYSTEM_PROMPT, cache_control: { type: "ephemeral" } }],
messages: history + [{ role: "user", content: user_message }]
)
end
Na turn één is de systeemprompt gecachet. Na turn twee de systeemprompt plus turn één. Na turn drie dat alles plus turn twee. Vanaf turn tien doet de cache echt werk — je betaalt vol tarief alleen voor het laatste gebruikersbericht en het modelantwoord. Dat is waarom lange supportgesprekken progressief goedkoper worden in plaats van progressief duurder.
Productiepatroon 4: Tooldefinities
Gebruik je tool use, dan kunnen je toolschema’s honderden tot duizenden tokens beslaan. Ze veranderen vrijwel nooit tussen verzoeken. Cache ze:
TOOLS = JSON.parse(File.read(Rails.root.join("config/prompts/tools.json"))).freeze
ANTHROPIC.messages.create(
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools: TOOLS,
system: [{ type: "text", text: SYSTEM_PROMPT, cache_control: { type: "ephemeral" } }],
messages: messages
)
De cache_control-marker op de systeemprompt dekt alles wat in de canonieke verzoekvolgorde daarvoor komt, dus inclusief de tools-array. Eén marker op de systeemprompt cachet beide. Heb je veel tools of genereer je tooldefinities dynamisch, normaliseer ze dan — zelfde JSON-output, zelfde key-volgorde — anders verschilt je hash per call en raak je de cache nooit.
Ik ging dieper in op het inbouwen van Claude-tools in Rails in de LLM function calling-gids; die post is een goede metgezel als je caching op een bestaande tool-gebruikende assistent bouwt.
Cache Hit Rate Meten in Productie
Wat je niet meet, optimaliseer je niet. De Anthropic API geeft cache-statistieken terug bij elk antwoord:
response = ANTHROPIC.messages.create(...)
response.usage.input_tokens # ongecachete input deze call
response.usage.cache_creation_input_tokens # tokens naar cache geschreven
response.usage.cache_read_input_tokens # tokens vanuit cache geserveerd
response.usage.output_tokens
Push deze bij elke call naar je metrics-pipeline:
class Llm::Metered
def self.call(label:, **kwargs)
response = ANTHROPIC.messages.create(**kwargs)
u = response.usage
StatsD.increment("llm.calls", tags: ["label:#{label}"])
StatsD.histogram("llm.cache_read_tokens", u.cache_read_input_tokens, tags: ["label:#{label}"])
StatsD.histogram("llm.cache_creation_tokens", u.cache_creation_input_tokens, tags: ["label:#{label}"])
StatsD.histogram("llm.input_tokens", u.input_tokens, tags: ["label:#{label}"])
StatsD.histogram("llm.output_tokens", u.output_tokens, tags: ["label:#{label}"])
response
end
end
De metric die je op een dashboard wilt is cache_read / (cache_read + cache_creation + input). Een gezonde productiechatbot zit binnen een paar minuten warm verkeer op 80–95%. Zit je structureel onder de 50%, dan verandert je cache-key te vaak — meestal omdat er iets dynamisch in het gecachete prefix is geslopen.
Valkuilen Waar Niemand Je Voor Waarschuwt
Vijf valkuilen die ik of mijn klanten zijn tegengekomen sinds prompt caching GA werd.
Whitespace- en volgordewijzigingen invalideren de cache. Een Markdown-bestand lezen met File.read geeft je een verse string met de regeleindes die het bestand nu eenmaal heeft. Trim trailing whitespace bij het laden. Bevries. Bouw je de systeemprompt door interpolatie, zorg dan dat de geïnterpoleerde waarden ook gecachet zijn of weggestript — "You are #{Time.current}" is een 100% miss rate.
Cache-markers worden in stilte genegeerd onder het minimum. Is je systeemprompt 800 tokens, dan gebeurt er geen caching, en dat valt mogelijk weken niet op omdat de API geen fout teruggeeft. Controleer altijd cache_creation_input_tokens op de eerste call na een deploy. Is die nul, dan zit je onder de drempel.
Van model wisselen invalideert alles. Een cache-record is gekeyd op model. Een rollout van Sonnet 4.6 ter vervanging van Sonnet 4 betekent een stormloop van cache writes voor de eerste vijf minuten. Doe model-rollouts bij voorkeur tijdens daluren als je hier scherp op wilt zijn.
Beta headers kunnen veranderen. Sommige geavanceerde caching-modi, zoals de TTL van één uur, hadden tijdelijk een beta header nodig (anthropic-beta: extended-cache-ttl-2025-04-11 of vergelijkbaar — check de actuele docs). De Ruby SDK laat je extra headers meegeven; pin de versie en lees de changelog bij elke upgrade.
Cache hits tellen niet hetzelfde mee voor rate limits als verse tokens. Dat is een feature, geen bug — je effectieve doorvoer gaat omhoog. Maar het betekent ook dat autoscaling-logica op basis van input-tokens je belasting onderschat. Track cache reads apart als je workers schaalt op tokenvolume. Net zoals ik in de Puma-tuning-gids pleitte voor het apart meten van concurrency, wil je hier een aparte metric.
Wanneer Anthropic Prompt Caching Niet de Juiste Keuze Is
Drie gevallen waarin het de verkeerde tool is.
Echt unieke prompts. Een code-search-tool die per vraag een andere systeemprompt opbouwt, heeft geen gedeeld prefix. Sla caching over en focus op outputtoken-reductie.
Heel weinig verkeer. Maak je twaalf calls per dag, verspreid over kantooruren, dan is je cache elke keer koud. De 1.25x write-premie wordt een permanente belasting.
Latency-gevoelige losse calls. Cache writes zijn iets langzamer dan ongecachete calls. Voor een one-shot-extractie in een nachtelijke job heb je er geen baat bij. Voor een synchrone, gebruikergerichte chatbot wordt de latency-tik op de eerste call uitgesmeerd over duizenden snelle hits — dat is het waard.
Veelgestelde Vragen
Hoeveel kan Anthropic prompt caching de Claude API-kosten verlagen?
In productie-Rails-apps met een stabiele systeemprompt en stabiel verkeer zie ik 70–90% reductie in inputtoken-kosten. De variatie hangt af van prompt-lengte en verkeerspatroon: langere gecachete prefixes en stabieler verkeer geven grotere besparing. Outputtokens worden niet gecachet en veranderen niet.
Wat is de minimale grootte voor Anthropic prompt caching?
Het minimum is 1024 tokens voor Claude Sonnet- en Opus-modellen, en 2048 tokens voor Haiku. Daaronder wordt er stil niet gecachet, ook al stuur je cache_control mee. Controleer cache_creation_input_tokens op de eerste call om te bevestigen dat caching actief is.
Werkt prompt caching met streaming responses in Rails?
Ja. Caching geldt voor het input-prefix, dat is hetzelfde of je nu streamt of niet. De cache-statistieken komen mee in het laatste usage-event van de stream. Gebruik je ActionController::Live om Claude-antwoorden te streamen, zie dan de streaming LLM responses-gids — caching laagt er schoon overheen.
Hoe lang blijft de Anthropic prompt cache geldig?
De standaard ephemeral cache houdt vijf minuten stand vanaf de laatste read en wordt bij elke hit ververst. Er is ook een TTL-optie van één uur voor prompts waarvan je weet dat ze langer warm blijven; die kost iets meer om te schrijven, maar verlengt het hergebruikvenster. Voor de meeste chatbots is de standaard van vijf minuten ruim voldoende, omdat stabiel verkeer hem continu warm houdt.
Na negentien jaar Rails heb ik geleerd dat de beste optimalisaties die zijn die één configuratielijn veranderen en een grafiek opleveren die met een orde van grootte daalt. Anthropic prompt caching is er zo eentje. Draai je Claude in productie via de Anthropic Ruby SDK, markeer dan vandaag je prefixes en kijk morgen hoe de rekening daalt.
Hulp nodig met het integreren van Claude in een Rails-systeem of het auditen van je LLM-kosten? TTB Software levert productiewaardige AI-infrastructuur op Rails. We doen dit al negentien jaar.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Rails Postgres Table Partitioning: Tijd-Gebaseerde Partities voor Grote Tabellen in Productie
April 28, 2026
Rails Postgres Advisory Locks: Stop Cron-Overlap en Race Conditions in Productie
April 26, 2026
Rails Pundit Authorization: Productiepatronen voor Multi-Tenant SaaS Applicaties
April 25, 2026
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation