Skip to content

pricing: bill cache hits at the model's own fraction on Opus 5.5, Sonnet 5.5, Fable, Mythos - #201

Open
Vishal Kannankara (kvishalv) wants to merge 2 commits into
microsoft:mainfrom
kvishalv:fix-claude-cache-hit-price
Open

Vishal Kannankara (kvishalv) wants to merge 2 commits into
microsoft:mainfrom
kvishalv:fix-claude-cache-hit-price

Conversation

@kvishalv

Copy link
Copy Markdown
Contributor

pricing.weights_for returns one curve for every claude-* id that is not in MODEL_WEIGHTS, with cache_read=0.10, and its docstring calls the Claude ratios uniform. Anthropic's price list (pricing, read on 2026-11-10) shows that every ratio is the same on all Claude models except the cache hit. A cache hit costs 0.05x the input price on Opus 5.5 and Sonnet 5.5, and 0.025x on Fable and Mythos 5.1 and 5.2. It costs 0.1x on the other models. Cache hits are 90% or more of an agent session's tokens, so the wrong ratio moves the whole figure. The MODEL_WEIGHTS snapshot is from 2026-07-27, before these models, so they all fall to the generic curve.

I measured it on my own Claude Code logs, counts only. In the 150 most recent session files (14,178 unique assistant messages, with the 1-hour split from #192), 90% of the messages are on claude-opus-5-5, claude-sonnet-5-5 or claude-fable-5-1, and cache hits are 95% to 98% of their tokens. Priced as main does it, effective tokens are 52% above the documented price on Opus 5.5, 31% above on Sonnet 5.5, and 48% above on Fable 5.1. Over all models in the logs, they are 41% above. te status and the ledger overstate these sessions by that much.

_CLAUDE_CACHE_READ lists the 2 exceptions as patterns on the id with dots as dashes: claude-(opus|sonnet)-5-5 and claude-(fable|mythos)-5-[12]. _claude_weights returns the Claude curve with that cache_read, and weights_for uses it for any other claude-* id. A router's spelling (claude-sonnet-5.5), a provider prefix and a dated snapshot match too. Mythos has no id in the models overview (limited availability), so a pattern covers it and I did not guess an id.

The tests are in the existing tests/test_cache_write_1h.py. They check the 6 ids and 3 spellings that get the model's own ratio and price, 9 other Claude ids that keep 0.1, and a live session on Opus 5.5 priced through accounting.effective. Two existing tests change. The test for "a Claude id outside the table" used claude-opus-5-5 as its example, which no longer keeps 0.1, so it uses claude-opus-4-5. The ledger test in tests/test_live_hooks.py priced a session of the helper's default model, Opus 5.5, at 485 per request. It is 435 with the 0.05 ratio, and the comment says why.

How it was produced: Claude Code, an AI coding agent, found the mismatch while checking #191 and #192 against Anthropic's price list, measured it on my logs, and wrote the fix and the tests. I directed the work and ran the checks below.

What was verified, on macOS with Python 3.12.0, on main at fd6299a:

  • Before the fix, python -m pytest tests/test_cache_write_1h.py tests/test_live_hooks.py -k "cache_hit or folded_once" reports 11 failed and 10 passed. The failures are the 9 ratio cases, the live-session test and the ledger test. The 9 control cases pass. The failing values are the ones the bug predicts, for example assert 0.1 == 0.05, and 1455 for the ledger test where 1305 is right.
  • After the fix, the same 2 files report 118 passed and 2 skipped. The skips need Windows.
  • python -m pytest tests -rfEs --tb=line reports 901 passed, 16 skipped and 0 failed. Each skip needs a tool or a platform that the machine does not have, for example a real Codex, Hermes or opencode CLI.
  • main has since moved to 81e32e5. None of its 6 new commits touches pricing.py or the accounting code, and the branch merges cleanly with it.

Note for the experiments, as in #192: Claude rows priced after this change on these models are not directly comparable to rows priced before it. I did not run experiments/tests.

…net 5.5, Fable, Mythos

weights_for gave every claude-* id outside MODEL_WEIGHTS one curve with
cache_read=0.10 and called the ratios uniform. Anthropic's price list bills
a cache hit at 0.05x input on Opus 5.5 and Sonnet 5.5 and at 0.025x on
Fable and Mythos 5.1 and 5.2; every other ratio is the same on all models.
Cache hits are most of a session's tokens, so a Claude Code session on one of
these models was priced well above its bill. Pick the ratio by model in
_claude_weights, on the id with dots as dashes, so a router's spelling and a
dated snapshot match too. The live ledger test prices Opus 5.5 at 435 per
request, not 485, and an "id outside the table" test uses Opus 4.5.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct. Here's what I checked.

  • The prices. I read Anthropic's pricing page today. Cache hits are 0.05x input on Opus 5.5 ($0.20 against $4) and Sonnet 5.5 ($0.10 against $2), and 0.025x on Fable 5.1/5.2 and Mythos 5.1/5.2 (the page's footnote 1). Every other model is at 0.1x, including Opus 5 ($0.50/$5), Sonnet 5 ($0.20/$2), Fable 5 and Mythos 5 ($1/$10), and Haiku 5.5 in both tiers. The other ratios match the curve on those models (5m write 1.25x, 1h write 2x, output 5x), so changing only cache_read is right.
  • The patterns. I copied _CLAUDE_CACHE_READ and the dot-to-dash step of _claude_weights into a standalone snippet and ran ids through it. These get the new ratio: claude-opus-5-5, claude-opus-5-5-20261101, claude-sonnet-5.5, us.anthropic.claude-opus-5-5-v1:0, claude-opus-5-5[1m], claude-fable-5-1 and claude-mythos-5.2. These stay at 0.1: claude-opus-5, a dated claude-opus-5-20260601, claude-fable-5, claude-fable-5-20260801, claude-haiku-5-5 and claude-opus-5-50. The \b is what keeps a dated Fable 5 id (5-2026...) out of the 5-[12] pattern.
  • Lookup order. claude-opus-5 and claude-sonnet-5 are still exact MODEL_WEIGHTS entries at 0.10, which is correct for them. No 5.5, 5.1 or 5.2 id is in the table, so none of them skips the new path.

I didn't run the test suite.

Optional: the comment above WEIGHTS_CLAUDE (pricing.py:118) still says Opus/Sonnet/Haiku share the ratios. It could point to _CLAUDE_CACHE_READ, as the module docstring now does.

Reviewed with help from Claude Code.

…odel

Two comments in pricing.py still called the Claude ratios the same on every
model: the one above WEIGHTS_CLAUDE and the one above AGENT_DEFAULT_MODEL.
Point both to _CLAUDE_CACHE_READ. No code changes.
@kvishalv

Copy link
Copy Markdown
Contributor Author

Muhammad Abdullah Butt (@1AbdullahButt) thanks for checking the prices and the id patterns yourself, and for the list of ids you ran. You are right about the comment: it still called the ratios shared. I changed it in ee5340f, and also the comment above AGENT_DEFAULT_MODEL, which said the same. No code changed.

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