Six percent. That is how much of our documentation assistant p95 latency was spent generating tokens the user actually read. Everything else was setup: embedding the question, searching, reranking, and waiting for a model to re read a 5,120 token instruction block it had already read four hundred times that hour.
Once you see the request that way, cost and latency stop being two projects. They are the same project, and prompt caching is where both of them start.
Where latency actually goes in one assistant request
Last part we wrapped the assistant in OpenTelemetry spans and found a CPU reranker eating 2,310 ms of a 6,605 ms p95. This part we spend those traces: cache the prefix, move the offline work to a batch, and stream what is left. Nothing here is guesswork, because the spans already told us which component to attack.
Before optimising anything, separate time to first token from total time. Users perceive the first one. Your cost report reflects the second. A change that improves one and worsens the other is common enough that measuring both is not optional.
3,204 ms of that 4,009 ms elapsed before a single character reached the user, and 805 ms produced 118 chunks of readable answer. Reading the prompt is the expensive part, not writing the reply. That ratio is what makes caching the highest leverage change available to you, and it is why I reach for it before touching model choice, which is the next part of this series.
Prompt caching and what a breakpoint keys on
Prompt caching stores the model internal representation of a prompt prefix so a later request with an identical prefix skips reprocessing it. Anthropic charges cache writes at 1.25 times base input price for a five minute entry, 2 times for a one hour entry, and cache reads at 0.1 times base input. OpenAI applies caching automatically once a prefix passes 1,024 tokens. Both providers key on an exact prefix match.
Here is where almost everyone, including me, gets it wrong. Intuition says cache the biggest block of tokens, and in a retrieval system that is the retrieved chunks. Retrieved chunks change with every question. A cache write happens only at your breakpoint, so putting the breakpoint after content that varies writes a fresh entry every single request and reads one never.
Field note
I shipped a breakpoint on the block that held the retrieved chunks plus a generated timestamp header. For eleven days our cache write charges rose and our read charges stayed at zero, which nobody noticed because the total bill was only 25 percent above the uncached baseline and traffic was growing anyway. It surfaced when I plotted cache_read_input_tokens and got a flat line at 0. Moving the breakpoint up two blocks, onto the last block identical across requests, took four minutes. It cut prefix spend by 57 percent that afternoon.
Rule to hold onto: place the breakpoint on the last block whose prefix is byte identical across the requests you want sharing a cache. For a retrieval assistant that is the end of system instructions, tool definitions and few shot examples, before anything query dependent. Smaller than you expected, and it is the one that pays.
Now the failure you will actually hit. Caching below the model minimum produces no error at all. Minimums differ per model, and they are not intuitive: 1,024 tokens for Claude Sonnet 5 and Opus 4.8, but 4,096 for Haiku 4.5. Route a request to the cheap model to save money and your caching silently stops working.
Two more constraints worth memorising. You get four explicit breakpoints, and the lookback window is 20 blocks, so a long conversation can push your breakpoint past the last write and lose every hit. And on a growing chat, automatic caching falls into exactly the same trap as my timestamp block: it places the breakpoint on the last cacheable block, which in a retrieval prompt is the one that changes.
Cost arithmetic on a cached prefix
Vendor guidance says stay on the default five minute cache and only reach for the one hour entry in unusual cases. For a steady public API that is right. For an internal assistant it is wrong, and the reason is traffic shape. Our staff traffic is bursty: a cluster of questions at 09:40, silence until 11:15. Five minute entries expire in the gaps, so we paid write prices on 29 percent of requests. One hour entries cost twice as much to write and we write them 25 times less often.
Figures below are for a 5,120 token prefix on Claude Sonnet 5 at introductory pricing of 2 dollars per million input tokens, cache reads at 0.20 and five minute writes at 2.50. Prefix only; retrieved chunks and output tokens are extra and unaffected.
| Configuration | Measured hit rate | Cost per 1,000 requests | Saving |
|---|---|---|---|
| No caching | 0 percent | 10.24 dollars | baseline |
| Five minute TTL | 71 percent | 4.44 dollars | 57 percent |
| One hour TTL | 96 percent | 1.80 dollars | 82 percent |
| One hour TTL via Batch API | 96 percent | 0.90 dollars | 91 percent |
Cache discounts and the batch discount stack, which is the last row. Verdict: if your traffic arrives in bursts with gaps longer than five minutes, move to the one hour TTL and stop reading the default as a recommendation. If your traffic is genuinely continuous, stay on five minutes, because a five minute entry refreshes on every hit at no charge and you will never pay the 2 times write.
Batching work that nobody is waiting for
Not every model call has a human attached. Our assistant regenerates changelog summaries nightly, re scores the eval set from Part 20, and back fills ticket categories. All of that ran through the synchronous endpoint for months at full price, competing with live traffic for the same rate limit. Batch endpoints halve the price and take that pressure off, which is the same batch versus real time split covered for classical models in serving machine learning models.
Batch caching is best effort, because requests run concurrently and a cache entry only exists after the first response begins. Anthropic quotes hit rates from 30 to 98 percent; we saw 88. Use the one hour TTL inside a batch, since a batch commonly runs longer than five minutes. And a limit that caught me: cache pre warming with max_tokens set to 0 is rejected inside a batch, so warm the cache with a synchronous call first, then submit.
Streaming and perceived latency
Streaming changes no cost and no total duration. What it changes is when the user stops waiting, and that is worth a great deal. Our support team rated the assistant usable at a 4,009 ms non streamed response and unusable at 6,605 ms, but rated a 6,605 ms streamed response as fine, because text began arriving at 3,204 ms.
Streaming has a cost elsewhere though, and it is one people discover late. Output validation cannot run on a partial response. If you enforce a JSON schema as in Part 4, or a guardrail as in Part 23, you have already streamed unvalidated text to the browser by the time the check fails. We stream free form answers and buffer anything structured. Splitting those two paths is a five line change and it removes an entire category of retraction bug.
One practical warning about timeouts. A cache miss on a large prefix is much slower than a hit, so a timeout tuned against warm traffic will fire on the first request after every deploy. Ours was set at 8 seconds and the cold path took 11.
Response caching above the model
Prompt caching is provider side and only skips prefix reprocessing. A response cache in your own service skips the call entirely. For our assistant, 12 percent of questions were byte identical repeats within an hour, mostly during onboarding weeks when six new hires asked the same VPN question. Those cost nothing now.
Key the response cache on the question text and a corpus version stamp, so republishing the docs invalidates every stored answer at once. Without that stamp you will serve last month answer about a setting that moved, and nobody will be able to explain why.
Semantic response caching, where you embed the incoming question and serve the stored answer for any question above a similarity threshold, is the one I would avoid. We trialled it at cosine 0.95 and it served the answer for how to rotate a production API key in response to how to rotate a sandbox API key. Two questions can sit at 0.97 similarity and have opposite correct answers. You buy a few percent extra hit rate and pay for it in wrong answers that look confident, which is precisely the failure the retrieval evaluation in Part 13 exists to prevent.
| Workload shape | Mechanism to reach for | Gotcha that bites |
|---|---|---|
| Long fixed system prompt, continuous traffic | Prompt cache, five minute TTL | Breakpoint must sit before anything query dependent |
| Same prompt, bursty traffic with gaps | Prompt cache, one hour TTL | Writes cost 2 times base, so only worth it below roughly 85 percent hit rate |
| Thousands of calls, no user waiting | Batch API, 50 percent off | Results unordered and can expire at 24 hours |
| Slow acceptable, 24 hours too slow | Flex service tier | 429 resource unavailable is normal, needs a retry loop |
| Repeated identical questions | Exact match response cache | Must include a corpus version in the key |
| Similar but not identical questions | Nothing. Let it run | Semantic caches serve confident wrong answers near the threshold |
| User perceives the wait | Streaming | Cannot validate output you have already sent |
Cache the stable prefix before you tune anything else
On Monday, print cache_creation_input_tokens and cache_read_input_tokens for one hundred production requests and look at the ratio. If reads are near zero while writes are not, your breakpoint is on a block that changes, and moving it is a four minute fix worth more than any model swap you are considering. If both are zero, your prefix is under the minimum for the model you routed to. That single check found more savings for us than a quarter of prompt tuning did.
Order matters after that: prefix caching first, batch anything without a human waiting, stream what remains, and add an exact match response cache only if you have a version stamp to invalidate it with. Skip semantic caching. Model routing, which is where most teams start and where the real remaining money sits once the mechanical wins are taken, is Part 27. Costs also drift as traffic patterns change, so keep the hit rate on a dashboard next to the drift metrics from monitoring models in production, and read the generative AI cost breakdown if you need to explain the bill to somebody who does not write code.
Your turn. Run that ratio check on your own service today and tell me what you found. I have yet to see a first measurement come back healthy.
References
- Anthropic prompt caching, for the pricing multipliers, per model minimums, the 20 block lookback and cache pre warming with max_tokens set to 0
- Anthropic batch processing, for the 50 percent pricing, the 24 hour expiry, result streaming and the best effort cache hit range
- OpenAI prompt caching, for the automatic 1,024 token prefix threshold
- OpenAI Batch API, for the 24 hour turnaround and batch rate discount
- OpenAI flex processing, for the service_tier parameter and the 429 resource unavailable behaviour


DrJha