Surface prompt caching that was requested and never delivered - #63
Conversation
A host can accept a cache marker on an API layer that does not implement caching and return zero cached tokens without an error. The run then reports uncached cost and latency as though they were properties of the tier or the model. serving_metrics already avoids this for Anthropic by measuring it on its native Messages API; nothing detected the same shape anywhere else. cache_effectiveness compares caching requested against caching observed and warns when a route asked on every call and received none. Usage is read in the Responses, Chat Completions and Anthropic shapes, because a missed field name silently reads as a 0% cache. Doubleword is the live case. Its caching guide states cache_control is supported on /v1/chat/completions and "isn't supported yet" on /v1/responses, and its async/flex tier is reachable from either. The doubleword provider block is type: flex, which is the Responses API, and four candidate models route through it. Measured 2026-09-14 on DeepSeek V4.1 Flash, one account, one byte-identical 4k-token prompt repeated six times per route: chat completions cached 57.8% on realtime and 58.1% with service_tier flex, while /v1/responses cached 0% on both the flex and the default tier, with and without the marker. Caching tracks the API, not the tier. compound.yaml now records that, so a flex run through responses is not mistaken for evidence that the tier cannot cache. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016AKUCAqPBAs4GEgtmWgCYN
… claim
Codex review of the first commit found the parser inverted its own purpose.
Native Anthropic Messages reports input_tokens alongside cache_read_input_tokens,
so branching on input_tokens sent that shape down the Responses path, looked for
input_tokens_details.cached_tokens which Anthropic never sends, and scored it at
0% cache. Anthropic is the provider the module docstring exists to protect, and
its input_tokens excludes cache reads, so the total was undercounted too. Now
selected explicitly: prompt_tokens for the Chat Completions family, then the
native Anthropic counters, then Responses.
The first test passed because it invented the schema. It used prompt_tokens with
cache_read_input_tokens, which no provider emits. Tests now use payloads captured
from live runs on 2026-09-14 and a native Anthropic block, plus a cold-write case
so a real 0% is not confused with a parse failure.
Also from the review:
* a nested cached_tokens of zero is a measurement, not a missing field, so it
no longer falls back to a stale top-level counter;
* cache_marked, which the serving harness already emits, is accepted as the
request flag alongside cache_requested;
* the warning reports how many calls asked rather than claiming all of them;
* the docstring says to group by host, API and model, because an aggregate can
hide one wholly uncached route behind others.
cache_strategy: none is withdrawn from the doubleword block. The field is read
only for direct/<name> routes, where this host already defaults to none;
doubleword/flex and doubleword/realtime never consult it, so declaring it implied
an enforcement that does not exist. The measurement is documented as a comment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016AKUCAqPBAs4GEgtmWgCYN
Codex review appliedHigh — the parser inverted its own purpose. Native Anthropic Messages reports The original test passed because it invented the schema ( Low — explicit zero treated as missing. A nested Low — warning overstated. It now reports how many calls asked rather than claiming all of them. Medium — not wired into shipped reporting. Partially addressed: the helper now accepts
On the warning threshold, Codex's recommendation is taken: "never observed" stays the strong diagnostic, since partial delivery is explained by cold starts, prefix differences and eviction. The docstring now says to group by host, API and model, because an aggregate can hide one wholly uncached route. 551 passed, 3 skipped. |
Problem
A host can accept a cache marker on an API layer that does not implement caching, return zero cached tokens, and raise no error. The run then reports uncached cost and latency as though they were properties of the tier or the model.
serving_metricsalready avoids this for Anthropic, by measuring it on its native Messages API rather than the OpenAI-compatible layer that "would score Anthropic at 0% cache". Nothing detected the same shape anywhere else.The live case
Doubleword's caching guide states
cache_controlis supported on/v1/chat/completionsand "isn't supported yet" on/v1/responses. Its async/flex tier is reachable from either API. Thedoublewordprovider block istype: flex, which is the Responses API, and four candidate models route through it.Measured 2026-09-14 on DeepSeek V4.1 Flash, one account, one byte-identical 4k-token prompt repeated six times per route:
Caching tracks the API, not the tier. A flex run through chat completions pays async rates and still caches; a flex run through responses pays async rates on every input token.
Change
cache_effectiveness()compares caching requested against caching observed and warns when a route asked on every call and received none. Usage is read in the Responses, Chat Completions and Anthropic shapes, because a missed field name silently reads as a 0% cache.compound.yamlrecords the Doubleword finding against the provider block, so a flex run through responses is not mistaken for evidence that the tier cannot cache.Tests
549 passed, 3 skipped. New tests cover requested-and-denied, requested-and-served, never-requested, and all three usage shapes.
🤖 Generated with Claude Code