Prompt caching, section 1 of 2, 5h

Prompt caching

By the end: You can cut repeated-prompt cost substantially and tell whether it is working.

  • What is cached and why it is cheaper
  • Prefix matching, and what invalidates it
  • Ordering a prompt for caching
  • Verifying it from usage data

New to prompt caching? Most calls to a model repeat a great deal of text: the same instructions, the same examples, the same reference documents, followed by a small changing question. Prompt caching lets the provider keep the processed form of that unchanging opening stretch, so it does not have to be worked through again. Send the same opening twice and the second call reuses it, at a fraction of the price and noticeably faster. Why it matters

A model re-reads your entire prompt on every request. If the first twenty thousand tokens are the same every time — a system prompt, a tool schema, a document the conversation is about — you are paying to process identical text repeatedly. Prompt caching lets the provider keep the computed state for that prefix and skip the work, which is cheaper and faster. This section covers what is actually cached, the one rule that decides whether you get a hit, how to order a prompt so you do, the arithmetic of whether it pays, and how to check from the response rather than by hoping.

What is cached, and why it is cheaper#

What is stored is not the text but the model's internal state after reading it — the key and value tensors for those tokens, the same KV cache that makes generation fast. Because attention is causal, every token attends only to what came before it, so that state depends on the prefix alone and is valid for any continuation. That is the whole mechanism, and it explains the constraint that follows: the cache works from the start of the prompt forward, and only for an exact match.

The pricing follows the work. Writing the prefix into the cache costs somewhat more than processing it normally, because the state has to be stored as well as computed. Reading it back costs a small fraction, because nothing is recomputed. Providers differ in the exact multipliers and in how long an entry survives, so check the ones you are billed under, but the shape is the same everywhere.

Prefix matching, and what invalidates it#

The match is on an exact prefix, byte for byte, from the very first token. Change anything early and everything after it is a miss, regardless of how much of the rest is identical. This is the single fact that determines whether caching works for you, and it has one consequence people discover the expensive way.

prompt                                          cached?
[system 20k][doc][question A]                   write
[system 20k][doc][question B]                   hit on the 20k + doc
[system 20k + today's date][doc][question C]    MISS on everything
[question D][system 20k][doc]                   MISS on everything
The third row is the trap: one variable token near the front invalidates every cached token after it. A timestamp, a user id, a randomised greeting -- anything that changes per request belongs after the stable material, not before it.

The date in the system prompt

Putting the current date at the top of a system prompt is a common and reasonable-looking habit, and it silently costs you every cache hit you would otherwise have had. If the model needs the date, put it at the end of the prompt, after the cached material, or in the user turn. The same applies to a session id, a user name, or anything else that is stable within a request and different between them.

Ordering a prompt for caching#

The rule follows directly: arrange the prompt from least variable to most. Everything identical across all requests first, then material shared within a session, then the part that changes every time.

1. tool definitions            identical for the lifetime of the deployment
2. system instructions         identical until you deploy a change
3. few-shot examples           identical, and often large
4. retrieved documents         identical within a conversation
5. conversation history        grows, but only at the end
6. the current user message    different every time
7. the current date, ids       different every time
Conversation history sits at 5 for a reason: appending to the end preserves the prefix, so each turn re-reads the whole conversation from cache and only pays full price for the new turn.

That last point is what makes caching so effective for agents in particular. An agent loop appends a tool call and its result to the end of a growing message list, so every iteration is a pure extension of the previous prefix. Handled correctly, a twenty-step agent pays full price for its context once and a tenth of it nineteen times. Handled carelessly — by rewriting or re-summarising the history in place — it pays full price twenty times for the same tokens.

Working out whether it pays#

Caching is not free on the first request, so for genuinely one-off prompts it is a loss. The break-even arrives quickly, and the arithmetic is worth doing on your own numbers rather than assuming either way.

BASE, WRITE, READ = 1.0, 1.25, 0.10     # multipliers on a normal input token

def cost(prefix, suffix, requests, cached):
    """Total input cost in units of one normal input token."""
    if not cached:
        return requests * (prefix + suffix) * BASE
    first = prefix * WRITE + suffix * BASE
    rest = (requests - 1) * (prefix * READ + suffix * BASE)
    return first + rest

prefix, suffix = 20_000, 500
for n in (1, 2, 3, 10, 100, 1000):
    u, c = cost(prefix, suffix, n, False), cost(prefix, suffix, n, True)
    print(f"{n:>5} requests  uncached {u:>10,.0f}  cached {c:>9,.0f}  {1 - c/u:>7.1%}")

#     1 requests  uncached     20,500  cached    25,500   -24.4%   <- a loss
#     2 requests  uncached     41,000  cached    28,000    31.7%
#     3 requests  uncached     61,500  cached    30,500    50.4%
#    10 requests  uncached    205,000  cached    48,000    76.6%
#   100 requests  uncached  2,050,000  cached   273,000    86.7%
#  1000 requests  uncached 20,500,000  cached 2,523,000    87.7%
One request costs a quarter more; from the second onward it pays, and at volume it approaches the read multiplier, which is a ninety per cent saving on the cached portion. Substitute your provider's multipliers -- they are the only inputs that matter.

The number that decides it in practice is not the request count but whether the second request arrives before the entry expires. Cache lifetimes are short — commonly a few minutes, extendable at extra cost — so a prefix reused twenty times an hour caches beautifully and the same prefix used twice a day never hits at all. Work out your reuse rate within the lifetime, not in total.

Verifying it from usage data#

Never assume a cache is working. Every provider reports cache reads and writes in the usage block of the response, and those fields are the only evidence that matters — a prompt you believe is stable is often not, and the fields say so immediately.

usage:
  input_tokens                    512     paid at the normal rate
  cache_creation_input_tokens  20,000     written this request
  cache_read_input_tokens           0     read from cache

next request, if all went well:
  input_tokens                    498
  cache_creation_input_tokens       0
  cache_read_input_tokens      20,000     the prefix was reused
Field names vary by provider; the three quantities do not. If creation is non-zero on every request, your prefix is not stable and something near the front is changing.

Log those three numbers per request from the start. It turns cache behaviour into something you can graph and alert on, and the usual discovery is that a deploy changed a system prompt, or that one code path assembles the prompt in a different order from the rest — both of which show up as a cache hit rate falling off a cliff, and neither of which shows up any other way.

Where people go wrong

Putting personalisation at the top of the system prompt. Opening with the user's name feels natural and gives every single user a completely separate cache, which is the same as having none.

If this comes up in an interview

What is prompt caching and when does it help?

Providers cache the processed form of a stable prompt prefix and charge much less to reuse it. It helps whenever calls share a long fixed preamble: a large system prompt, tool definitions, few-shot examples, or a document queried repeatedly.

What breaks prompt caching?

Any change in the prefix, since matching runs from the start and stops at the first difference. Timestamps, request ids, a user name near the top, or JSON serialised with unstable key order all invalidate it silently.

How do you verify caching is working?

Check the cached token count in usage data on repeated similar requests. Consistently zero means something volatile is in the prefix. It is worth asserting on in a test, because the failure has no behavioural symptom.

Build this: Take a prompt with a long system message, send it twenty times, and record the cached token count. Then add a timestamp to the top and watch it drop to zero.

This is one section of Prompt caching, 9 hours across 2 sections. Read it in full to get the whole topic in order.

Stop 14 of 50 on the AI Engineer route

0 of 50 topics done · 0h behind you, 603h to go

Next on this routeTool callingThe model asks; your code acts.7h

What you have marked done is stored in this browser only. It does not reach a server, and clearing site data will clear it.