Skip to content

fix: [AIPLAT-1489]: stop double-counting LiteLLM cache tokens as gen_ai input - #22

Open
shreyas-n-harness wants to merge 3 commits into
mainfrom
AIPLAT-1489/litellm-cache-exclusive-input-tokens
Open

fix: [AIPLAT-1489]: stop double-counting LiteLLM cache tokens as gen_ai input#22
shreyas-n-harness wants to merge 3 commits into
mainfrom
AIPLAT-1489/litellm-cache-exclusive-input-tokens

Conversation

@shreyas-n-harness

Copy link
Copy Markdown
Collaborator

Summary

  • LiteLLM prompt_tokens is cache-inclusive. The LiteLLM instrumentor copied it to gen_ai.usage.input_tokens and set gen_ai.usage.cache_read.input_tokens / cache_creation.input_tokens, so GenAI traces inflated input the same way LMS Prometheus llm_tokens{tokenType=request} did.
  • _set_response_attributes now reports uncached input: max(prompt_tokens - cache_read - cache_creation, 0). Stream and non-stream both use this helper.
  • Anthropic-shaped input_tokens (no prompt_tokens) is already cache-exclusive; those spans are not subtracted.
  • LiteLLM response.usage is not mutated, so callers that price with cost_per_token() still see inclusive prompt_tokens.

Test plan

  • pytest test/instrumentation/litellm/litellm_instrumentation_test.py
  • Cached completion: gen_ai.usage.input_tokens is exclusive; cache series still present; total_tokens unchanged (LiteLLM total).
  • Anthropic-shaped usage: input_tokens left as-is when prompt_tokens is absent.
  • Embeddings / no-cache chat: input_tokens still equals prompt_tokens.

Made with Cursor

shreyas-n-harness and others added 3 commits September 2, 2026 12:16
…r LiteLLM

LiteLLM prompt_tokens is cache-inclusive. Copying it onto gen_ai.usage.input_tokens while also exporting cache read/write double-counted prompt cache against GCP-style disjoint buckets. Anthropic-shaped input_tokens is already exclusive and is left unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Current litellm + pydantic cannot rebuild Message on 3.10, which broke CI. Use a SimpleNamespace fake for patched completions and skip tests that call real completion/mock_response on 3.10.

Co-authored-by: Cursor <cursoragent@cursor.com>
if input_tokens is None:
cache_read = _get_value(usage, "cache_read_input_tokens")
if cache_read is None:
cache_read = _get_value(prompt_details, "cached_tokens")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I believe this should be handled at backend. SDK should generate the info that is needed.

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