The prompt-cache floor that made the cheap model expensive
I built an AI-assisted QA plugin at KnowBe4 that reviews e-learning course packages before they ship, and one stage of it grades keyframes pulled from a rendered course against a shared rubric. The obvious cost architecture for that stage is a two-tier one: a cheap, fast model triages every frame, and only the frames that trip a finding, an explicit escalation flag, or a confidence score below threshold get sent to a more expensive model for a second look. Most frames are clean, so most of the volume should run on the cheap tier. The design only pencils out, though, if the cheap tier's cost per call is actually cheap, and that depends on something the architecture diagram doesn't show at all.
The assumption baked into the diagram
The triage call sends the same rubric text on every single frame, roughly 1,500 tokens of instructions that don't change from one call to the next. Prompt caching exists for exactly this case: pay full price the first time, then pay a fraction of that price on every subsequent call that reuses the same prefix. The cost model for the whole two-tier design assumed that rubric was being cached. It had to be, or the "cheap" tier wasn't actually cheap, it was just a smaller sticker price on the same full-price call repeated thousands of times a week.
Nothing in the pipeline verified that assumption. The request set cache_control on the shared
prefix, the shape of the call looked correct, and the architecture was designed around caching
working. Designed around is not the same as measured.
What the token counts actually said
Reading the raw usage data per call told a different story. Every triage call carried 2,243 input
tokens, of which the shared rubric accounted for 1,495, about 67 percent of the request. The
cache_read_input_tokens field, the number that says how many of those tokens came from cache
instead of full price, read zero. Not low. Zero, on every single run ever recorded for that stage.
The cheap tier had never once served the shared prompt from cache, not on the first call and not on
the ten-thousandth.
The reason turned up in the model's own documented limits. That model's minimum cacheable prefix is 4,096 tokens. The shared rubric was 1,495. The request was well under the floor the model requires before it will cache anything at all, so the API silently accepted the caching directive on every call and did nothing with it. No error, no warning, no field that flags a caching request as rejected. It just billed full price and returned a normal-looking response, indistinguishable from a cache hit unless you go looking at the token accounting yourself.
Why the fix wasn't a bigger prompt
The reflexive fix is to pad the rubric past the floor: add filler text, restructure the prompt, whatever gets the shared prefix over 4,096 tokens so the same cheap model starts caching. I considered it and rejected it. Inflating a rubric to satisfy a caching threshold couples prompt content to a billing implementation detail, and the next rubric edit could shrink it back under the floor without anyone noticing until the telemetry was checked again.
The more capable model in the pipeline has a minimum cacheable prefix of 1,024 tokens, comfortably under the 1,495-token rubric as written. Running triage on that model instead meant the shared prompt cached correctly from the first call. On one module, switching triage tiers took cache reads from 0 to 23,920 tokens per run and cut total run input tokens by about 25 percent, even while output tokens rose because the more capable model reasons more and finds more: four visual findings where the cheap tier had found one. At the plugin's planned weekly volume, the difference works out to roughly $130 to $156 a week at list pricing, and that number comes from real per-frame token measurements scaled to a volume target, not a hypothetical.
Verification as an ongoing practice
The interesting failure here isn't a bug in the request. cache_control was set correctly, the
architecture was sound on paper, and every unit test that checks the shape of a call would have
passed. The gap only exists in a field most of the pipeline's own logging never printed. A two-tier
cost architecture makes exactly one load-bearing assumption that the diagram can't express: that
the cheap tier's repeated context clears whatever cache floor that specific model enforces. That
floor differs by model and isn't obvious from pricing pages built around per-token rates, not
caching behavior.
So checking cache_read_input_tokens against expected volume is now a standing check on any stage
in that pipeline that claims to use caching, not a one-time audit. Every stage that sets
cache_control reports its cache-read count in its own output alongside the findings it produces,
specifically so a silent zero shows up in the normal run log instead of requiring a special
investigation to surface. A cost model is a claim about what the API will actually do with a
request. The only way to know if that claim holds is to read what the API says it did, not what the
architecture intended.
More in AI
All in AI →- I deleted five AI review agents and the reviews got betterA five-specialist review fan-out looked thorough. One session cost $5.88 and 905 seconds for zero findings, and that's what actually changed the design.AI
- A green test suite is not proof a non-programmer's edit was safeA change-tier system and a verifier that fails risky edits even when every test passes, because a green suite is not evidence the change was safe.AI
- Don't trust a model's arithmetic, only its tool callsWhy I moved every numeric answer out of model prose and into a calculator tool call, and what a data-quality investigation taught me about trusting the output.AI