Skip to content

[Bug]: cost_breakdown.cache_read_cost / cache_creation_cost always null for OpenAI Responses API (only read from Anthropic-style top-level usage keys) #34309

Description

@sarveshb-p72

Check for existing issues

  • I have searched the existing issues and checked that my issue is not a duplicate.

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

  1. 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
  2. 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

  3. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions