Check for existing issues
What happened?
What happened
On the OpenAI Responses API (/v1/responses) path, the total response_cost is computed correctly (cache-read and cache-write dollars are folded into prompt_tokens_cost), but the itemized StandardLoggingPayload.response_cost_breakdown fields cache_read_cost and cache_creation_cost are always None/null.
Any downstream consumer of the standard logging payload (S3, a data warehouse, cost dashboards) therefore cannot attribute spend to cache-read vs cache-write vs fresh input for OpenAI models — all prompt-side dollars appear only under input_cost. This is an attribution/observability gap; the grand total is unaffected.
Root cause
In litellm/cost_calculator.py, the breakdown-itemization block (v1.89.1, around lines 1605-1631) only derives the cache token counts from Anthropic-style top-level usage attributes:
_cr = getattr(cost_per_token_usage_object, "cache_read_input_tokens", None) \
or (cost_per_token_usage_object.model_extra or {}).get("cache_read_input_tokens")
_cc = getattr(cost_per_token_usage_object, "cache_creation_input_tokens", None) \
or (cost_per_token_usage_object.model_extra or {}).get("cache_creation_input_tokens")
if (_cr or _cc) and model:
_mi = litellm.get_model_info(model=model, custom_llm_provider=custom_llm_provider)
_cr_rate = _mi.get("cache_read_input_token_cost")
if _cr and _cr_rate is not None:
_cache_read_cost = float(_cr) * float(_cr_rate)
_cc_rate = _mi.get("cache_creation_input_token_cost")
if _cc and _cc_rate is not None:
_cache_creation_cost = float(_cc) * float(_cc_rate)
OpenAI's Responses API does NOT set cache_read_input_tokens / cache_creation_input_tokens. It reports cache tokens under usage.input_tokens_details:
- cache-read -> prompt_tokens_details.cached_tokens
- cache-write -> prompt_tokens_details.cache_write_tokens
So both _cr and _cc resolve to None, the pricing branch is skipped, and _store_cost_breakdown_in_logging_obj(...) is called with cache_read_cost=None, cache_creation_cost=None -> the fields serialize as null in the StandardLoggingPayload.
Note this is consistent with how the total is computed: _parse_prompt_tokens_details DOES read cached_tokens (and, with the cache-write mapping, cache_creation_tokens) from prompt_tokens_details, so the dollars are correctly in the total — they're just never surfaced in the itemized breakdown for OpenAI.
Expected behavior
For OpenAI (and any provider that reports cache tokens via prompt_tokens_details), the breakdown should itemize cache_read_cost and cache_creation_cost from those counts, using the same cache_read_input_token_cost / cache_creation_input_token_cost rates.
Suggested fix
Extend the itemization block to fall back to prompt_tokens_details when the Anthropic-style top-level keys are absent:
if _cr is None:
_cr = getattr(prompt_tokens_details, "cached_tokens", None)
if _cc is None:
_cc = getattr(prompt_tokens_details, "cache_write_tokens", None) \
or getattr(prompt_tokens_details, "cache_creation_tokens", None)
Semantics should match the existing Anthropic path: input_cost stays the FULL prompt-side cost, and cache_read_cost / cache_creation_cost are additive breakouts that overlap it (not a disjoint partition) — so the total is unchanged.
Environment
- litellm v1.89.1
- OpenAI Responses API (/v1/responses); models emitting input_tokens_details.{cached_tokens, cache_write_tokens} (e.g. GPT-5.x family)
Steps to Reproduce
-
Configure an OpenAI model with cache pricing in model_info:
- model_name: gpt-cache-test
litellm_params:
model: openai/gpt-5.1 # any OpenAI model that emits cache tokens
model_info:
input_cost_per_token: 0.000005
output_cost_per_token: 0.00003
cache_read_input_token_cost: 0.0000005
cache_creation_input_token_cost: 0.00000625
-
Send a Responses API request large enough to trigger a cache write, then an identical one to trigger a cache read. The provider usage will contain:
"input_tokens_details": { "cache_write_tokens": 4012, "cached_tokens": 0 } // write
"input_tokens_details": { "cache_write_tokens": 0, "cached_tokens": 4012 } // read
-
Inspect the StandardLoggingPayload / response_cost_breakdown:
- Actual: cache_read_cost = null, cache_creation_cost = null; all prompt-side spend in input_cost.
- Expected: cache_read_cost = 4012 * 0.0000005, cache_creation_cost = 4012 * 0.00000625, non-null.
- total_cost is correct in both cases.
Relevant log output
What part of LiteLLM is this about?
Proxy
What LiteLLM version are you on ?
1.89.1
Twitter / LinkedIn details
No response
Check for existing issues
What happened?
What happened
On the OpenAI Responses API (/v1/responses) path, the total response_cost is computed correctly (cache-read and cache-write dollars are folded into prompt_tokens_cost), but the itemized StandardLoggingPayload.response_cost_breakdown fields cache_read_cost and cache_creation_cost are always None/null.
Any downstream consumer of the standard logging payload (S3, a data warehouse, cost dashboards) therefore cannot attribute spend to cache-read vs cache-write vs fresh input for OpenAI models — all prompt-side dollars appear only under input_cost. This is an attribution/observability gap; the grand total is unaffected.
Root cause
In litellm/cost_calculator.py, the breakdown-itemization block (v1.89.1, around lines 1605-1631) only derives the cache token counts from Anthropic-style top-level usage attributes:
OpenAI's Responses API does NOT set cache_read_input_tokens / cache_creation_input_tokens. It reports cache tokens under usage.input_tokens_details:
So both _cr and _cc resolve to None, the pricing branch is skipped, and _store_cost_breakdown_in_logging_obj(...) is called with cache_read_cost=None, cache_creation_cost=None -> the fields serialize as null in the StandardLoggingPayload.
Note this is consistent with how the total is computed: _parse_prompt_tokens_details DOES read cached_tokens (and, with the cache-write mapping, cache_creation_tokens) from prompt_tokens_details, so the dollars are correctly in the total — they're just never surfaced in the itemized breakdown for OpenAI.
Expected behavior
For OpenAI (and any provider that reports cache tokens via prompt_tokens_details), the breakdown should itemize cache_read_cost and cache_creation_cost from those counts, using the same cache_read_input_token_cost / cache_creation_input_token_cost rates.
Suggested fix
Extend the itemization block to fall back to prompt_tokens_details when the Anthropic-style top-level keys are absent:
Semantics should match the existing Anthropic path: input_cost stays the FULL prompt-side cost, and cache_read_cost / cache_creation_cost are additive breakouts that overlap it (not a disjoint partition) — so the total is unchanged.
Environment
Steps to Reproduce
Configure an OpenAI model with cache pricing in model_info:
litellm_params:
model: openai/gpt-5.1 # any OpenAI model that emits cache tokens
model_info:
input_cost_per_token: 0.000005
output_cost_per_token: 0.00003
cache_read_input_token_cost: 0.0000005
cache_creation_input_token_cost: 0.00000625
Send a Responses API request large enough to trigger a cache write, then an identical one to trigger a cache read. The provider usage will contain:
"input_tokens_details": { "cache_write_tokens": 4012, "cached_tokens": 0 } // write
"input_tokens_details": { "cache_write_tokens": 0, "cached_tokens": 4012 } // read
Inspect the StandardLoggingPayload / response_cost_breakdown:
Relevant log output
What part of LiteLLM is this about?
Proxy
What LiteLLM version are you on ?
1.89.1
Twitter / LinkedIn details
No response