fix(routing): price InputInflation tokens as cache_read on cache branch - #17
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
suffixalongside the newly appended user turn and prices them atpricing.InputPerToken(the full input rate). For any cache-supporting upstream this over-estimates the cache branch cost by roughly(InputPerToken - CacheReadPerToken) * inflation_tokens * nper window — about 10× the correct read price for typical Anthropic-style pricing.Impact
For a session with
InputInflation ≈ 1.8and 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
originalInputTokensbefore the inflation multiply.suffixis now derived from the original count — only the newly appended user turn, which is never cacheable.cacheable = prefix + inflationTokens.CoverageRatiothen splits that pool as before.hits × cachedPortion × CacheReadPerTokenandmisses × cachedPortion × CacheWritePerTokennow include the inflation share.no_cachebranch is unchanged: on non-caching upstreams the inflation still bills at full input viafeatures.InputTokensinNoCacheInputCost.breakEvenRequestsmirrors the fix for consistency.Tests
Two regression tests, both in
internal/routing/cost_test.go:TestEstimateWindowCostInflationBilledAsCacheRead— 1.8× inflation with hit_rate=1, coverage=1.CacheInputCostmust stay ≈ 0 (all inflation goes through cache),CacheReadCostmust matchhits × 180 000 × 5e-7,CacheWriteCostmust match1 × 180 000 × 6.25e-6, cache path must win over no-cache.TestEstimateWindowCostTrueSuffixStillFullPrice— 105k input with 100k reusable prefix and 1.5× inflation. Ensures the 5 000-token user turn is still billed at fullInputPerToken(only inflation moves, not real suffix).Full
go test ./...andgo 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
usagereports 575k billed tokens for the cache endpoint (with 405kcache_read_input_tokensafter warm-up) and 575kinput_tokensfor 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.