Prompt Caching: The Trick That Cuts Your LLM Bill by 10x (Anthropic, OpenAI, Google)
Complete guide to prompt caching for reducing LLM costs: how prefix matching works, per-provider strategies (Anthropic, OpenAI, Google), savings calculations, and RAG-specific optimization.
Prompt Caching: The Trick That Cuts Your LLM Bill by 10x
Is your LLM bill spiraling out of control? Are you sending the same system instructions, the same few-shot examples, the same RAG context with every request? There's a solution that can cut your costs by 90% with Anthropic and 50% with OpenAI -- and most developers aren't using it yet.
Welcome to the world of prompt caching.
TL;DR
- Prompt caching = the LLM provider caches the prefix of your prompt so it doesn't reprocess it every request
- Anthropic: -90% on cached tokens (most aggressive), minimum 1,024 tokens, 5-minute TTL
- OpenAI: -50% on cached tokens, fully automatic, minimum 1,024 tokens
- Google: -75% on cached tokens, explicit context caching, minimum 32,768 tokens
- For RAG: cache the system prompt + few-shot examples + static context, only vary the query
- Typical savings: 40-80% reduction on monthly LLM bill
How Prompt Caching Works
The principle: prefix matching
On each LLM call, the provider compares the beginning of your prompt with recent prompts. If an identical prefix is found in cache, only the new portion is processed at full price.
Request 1:
┌──────────────────────────────────────────────────────────┐
│ System prompt (2000 tokens) │ Few-shots (1000) │ Query 1 │
│ ████████████████████████████ │ ████████████████ │ ████ │
│ Processed normally │ │ │
└──────────────────────────────────────────────────────────┘
Cost: 3000 tokens × normal price = $0.045
Request 2 (same prefix):
┌──────────────────────────────────────────────────────────┐
│ System prompt (2000 tokens) │ Few-shots (1000) │ Query 2 │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░ │ ░░░░░░░░░░░░░░░ │ ████ │
│ CACHED (reduced rate) │ │ Normal │
└──────────────────────────────────────────────────────────┘
Cost: 3000 tokens × cached price + 50 tokens × normal price = much less
Conditions for cache hits
| Condition | Description |
|---|---|
| Identical prefix | Tokens must be exactly identical at the start |
| Same model | Cache is per-model (GPT-4o ≠ GPT-4o mini) |
| Within TTL | Cache expires after a certain time (varies by provider) |
| Minimum size | A minimum number of tokens required to trigger caching |
| Order matters | System → User → Assistant must be in the same order |
Anthropic: The Caching Champion (-90%)
How it works
Anthropic offers the most aggressive reduction: 90% off cached tokens, with a slight 25% surcharge on the initial cache write.
| Parameter | Value |
|---|---|
| Cache read discount | -90% |
| Cache write surcharge | +25% |
| TTL | 5 minutes (renewed on each hit) |
| Minimum size | 1,024 tokens (Claude 3.5) / 2,048 tokens (Claude 3) |
| Cache blocks | Multiple breakpoints supported |
Detailed pricing: Claude 3.5 Sonnet
| Token type | Price / 1M tokens | vs Base |
|---|---|---|
| Input (base) | $3.00 | - |
| Input (cache write) | $3.75 | +25% |
| Input (cache read) | $0.30 | -90% |
| Output | $15.00 | - |
Anthropic implementation
DEVELOPERpythonimport anthropic client = anthropic.Anthropic() # The system prompt will be cached after the first call SYSTEM_PROMPT = """You are an expert assistant for {company_name}. You answer only from the provided documents. You always cite your sources with [Source: document_name]. You never hallucinate. If you don't know, say so. ## Formatting rules - Concise responses (3-5 sentences max) - Use bullet points for lists - Cite the source in brackets ## Expected response examples Q: What is the return policy? A: Our return policy allows returns within 30 days for any unused item [Source: return-policy.pdf]. Return shipping costs are the customer's responsibility except for defective products [Source: terms-2026.pdf]. Q: How do I configure SSO? A: To configure SSO, go to Settings > Security > SSO. Select your provider (Okta, Azure AD, Google) and paste the metadata URL [Source: admin-guide.pdf]. """ + context_documents # Add your RAG context here def query_with_caching(user_query: str, rag_context: str): """Query with Anthropic prompt caching.""" response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=[ { "type": "text", "text": SYSTEM_PROMPT, "cache_control": {"type": "ephemeral"} # Enable caching } ], messages=[ { "role": "user", "content": f"RAG Context:\n{rag_context}\n\nQuestion: {user_query}" } ], ) # Check cache hit usage = response.usage print(f"Cache read: {usage.cache_read_input_tokens} tokens") print(f"Cache write: {usage.cache_creation_input_tokens} tokens") print(f"Uncached input: {usage.input_tokens} tokens") return response.content[0].text
Multi-breakpoint strategy with Anthropic
DEVELOPERpythondef query_with_multi_cache(user_query: str, rag_context: str): """Multiple cache levels to maximize savings.""" response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=[ { "type": "text", "text": STATIC_SYSTEM_PROMPT, # Rarely changes "cache_control": {"type": "ephemeral"} }, { "type": "text", "text": FEW_SHOT_EXAMPLES, # Changes sometimes "cache_control": {"type": "ephemeral"} } ], messages=[ { "role": "user", "content": [ { "type": "text", "text": rag_context, # Changes often but shared "cache_control": {"type": "ephemeral"} }, { "type": "text", "text": user_query, # Always unique } ] } ], ) return response
OpenAI: Automatic Caching (-50%)
How it works
OpenAI launched automatic caching: no configuration needed, the system automatically detects and caches identical prefixes.
| Parameter | Value |
|---|---|
| Cache read discount | -50% |
| Cache write surcharge | None (free) |
| TTL | 5-10 minutes (variable) |
| Minimum size | 1,024 tokens |
| Activation | Automatic (no configuration) |
Detailed pricing: GPT-4o
| Token type | Price / 1M tokens | vs Base |
|---|---|---|
| Input (base) | $2.50 | - |
| Input (cached) | $1.25 | -50% |
| Output | $10.00 | - |
OpenAI implementation
DEVELOPERpythonfrom openai import OpenAI client = OpenAI() # Caching is AUTOMATIC with OpenAI # Just structure the prompt with a stable prefix SYSTEM_PROMPT = """You are an expert assistant for {company_name}. ... (same long system prompt) ... """ def query_with_openai_caching(user_query: str, rag_context: str): """Caching is automatic - just keep the prefix identical.""" response = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, # Tip: put stable RAG context as first user message {"role": "user", "content": f"Context:\n{rag_context}"}, {"role": "assistant", "content": "Context noted."}, {"role": "user", "content": user_query}, ], max_tokens=1024, ) # Check cached tokens in usage usage = response.usage cached = getattr(usage, 'prompt_tokens_details', {}) if cached: print(f"Cached tokens: {cached.get('cached_tokens', 0)}") return response.choices[0].message.content
Maximizing cache hit rate with OpenAI
DEVELOPERpython# BAD: message order changes → no cache hit messages_v1 = [ {"role": "system", "content": SYSTEM}, {"role": "user", "content": f"Context: {context_A}\nQuery: {query_1}"}, ] messages_v2 = [ {"role": "system", "content": SYSTEM}, {"role": "user", "content": f"Context: {context_B}\nQuery: {query_2}"}, ] # context_A ≠ context_B → cache miss from 2nd message # GOOD: separate stable and variable context messages_v1 = [ {"role": "system", "content": SYSTEM}, # ← Always identical {"role": "user", "content": "Context: " + STATIC_CONTEXT}, # ← Identical {"role": "assistant", "content": "OK."}, {"role": "user", "content": query_1}, # ← Only variable element ] messages_v2 = [ {"role": "system", "content": SYSTEM}, # ← Cache hit! {"role": "user", "content": "Context: " + STATIC_CONTEXT}, # ← Cache hit! {"role": "assistant", "content": "OK."}, {"role": "user", "content": query_2}, # ← Only variable element ]
Google: Explicit Context Caching (-75%)
How it works
Google offers explicit context caching with configurable TTLs and a 75% discount.
| Parameter | Value |
|---|---|
| Cache read discount | -75% |
| Cache storage cost | $1.00 / 1M tokens / hour |
| TTL | Configurable (min 1 min, max 24h) |
| Minimum size | 32,768 tokens (highest threshold) |
| Activation | Explicit via API |
Detailed pricing: Gemini 1.5 Pro
| Token type | Price / 1M tokens | vs Base |
|---|---|---|
| Input (base) | $1.25 | - |
| Input (cached) | $0.3125 | -75% |
| Output | $5.00 | - |
| Cache storage | $1.00/h/M tokens | - |
Google implementation
DEVELOPERpythonimport google.generativeai as genai from google.generativeai import caching import datetime genai.configure(api_key="GOOGLE_API_KEY") # Create an explicit cache cache = caching.CachedContent.create( model="models/gemini-1.5-pro-002", display_name="rag-system-prompt", system_instruction=SYSTEM_PROMPT, contents=[ # Pre-load static RAG context (must be > 32K tokens) {"role": "user", "parts": [{"text": LARGE_RAG_CONTEXT}]}, {"role": "model", "parts": [{"text": "Context loaded."}]}, ], ttl=datetime.timedelta(hours=1), # Cache for 1 hour ) # Use the cache for queries model = genai.GenerativeModel.from_cached_content(cached_content=cache) def query_with_google_caching(user_query: str): """Query using Google context cache.""" response = model.generate_content(user_query) # Check cache usage print(f"Cached tokens: {response.usage_metadata.cached_content_token_count}") print(f"Total tokens: {response.usage_metadata.total_token_count}") return response.text # Delete the cache when no longer needed cache.delete()
Provider Comparison
Summary table
| Criteria | Anthropic | OpenAI | |
|---|---|---|---|
| Cache discount | -90% | -50% | -75% |
| Write surcharge | +25% | None | Storage/h |
| Minimum size | 1,024 tokens | 1,024 tokens | 32,768 tokens |
| TTL | 5 min (renewable) | 5-10 min | Configurable (24h max) |
| Activation | Manual (cache_control) | Automatic | Manual (API) |
| Multi-breakpoints | Yes | No | No |
| Best for | Long system prompts | All usage | Very large contexts |
Cost simulator: 10,000 requests / day
Assumption: 3,000-token system prompt + 200-token query, GPT-4o / Claude 3.5 Sonnet / Gemini 1.5 Pro.
| Scenario | Without cache | With cache | Savings |
|---|---|---|---|
| Anthropic | $960/month | $144/month | -85% |
| OpenAI | $750/month | $412/month | -45% |
| $480/month | $168/month | -65% |
Note: Actual savings depend on cache hit rate. With a stable system prompt and steady traffic, hit rate exceeds 90%.
Which provider for caching?
| Your situation | Recommendation |
|---|---|
| System prompt > 2K tokens, continuous traffic | Anthropic (max savings) |
| Don't want to change code | OpenAI (automatic) |
| RAG context > 32K tokens, long TTL | Google (explicit cache) |
| Tight budget, small requests | OpenAI (no write surcharge) |
| Multi-tenant with different prompts | Anthropic (multi-breakpoints) |
RAG-Specific Caching Strategies
Optimal architecture for RAG
┌─────────────────────────────────────────────────────┐
│ RAG PROMPT STRUCTURE │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ ← CACHED │
│ │ System prompt (instructions) │ (identical │
│ │ + formatting rules │ for all │
│ │ + tone of voice │ requests) │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ ← CACHED │
│ │ Few-shot examples (3-5) │ (rarely │
│ │ + expected format │ changes) │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ ← PARTIALLY │
│ │ Dynamic RAG context │ CACHED │
│ │ (retrieved documents) │ (if same docs) │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ ← NOT CACHED │
│ │ User query │ (always │
│ │ │ unique) │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Strategy 1: Cache system prompt + few-shots
The simplest and most effective strategy:
DEVELOPERpython# System prompt and few-shots never change # → Cache hit rate close to 100% CACHED_PREFIX = f"""{SYSTEM_PROMPT} ## Examples {FEW_SHOT_EXAMPLES} """ # Only the RAG context and query change def build_prompt(rag_docs: list, query: str) -> str: return f"""{CACHED_PREFIX} ## Reference documents {format_documents(rag_docs)} ## Question {query} """
Strategy 2: Cache frequently accessed RAG context
For recurring questions that return the same documents:
DEVELOPERpythonfrom functools import lru_cache @lru_cache(maxsize=100) def get_top_documents(topic: str) -> str: """Local cache of most requested documents.""" docs = search_qdrant(topic, limit=5) return format_documents(docs) # If 2 users ask questions on the same topic # → same RAG context → cache hit on the prefix
Strategy 3: Grouping by tenant / category
DEVELOPERpython# Multi-tenant: each tenant has its own cached system prompt TENANT_PROMPTS = { "tenant_A": "You are TechCorp's assistant...", "tenant_B": "You are RetailCo's assistant...", } # As long as requests from the same tenant arrive within the TTL window # → cache works perfectly
Savings Calculator
Calculation formula
Monthly savings =
(num_requests × cached_tokens × normal_price × (1 - cache_discount))
- (num_requests × cached_tokens × cache_price)
- cache_write_cost
Concrete examples
| Profile | Requests/month | Cached tokens/req | Without cache | With cache | Savings |
|---|---|---|---|---|---|
| Startup | 50K | 2,000 | $300 | $75 | $225/mo |
| SMB | 300K | 3,000 | $2,700 | $540 | $2,160/mo |
| Enterprise | 2M | 5,000 | $30,000 | $4,500 | $25,500/mo |
| E-commerce | 1M | 2,500 | $7,500 | $1,500 | $6,000/mo |
Pitfalls and Best Practices
Common mistakes
| Pitfall | Consequence | Solution |
|---|---|---|
| Timestamp in prompt | Systematic cache miss | Exclude from cached prefix |
| Variable message order | Cache miss | Standardize the order |
| RAG context at the start | Prevents caching | Put at the end of prompt |
| TTL too short | Cache expires between requests | Increase traffic or TTL |
| Too many variants | Low hit rate | Reduce prompt variants |
Optimization checklist
- Structure the prompt: static at top, dynamic at bottom
- Minimize changes in the prefix
- Monitor cache hit rate
- Group requests by tenant/category
- Avoid dynamic elements (date, time) in the prefix
- Test with before/after metrics
FAQ
Is prompt caching compatible with streaming?
Yes, prompt caching is fully compatible with streaming across all 3 providers. Caching acts on input tokens, not on generation (output). You can cache your prompt and stream the response normally.
What happens if my system prompt changes?
A change to the system prompt invalidates the cache. The first request after the change pays full price (+ write surcharge with Anthropic). Subsequent requests benefit from the new cache. Tip: version your prompts and deploy changes during off-peak hours.
Does caching work in multi-tenant setups?
Yes, but each tenant will have its own cache (since system prompts differ). The trick: structure the prompt with a portion common to all tenants (cached) and a tenant-specific portion (uncached or with its own cache via Anthropic breakpoints).
How long does the cache stay active?
With Anthropic: 5 minutes, renewed on each hit. With OpenAI: 5-10 minutes, not guaranteed. With Google: configurable up to 24h. For continuous traffic (> 1 request / 5 min), the cache practically never expires with Anthropic.
Is prompt caching useful for low volumes?
Beyond ~100 requests per day with a stable prompt, prompt caching is worthwhile. Below that, the cache may expire between requests (especially with OpenAI's short TTL). For very low volumes, focus first on model selection optimization.
Ready to slash your LLM bill? Create your Ailog account and benefit from an optimized RAG pipeline with built-in prompt caching, hosted in France.
Tags
Related Posts
Smart RAG Caching: Cut Your LLM Costs by 80% (Without Losing Quality)
Complete guide to RAG caching: semantic cache, prompt caching, embedding cache, Redis vs GPTCache comparison, and ROI calculations to cut your LLM costs by 80%.
Evaluating a RAG System: Metrics and Methodologies
Complete guide to measuring your RAG performance: faithfulness, relevancy, recall, and automated evaluation frameworks.
Context Window Optimization: Managing Token Limits
Strategies for fitting more information in limited context windows: compression, summarization, smart selection, and window management techniques.