fix(databricks): bill cached tokens at cache rates and add missing Claude pricing - #37975
Conversation
…aude pricing Databricks cost calculation multiplied every prompt token by the input rate, so a cache read cost the same as an uncached token. Route it through generic_cost_per_token, which already understands cache reads and cache writes, and add the cache rates the registry was missing. Adds Claude Opus 4.7, Opus 4.8, Opus 5, Sonnet 5 and Fable 5 on Databricks.
Greptile SummaryThis PR makes Databricks spend calculation cache-aware and adds pricing metadata for newer Claude models.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains, and the previously reported test typing issue is resolved at the current head.
|
| Filename | Overview |
|---|---|
| litellm/llms/databricks/cost_calculator.py | Replaces direct input/output arithmetic with the shared cache-aware calculator while preserving legacy endpoint normalization. |
| model_prices_and_context_window.json | Adds Databricks cache rates, caching capability metadata, and five Claude pricing entries. |
| litellm/model_prices_and_context_window_backup.json | Mirrors the Databricks pricing and capability updates in the backup registry. |
| tests/test_litellm/llms/databricks/test_databricks_cost_calculator.py | Adds comprehensive billing and registry tests; the previously reported missing type annotations are now present. |
| tests/test_litellm/integrations/test_anthropic_cache_control_hook.py | Verifies that advertising Databricks prompt caching does not enable automatic Anthropic cache-control injection. |
| tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py | Updates fallback generalization coverage for newly registered Claude model families. |
Reviews (6): Last reviewed commit: "test: separate gemini entries storing th..." | Re-trigger Greptile
PR overviewThis pull request updates Databricks model pricing so cached tokens are billed at cache-specific rates and adds missing Claude pricing. It also modifies the primary and backup model pricing maps. Two pricing issues remain open despite one issue having been addressed. Long-context Gemini Pro cache usage is underbilled, allowing users to consume more resources than proxy budgets reflect, while Sonnet 5 rates are applied before their effective date and can prematurely exhaust shared budgets. The tiered cache-pricing gap is the more significant remaining concern because it permits repeatable budget-control circumvention. Open issues (2)
Fixed/addressed: 1 · PR risk: 6/10 |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…ry window The introductory DBU rates run through 2026-08-31 and pricing carries no expiry date, so a static introductory entry would undercharge by a third from September 1 and let spend outrun enforced budgets. Ship the standard rates, which match Sonnet 4.5 and 4.6, and keep the introductory numbers in the entry notes. Also give the new cost calculator tests full type annotations.
| "databricks/databricks-claude-sonnet-5": { | ||
| "cache_creation_input_token_cost": 3.7499875e-06, | ||
| "cache_read_input_token_cost": 2.9999900000000006e-07, | ||
| "input_cost_per_token": 2.9999900000000002e-06, |
There was a problem hiding this comment.
Low: Premature rate increase enables shared-budget exhaustion
These standard rates do not take effect until September 1, 2026, but the calculator and reservation path consume this value immediately. Until then, a team member can make Sonnet 5 requests count at roughly 150% of their actual cost and prematurely exhaust shared team, key, or model budgets; retain the introductory rates until the transition date or add date-aware pricing. Apply the same correction to litellm/model_prices_and_context_window_backup.json.
There was a problem hiding this comment.
Your earlier thread asked for exactly these standard rates. No date-aware pricing exists, and undercharging past August 31 breaks budgets worse.
There was a problem hiding this comment.
Thanks for the detail. I can't automatically confirm this is safe to dismiss, so I'm leaving the thread open for a maintainer to make the call.
…h no cache pricing The shared cost calculator treats a missing cache rate as free, so routing Databricks through it billed cached tokens at zero on the 14 entries that publish no cache pricing. On a 10,000 token prompt with 8,000 cache reads that is $0.0010000 against the correct $0.0050001, a fivefold undercharge. Those entries now declare cache rates equal to their input rate, which is what a model with no caching discount should charge, and a test pins every priced Databricks entry to declaring cache rates so no future entry can regress into it. Also repoints the provider-neutral generalization test off an id the new Opus 5 entry now shadows, adds backup-to-main parity tests for the five new entries, pins that Databricks Claude is never auto-injected with cache control despite reporting caching support, and trims the Sonnet 5 pricing note, which is served on an unauthenticated route.
The new supports_prompt_caching assertion reads a capability this branch adds to the registry, so it only holds against the bundled map. CI leaves LITELLM_LOCAL_MODEL_COST_MAP unset and fetches main's copy, which lags the branch until merge, so the test failed there while passing locally. Use the local_model_cost_map fixture the repo already provides, matching what the other two test files in this change do.
The figures came out of a float product of the DBU rate and the dollar rate, so the map carried representation tails such as 2.9999900000000006e-07 where the derivation only means 2.99999e-07. Trim the 44 values this change adds or edits to the shortest literal that round-trips to the same figure, leaving every pre-existing value alone. The largest move is under 1e-15 relative, so no billed amount changes.
Cache rates were derived as ratios of the dollar input rate (1.25x write, 0.1x read) while input and output derive from the published DBU table times $0.070. Databricks publishes cache write and cache read DBU per model, and those are not exact multiples of the input DBU, so the two rules disagreed by up to 0.1 percent. Rewrites 43 cache literals across 31 entries to published_cache_DBU x $0.070. Skips databricks-gemini-2-5-pro and databricks-gemini-2-5-flash, whose input and output rates predate the current table by a 1.25x increase; their cache rates stay tied to their own input rate so each entry remains internally consistent. Replaces the ratio assertions with a test pinning the absolute published DBU figures for the five new models, and adds a test pinning the older-vintage exception. Corrects the metadata note on the five new entries, which claimed the reference-only *_dbu_cost_per_token fields drive cost calculation.
… published DBU databricks-claude-fable-5 was the only fable-5 entry in the registry declaring supports_vision false, and the only one of the five new entries to do so. Only the five new models were pinned against the published DBU table, so the 26 cache literals added to pre-existing entries were checked by nothing independent. Extend the table to all 33 entries carrying cache rates and assert both cache fields against it for the 31 that take the published rates, leaving the two older-vintage gemini-2-5 entries to their existing guard. Also widen the cache-declaration guard to both cache fields, and replace the single-model equals-input assertion with one that covers all 14 entries publishing no cache rates.
…tale The two gemini 2.5 entries price a factor of 1.25 under the published DBU table because the published figures exclude a 20% promotion that runs to 2027-01-31. The previous constant name and test called them an older vintage awaiting a refresh, which would have led a future reader to scale them up and overcharge. Pin the discount and the cache relationship instead.
Databricks documents databricks-claude-fable-5 as accepting text only, where every sibling Claude endpoint accepts text and image. An earlier commit flipped the flag to true on the reasoning that fable-5 was the only entry of its generation declaring false, which had it backwards: it is the only one because the endpoint really does refuse images. Advertising vision here would surface the model in capability filters and hand the caller a provider-side rejection.
…ng list The 20% promotion that runs to 2027-01-31 covers every gemini model, not just the 2.5 pair, so a constant naming two of them implied the other four were exempt. Six covered entries live in the registry: two store the discounted rate and four store list, which is a pre-existing overcharge this branch does not touch, since it only adds cache fields and derives them from each entry's own input rate. Name both groups for what they store, pin the expiry, and tighten the tolerance to 2e-4.
| }, | ||
| "databricks/databricks-gemini-2-5-pro": { | ||
| "cache_creation_input_token_cost": 1.24999e-06, | ||
| "cache_read_input_token_cost": 1.24999e-07, |
There was a problem hiding this comment.
Medium: Long-context cache usage is underbilled
Databricks increases Gemini Pro cache rates above 200k input tokens, but this entry only defines the short-context rate. Because generic_cost_per_token activates tiered cache pricing only when an input_cost_per_token_above_200k_tokens field exists, a user can repeatedly submit cached prompts above 200k tokens and be charged the lower rate against their proxy budget. The same issue affects databricks-gemini-3-1-pro and databricks-gemini-3-pro; add the input, output, cache-creation, and cache-read above-200k fields to both price maps.
There was a problem hiding this comment.
These entries never modeled 200k tiers; input and output share that pre-existing gap. A cache-rate fix is the wrong place to introduce tiered pricing.
There was a problem hiding this comment.
Thanks for the detail. I can't automatically confirm this is safe to dismiss, so I'm leaving the thread open for a maintainer to make the call.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 15e8a35. Configure here.
TLDR
Problem this solves:
How it solves it:
User Flow
Before: a developer tracking Databricks Claude spend pays full input price for every cached token, and sees no spend at all on the newest models
databricks/databricks-claude-sonnet-4-5and send POST https://litellm-domain/v1/chat/completions with a prompt long enough to be cached{"cost": 0.005999987}, the same amount an entirely uncached request of that size costsdatabricks/databricks-claude-opus-5and repeat, and https://litellm-domain/spend/calculate returns{"cost": 0.0}, so the request lands on https://litellm-domain/ui/?page=logs at $0 spendAfter: cache reads and cache writes bill at their own rates, and the newest models report real spend
databricks/databricks-claude-sonnet-4-5and send POST https://litellm-domain/v1/chat/completions with a prompt long enough to be cached{"cost": 0.004215001}, with cache reads and cache writes each billed at their own published ratedatabricks/databricks-claude-opus-5and repeat, and https://litellm-domain/spend/calculate returns{"cost": 0.007025025}, which is what https://litellm-domain/ui/?page=logs now shows for the requestRelevant issues
Linear ticket
Resolves LIT-5977
Pricing sources
Rates come from the published Databricks DBU table for proprietary foundation model serving, converted at the documented $0.070 per DBU that the existing entries already use.
Each dollar figure is that DBU rate multiplied by $0.070, written to the significant digits the multiplication yields. Databricks publishes rates like 71.429 DBU per million tokens rather than round numbers, so the dollar rates come out non-round as well: Opus 5 input is 71.429 x $0.070 = $5.00003 per million tokens, not an even $5.00. A literal such as
3.0002e-07is therefore the intended cache read rate for Sonnet 5, taken from its published 4.286 cache read DBU, and not a rounding artifact. The values are stored at exactly that precision, so none of them carry the trailing digits a raw float product would leave behindCache rates are derived exactly the way input and output are, from the published cache write and cache read DBU for that model, never as a ratio of the input rate. That distinction is worth stating because Databricks does not publish cache rates as clean multiples: Opus 5 lists 7.143 cache read DBU against 71.429 input DBU, giving $0.50001 per million tokens where a flat tenth of input would have given $0.500003. Of the 47 Databricks entries, 33 carry cache rates and the remaining 14 publish none at all and are covered two paragraphs below. Of those 33, 29 take their cache DBU straight from the published table, while
databricks-claude-3-7-sonnetanddatabricks-claude-sonnet-4-1are absent from it and instead inherit the Sonnet 4 family's rates, which they already match on an implied 42.857 input DBUTwo of those 33 deliberately sit below the published table.
databricks-gemini-2-5-proanddatabricks-gemini-2-5-flashcarry a 20% promotional discount that the published DBU figures exclude and that runs through 2027-01-31, which is exactly why their stored rates land a factor of 1.25 under the table. They are correct as they stand rather than stale, so this PR does not touch them. Their cache rates follow their own discounted input rate instead of the undiscounted published cache DBU, keeping each entry on a single price list. A test pins both the discount and the cache relationship, so whoever drops the 1.25x when the promotion ends has to move the cache rates with itThat promotion is marked on every Gemini row, not just the 2.5 pair, and six covered models have registry entries. The other four,
databricks-gemini-3-pro,databricks-gemini-3-1-pro,databricks-gemini-3-flashanddatabricks-gemini-3-1-flash-lite, store the full list rate and therefore bill about 25% above what Databricks actually charges. Those input and output literals predate this branch and it does not change them. The cache rates it adds for the four are derived from each entry's own stored input rate, which is what Databricks bills a Gemini cache write at, so every field on those entries stays on one price list and a later correction can move all of them together. The tests name the two groups separately for what they store rather than implying the four are exempt from the promotion, and the overcharge is tracked on its own ticketThe change is not uniformly a refund, and it is worth being explicit about the direction. Cache reads fall a long way, 9.7x on the request in the proof below, because they used to bill at the full input rate. Cache writes move the opposite way: they also used to fall back to the input rate, and they now bill the published write premium, 1.25x input on every Claude model here. A workload that writes the cache more often than it reads will see its reported spend go up, and the proof below shows both directions on the same pair of requests.
Sonnet 5 is listed at its standard rates rather than the introductory launch rates (28.571 input and 142.857 output DBU), which run only through 2026-08-31. Pricing entries carry no expiry date, so the choice is which direction to be wrong in. Standard rates overcharge Sonnet 5 by 50% for the nine days left in the introductory window, then become correct on their own. Introductory rates would read correctly for nine days and then undercharge by a third indefinitely, letting real spend outrun an enforced budget with no signal. The introductory numbers are kept in the entry's
metadata.notes.Opus 4.7 and Opus 4.8 are both priced from their own published rate of 71.429 input DBU, matching Opus 4.5 and 4.6. Neither had an entry before, so both fell back to the original Opus 4 at 214.286 DBU and billed $15 per million input tokens where Databricks charges $5. Correcting that drops their billed rate threefold, to $5 in and $25 out per million. It is a correction rather than a discount, but it is a visible decrease for anyone already calling either model. Opus 5, Sonnet 5 and Fable 5 had no fallback to land on at all and billed $0, so for those three the change is a rise from nothing to a real rate
The 14 Databricks models that publish no cache pricing at all, the Llama, Mixtral, Gemma, GPT OSS, MPT and embedding entries, get cache rates equal to their input rate. Routing them through the shared calculator otherwise bills a cached token at zero, because that calculator treats a missing cache rate as free, and a model with no caching discount should charge the ordinary input price instead. On a 10,000 token prompt with 8,000 cache reads this is the difference between $0.0050001 and $0.0010000, a fivefold undercharge, and a test now pins every priced Databricks entry to declaring cache rates so no future entry can regress into it.
Registry changes are additive and confined to
databricks/keys: no entry is removed and no existing rate is changed. Alongside the cache rates, 28 of the 42 existing entries newly declaresupports_prompt_caching, which they had left off despite the provider supporting it. An old-formula versus new-formula comparison over an uncached usage block came back identical for all 47 Databricks models, and all seven legacy endpoint aliases kept their existing behaviour.Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup, identical on both legs: two separate proxy processes each booted
litellm --config <cfg> --num_workers 2on two random free high ports, sharing one Postgres database and one Redis, matching the multi-pod shape customers run. Every request is sent through one instance and its spend is read back through the other.LITELLM_LOCAL_MODEL_COST_MAP=Trueso pricing comes from the checked-out registry. Clients are the OpenAI Python SDK for/v1/chat/completionsand/v1/responsesand the Anthropic Python SDK for/v1/messages. Cached cases send a large system block carryingcache_control: {"type": "ephemeral"}twice, so the second call is a real cache read against Databricks. Model deployments aredatabricks/databricks-claude-sonnet-4-5anddatabricks/databricks-claude-opus-5on a live Databricks workspace.Before (a44bb47)
Cached request on /v1/chat/completions
prompt_tokens 9895,cache_read_input_tokens 9882,completion_tokens 4, so only 13 prompt tokens were fresh9895 * 3.00/1M + 4 * 15.00/1M, so all 9882 cache-read tokens were billed at the full input rate. At the published cache-read rate the same request costs0.00306379759, so the bill is 9.7x too highUnpriced new model on /v1/chat/completions
databricks/databricks-claude-opus-5. It returns 200 with contentokand usageprompt_tokens 16,completion_tokens 4, so Databricks really served and really billed it0.0on a real billable requestCached request on /v1/messages
input_tokens 13,cache_read_input_tokens 9882,output_tokens 4Cached request on /v1/responses
input_tokens 9895,input_tokens_details.cached_tokens 9882,output_tokens 40.02974490113, because the cost calculation never looks at the cache-read splitAfter (0a869b7)
Commits since this hash (112224a, 9f1191e, 77cae92, 15e8a35) net to test-only changes: the head's tree differs from it solely in tests/test_litellm/llms/databricks/test_databricks_cost_calculator.py, so proxy behavior at the tip is identical to this proven run
Two proxy instances, each started with
--num_workers 2, sharing one Postgres and one Redis. Every case is sent through one instance and the spend read back through the other, so the numbers below survive a process boundary. Instance A is127.0.0.1:39346, instance B is127.0.0.1:51566.Cached request on /v1/chat/completions
cache_control: {"type": "ephemeral"}system block through A, then send the identical call again. The second response reportsprompt_tokens 10184,cache_read_input_tokens 10170,completion_tokens 414 * 2.99999/1M + 10170 * 0.30002/1M + 4 * 15.00002/1M, exact to the last digit, against0.03061189824under the old full-rate arithmetic, so 9.71x cheaper. The 14 text tokens areprompt_tokensminus the cache tokens, because Databricks reportsprompt_tokensinclusive of themmsg_bdrk_01VhKmwPEhWYxD5SFKhyWvqNbilled0.038239194840000004, exactly14 * 2.99999/1M + 10170 * 3.74997/1M + 4 * 15.00002/1M, so the write rate is live tooUnpriced new model on /v1/chat/completions
databricks/databricks-claude-opus-5through B. It returns 200 with usageprompt_tokens 22,completion_tokens 722 * 5.00003/1M + 7 * 25.00001/1M, non-zero where the same call previously billed nothing, and $5.00003/1M is exactly the published 71.429 input DBU at $0.070 per DBUCached request on /v1/messages
input_tokens 14,cache_read_input_tokens 10170,output_tokens 4Cached request on /v1/responses
input_tokens 10184,input_tokens_details.cached_tokens 10170Observations from the run, none of them caused or worsened by this PR:
spendstores one ULP belowcost_breakdown.total_coston the largest of the values, a float round-trip in that columntemperatureupstream, anddrop_paramsdoes not strip it/spend/logskeys on the provider message id, not theresp_...id the responses SDK hands backType
🐛 Bug Fix
🆕 New Feature
Caveats (if any)
metadata.noteswording about the DBU fieldsopus-5-1forhaiku-5-1, since the new entry makes the former mappeddbrx-instructandmeta-llama-3.1-70baliases stay unpriced, unchangedFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
15e8a35 passes /live-pr-risk
Note
Medium Risk
Changes affect reported customer spend and billing for all Databricks traffic, including large corrections for newly mapped Opus models and cache-heavy workloads; risk is mitigated by extensive tests but pricing JSON mistakes would propagate widely.
Overview
Databricks spend calculation now routes through
generic_cost_per_tokeninstead of multiplying rawprompt_tokensby the input rate. Legacy endpoint aliases (e.g.mixtral-8x7b-instruct-v0.1) are normalized via a small prefix map before registry lookup, so cache read/write usage fields can bill at their own rates.The model price registry (main + backup JSON) is updated in bulk for Databricks:
cache_creation_input_token_costandcache_read_input_token_coston existing entries,supports_prompt_cachingon many chat models, and new priced entries for Claude Opus 4.7/4.8/5, Sonnet 5, and Fable 5 at published DBU-derived dollar rates.Tests lock in cached vs uncached math, DBU conversion for new models, legacy alias resolution, promotional vs list Gemini rates, and that Databricks Claude models do not receive automatic
cache_controlinjection even when caching is marked supported.Reviewed by Cursor Bugbot for commit 15e8a35. Bugbot is set up for automated code reviews on this repo. Configure here.