Skip to content

fix: 🔧 don't double-bill cached tokens in LiteLLM registry cost fallback - #113

Merged
nj-io merged 1 commit into
developfrom
fix/litellm-cost-cache
Jul 9, 2026
Merged

fix: 🔧 don't double-bill cached tokens in LiteLLM registry cost fallback#113
nj-io merged 1 commit into
developfrom
fix/litellm-cost-cache

Conversation

@nj-io

@nj-io nj-io commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Code-review follow-up to #111. Cached tokens were double-billed in the LiteLLM registry cost fallback: OpenAI-style prompt_tokens includes cached tokens, but catalog.estimate_cost_cents (Anthropic-shaped) expects input to exclude cache — so the cached portion was charged at the full input rate and again at 0.1× (~5.7× over for a 90%-cached call).

Bounded impact: only the registry cost estimate (unmapped models like GLM-5.2 when LiteLLM reports no real cost), which feeds logged/displayed cost — never pipeline control flow (rate-limiting gates on evaluation count). Anthropic/claude-cli paths unaffected.

  • litellm_client.py: normalize input_tokens to non-cached (max(prompt_tokens - cache_read, 0)) so cost and UsageLog match the Anthropic-style disjoint accounting.
  • Guard the prose-fallback .strip() against non-str content; fix a misleading tool_choice comment.
  • New test_cached_tokens_not_double_billed (100k prompt / 90k cached on GLM-5.2 → 1.767¢, was ~10¢).

Verified: ruff + mypy clean; litellm test suite (12) passes locally.

🤖 Generated with Claude Code

Code-review follow-up. OpenAI-style `prompt_tokens` INCLUDES cached tokens, but
`catalog.estimate_cost_cents` (written for the Anthropic SDK) expects disjoint
buckets where input excludes cache. Passing full `prompt_tokens` as
`input_tokens` charged the cached portion at the full input rate AND again at
the 0.1x cache-read rate (~5.7x over-count for a 90%-cached call).

Only affects the registry cost estimate (unmapped models like GLM-5.2 when
LiteLLM reports no real cost) — inflates logged/displayed cost, never pipeline
control flow. Anthropic/claude-cli paths unaffected.

- litellm_client.py: normalize input_tokens to non-cached
  (max(prompt_tokens - cache_read, 0)) so cost + UsageLog match the
  Anthropic-style disjoint accounting.
- guard the prose-fallback .strip() against non-str content; correct the
  tool_choice comment.
- add test_cached_tokens_not_double_billed (100k prompt / 90k cached on
  GLM-5.2 -> 1.767c, was ~10c).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nj-io
nj-io merged commit f98549b into develop Jul 9, 2026
6 checks passed
@nj-io
nj-io deleted the fix/litellm-cost-cache branch July 9, 2026 03:39
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.

1 participant