Prompt Caching Basics
8 examples to get you started with prompt caching - 5 basic and 3 intermediate.
Prerequisites
- Install the SDK:
pip install anthropic - Set your API key in the environment:
export ANTHROPIC_API_KEY=sk-ant-... - Every example below uses
anthropic.Anthropic(), which reads that environment variable automatically.
Basic Examples
1. Add a Single cache_control Breakpoint
Mark the end of a long system prompt as cacheable.
import anthropic
client = anthropic.Anthropic()
long_policy_text = "You are Acme Corp's support assistant. " * 300
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=200,
system=[
{
"type": "text",
"text": long_policy_text,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "What's your refund window?"}],
)
print(response.content[0].text)cache_controlis set on the system content block, not on the request as a whole.- Everything up to and including this block becomes eligible for caching.
- The first call always writes the cache; it cannot be a hit yet.
{"type": "ephemeral"}uses the default 5-minute TTL.
Related: How Prompt Caching's Prefix Match Actually Works - why this has to be byte-identical to hit.
2. Check usage.cache_creation_input_tokens on the First Call
Confirm the first request wrote a cache entry.
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=200,
system=[
{
"type": "text",
"text": long_policy_text,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "What's your refund window?"}],
)
print("cache write tokens:", response.usage.cache_creation_input_tokens)
print("cache read tokens:", response.usage.cache_read_input_tokens)cache_creation_input_tokensshould be nonzero here - that's the cache write.cache_read_input_tokensshould be0on this first call, since nothing existed to read yet.- These fields live on
response.usage, alongside the normalinput_tokensandoutput_tokens.
3. Repeat the Same Request and Confirm a Cache Hit
Send an identical prefix again within the TTL window.
response_2 = client.messages.create(
model="claude-sonnet-5",
max_tokens=200,
system=[
{
"type": "text",
"text": long_policy_text,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "Do you ship internationally?"}],
)
print("cache read tokens:", response_2.usage.cache_read_input_tokens)- The
systemblock is byte-identical to example 1, so the prefix matches. - Only the final user message changed, and it sits after the breakpoint.
cache_read_input_tokensshould now be nonzero - this is the cache hit.- The response still answers the new question correctly; caching only skips reprocessing the shared prefix.
4. Set an Explicit TTL
Choose the 1-hour cache window instead of the 5-minute default.
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=200,
system=[
{
"type": "text",
"text": long_policy_text,
"cache_control": {"type": "ephemeral", "ttl": "1h"},
}
],
messages=[{"role": "user", "content": "What's your refund window?"}],
)"ttl": "1h"extends how long the entry survives before it must be rewritten.- The 1-hour TTL has a higher cache-write cost multiplier than the 5-minute default.
- Omitting
ttlis the same as"5m". - Pick 1-hour for sessions likely to span more than a few minutes between calls.
Related: Cache TTL Options and Pricing Reference - full comparison of the two windows.
5. Cache Tool Definitions Instead of the System Prompt
Put the breakpoint on a large tool schema.
tools = [
{
"name": "search_docs",
"description": "Search the internal knowledge base." * 20,
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
"cache_control": {"type": "ephemeral"},
}
]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=200,
tools=tools,
messages=[{"role": "user", "content": "Find our vacation policy."}],
)cache_controlcan go on the last tool in atoolslist, not only onsystem.- Tools are evaluated before the system prompt in the effective prefix.
- This is worth doing when you have many or large tool schemas that rarely change.
- A stable tool catalog paired with a changing system prompt can still cache the tools separately.
Intermediate Examples
6. Layer Two Breakpoints: Tools and System
Cache tools and the system prompt as two separate, independently-refreshable segments.
tools = [
{
"name": "search_docs",
"description": "Search the internal knowledge base." * 20,
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
"cache_control": {"type": "ephemeral"},
}
]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=200,
tools=tools,
system=[
{
"type": "text",
"text": long_policy_text,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "What's your refund window?"}],
)
print(response.usage.cache_read_input_tokens, response.usage.cache_creation_input_tokens)- The tools breakpoint caches the tool schemas as their own prefix segment.
- The system breakpoint then caches tools + system together as a second, longer segment.
- If only the system prompt changes, the tools segment can still hit while the system segment is rewritten.
- Two breakpoints is the practical ceiling for most everyday prompts - more than that adds bookkeeping without much extra benefit.
Related: Placing cache_control Breakpoints on System Prompts and Tools - the full ordering rules.
7. Build a Small Helper That Logs Cache Effectiveness
Wrap a call so every request reports its cache hit rate.
def call_with_cache_report(client, **kwargs):
response = client.messages.create(**kwargs)
usage = response.usage
total_prefix = usage.cache_read_input_tokens + usage.cache_creation_input_tokens
hit_rate = (
usage.cache_read_input_tokens / total_prefix if total_prefix else 0.0
)
print(
f"read={usage.cache_read_input_tokens} "
f"write={usage.cache_creation_input_tokens} "
f"hit_rate={hit_rate:.0%}"
)
return response
call_with_cache_report(
client,
model="claude-sonnet-5",
max_tokens=200,
system=[
{
"type": "text",
"text": long_policy_text,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "What's your refund window?"}],
)- Centralizing this check in one helper makes cache regressions visible in logs instead of buried in individual response objects.
- A
hit_ratethat drops to0%unexpectedly is the first signal something upstream changed the prefix. - This pattern scales cleanly to wrapping every
messages.createcall in a larger application. - Consider emitting these numbers to your metrics system rather than just printing them.
Related: Verifying Cache Hits with cache_read_input_tokens - a deeper look at building this kind of check.
8. Combine a Cached System Prompt with a Growing Conversation
Keep the system prompt cached while the messages array grows turn by turn.
conversation = [{"role": "user", "content": "What's your refund window?"}]
def cached_turn(client, conversation, user_text):
if user_text:
conversation.append({"role": "user", "content": user_text})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=300,
system=[
{
"type": "text",
"text": long_policy_text,
"cache_control": {"type": "ephemeral"},
}
],
messages=conversation,
)
conversation.append({"role": "assistant", "content": response.content[0].text})
return response
r1 = cached_turn(client, conversation, None)
r2 = cached_turn(client, conversation, "And for opened items?")
print(r2.usage.cache_read_input_tokens)- The system prompt's breakpoint stays identical across turns, so it keeps hitting the cache even as
conversationgrows. - Only the messages array - which sits after the breakpoint - changes between turns.
- This is the seed pattern for multi-turn agent caching, covered in more depth elsewhere in this section.
- Watch for context growth: a long-running conversation eventually needs trimming regardless of caching.
Related: Caching Strategy for Multi-Turn Agent Conversations - per-turn breakpoint placement for growing conversations.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.