Skip to content

fix(routing): price InputInflation tokens as cache_read on cache branch - #17

Merged
mirainya merged 1 commit into
mirainya:mainfrom
ShuYingJiYu:fix/inflation-cache-pricing
Sep 11, 2026
Merged

mirainya merged 1 commit into
mirainya:mainfrom
ShuYingJiYu:fix/inflation-cache-pricing

Conversation

@SakuraPuare

Copy link
Copy Markdown
Collaborator

Summary

Upstream-injected inflation tokens (system prompts, tool schemas) are stable per session and share the provider cache lifecycle with the reusable prefix. The current code puts them in suffix alongside the newly appended user turn and prices them at pricing.InputPerToken (the full input rate). For any cache-supporting upstream this over-estimates the cache branch cost by roughly (InputPerToken - CacheReadPerToken) * inflation_tokens * n per window — about 10× the correct read price for typical Anthropic-style pricing.

Impact

For a session with InputInflation ≈ 1.8 and 305 requests / 128k prefix (a real production trace), the cache branch was estimated at ~41 units. The correct cost, computed with the same coverage/hit-rate observations but pricing inflation as cache_read, is roughly half that. This can make caching upstreams lose the cost race to no-cache alternatives that are actually more expensive in reality.

Fix

  • Preserve originalInputTokens before the inflation multiply.
  • suffix is now derived from the original count — only the newly appended user turn, which is never cacheable.
  • Inflation flows into cacheable = prefix + inflationTokens. CoverageRatio then splits that pool as before.
  • hits × cachedPortion × CacheReadPerToken and misses × cachedPortion × CacheWritePerToken now include the inflation share.
  • no_cache branch is unchanged: on non-caching upstreams the inflation still bills at full input via features.InputTokens in NoCacheInputCost.
  • breakEvenRequests mirrors the fix for consistency.

Tests

Two regression tests, both in internal/routing/cost_test.go:

  1. TestEstimateWindowCostInflationBilledAsCacheRead — 1.8× inflation with hit_rate=1, coverage=1. CacheInputCost must stay ≈ 0 (all inflation goes through cache), CacheReadCost must match hits × 180 000 × 5e-7, CacheWriteCost must match 1 × 180 000 × 6.25e-6, cache path must win over no-cache.
  2. TestEstimateWindowCostTrueSuffixStillFullPrice — 105k input with 100k reusable prefix and 1.5× inflation. Ensures the 5 000-token user turn is still billed at full InputPerToken (only inflation moves, not real suffix).

Full go test ./... and go vet ./... pass on Go 1.24.

Empirical verification

I validated the direction with a real A/B against production upstreams (Anthropic via a proxy). Same 144k-token payload sent to a no-cache endpoint and a cache-enabled endpoint. Anthropic usage reports 575k billed tokens for the cache endpoint (with 405k cache_read_input_tokens after warm-up) and 575k input_tokens for the no-cache endpoint. So the inflation tokens do bill — but on cache-supporting endpoints the majority is charged at cache_read, not full input, exactly what the new model reflects.

Upstream-injected inflation tokens (system prompts, tool schemas) are stable
per session and share the provider cache lifecycle with the reusable prefix.
Previously they landed in "suffix" alongside the new user turn and were billed
at pricing.InputPerToken - the full input rate - which overestimated the cache
branch cost by up to (InputPerToken - CacheReadPerToken) * inflation_tokens * n
per window, i.e. roughly 10x the correct read price for Anthropic-style
providers.

Fix: preserve originalInputTokens before the inflation multiply, compute the
true suffix from that (only the newly appended user turn), and route the
inflation portion into cacheable = prefix + inflation. The whole cacheable
pool now flows through hits/writes at cache_read/cache_write rates, matching
what providers like Anthropic actually bill for stable injected content.

no_cache branch is unchanged: on non-caching upstreams the inflation portion
really does bill at full input, which is correctly reflected via the still-
inflated features.InputTokens in NoCacheInputCost.

breakEvenRequests mirrors the fix so the diagnostic stays consistent with the
main estimator.

Regression tests:
- TestEstimateWindowCostInflationBilledAsCacheRead: 1.8x inflation on a
  full-hit workload must produce zero CacheInputCost and non-zero
  CacheReadCost, otherwise the old bug is back.
- TestEstimateWindowCostTrueSuffixStillFullPrice: the new user turn (105k -
  100k prefix) stays at full input price; only the 52.5k inflation moves.
@mirainya
mirainya merged commit 9bd000b into mirainya:main Sep 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants