Measure the prompt cache instead of inferring it #103
No reviewers
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agent-proxy!103
Loading…
Reference in a new issue
No description provided.
Delete branch "prompt-cache-accounting"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #101 and #102.
What #101 actually found
The ticket read a 53 KB byte-identical system prefix as evidence the
sirens-echo/deepseekroute was uncached. It is not, and it was not then. LiteLLM'sgen_ai.cost.cache_read_costspan attribute was already nonzero fordeepseek-v4-flashover the same window and absent on both Ornith groups, so DeepSeek's automatic prefix caching had been working the whole time.What was missing was the ability to say so. The proxy read
prompt_tokensandcompletion_tokensout of the upstream usage block and discarded every cache field beside them, so a fully cached route and a fully uncached one produced identical evidence. Cost alone cannot give a hit rate either, because LiteLLM prices that model from its own bundled map rather than from Deploy-owned configuration.What this does
parse_cache_usagenormalizes the three shapes that reach this proxy through LiteLLM:prompt_cache_hit_tokens/prompt_cache_miss_tokensprompt_tokens_details.cached_tokens, which is also LiteLLM's normalized fieldcache_read_input_tokenswith its separate creation chargeThe counts surface in the response usage block under the OpenAI-canonical spelling, on request and upstream spans, in three Prometheus counters, and on the terminal trajectory event.
Reported zero is not silence
A provider that reports nothing is an unmeasured route, not a cache miss. An Ollama backend reuses its KV cache without ever saying so, and publishing that silence as a 100% miss would invent a regression it never had.
UpstreamResultcarriescache_usage_reportedbeside the counts and every surface stays absent without it. A reported zero, which is what the turn that populates a cache returns, is a real measurement and is published.Metrics record once per served response rather than once per upstream attempt, so a turn that retried counts the tokens the caller was actually billed for. Misses derive from
prompt_tokens - cached_tokensrather than from a provider's own miss field, which keeps the counters summing to the billed prompt on every provider.Request shape
The request span also carries
gen_ai.request.system_bytes,gen_ai.request.tool_count, andgen_ai.request.tool_bytes. The second half of #101 asks whether a narrower default tool roster is worth the tool calls it would lose, and that trade needs the roster's share of the request measured at the one point every governed route passes through. These are byte counts, so they carry no model-visible content and emit with body capture off.Not done, on purpose
Nothing here injects
cache_controlbreakpoints. No configured backend needs one, and an unexercised injector would be dead policy on the live routes.#102
The MCP surface already declares
tools.listChanged: false, which is what makes a client-side roster cache correct against Agent Proxy. Stateless HTTP gives the server no channel for an unsolicited notification, so a client that cached the roster and waited for one would wait forever. That is now a tested contract rather than an accident, because a silent flip to true would break every caching client.The cache itself belongs to the MCP client. For the Discord path that is Sirens Echo, tracked in sirens-echo#163, and the four round-trips per turn are its two roster servers initializing.
Verification
ward exec test(307 passed),format-check,lint,typecheck, andpre-commitover all files.Deploy's half is coilyco-bridge/deploy#404.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com