From 0d0877c73a6fdbae397adfb433eefc09198a89a7 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:57:08 +0000 Subject: [PATCH] fix(cost): skip a chat usage.cost that already holds litellm's stamped bill and Responses API objects --- litellm/litellm_core_utils/core_helpers.py | 19 +++++++++++++------ .../litellm_core_utils/test_core_helpers.py | 17 +++++++++++++++++ .../test_streaming_handler.py | 6 +++--- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index 58ff7c2218b..564a0ee835c 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -1021,16 +1021,23 @@ OWN_REPORTED_COST_READER_PROVIDERS: Final = frozenset({"xai", "perplexity", "ope def _carries_litellm_bill(response: object) -> bool: - """A response the stream builder already priced carries litellm's own bill in ``usage.cost`` too.""" - hidden: Final[object] = getattr(response, "_hidden_params", None) - stamped: Final = hidden.get("response_cost") if isinstance(hidden, Mapping) else None + hidden: Final = as_str_mapping(getattr(response, "_hidden_params", None)) + stamped: Final[object] = None if hidden is None else hidden.get("response_cost") return isinstance(stamped, (int, float)) and not isinstance(stamped, bool) def billed_usage_cost(response: object, custom_llm_provider: str | None) -> float | None: - """The charge an OpenAI-compatible upstream reports in ``usage.cost``, billed unless the provider has its own - reader or the number is litellm's own bill written back onto the usage.""" - if custom_llm_provider in OWN_REPORTED_COST_READER_PROVIDERS or _carries_litellm_bill(response): + """The charge an OpenAI-compatible upstream reports in a chat completion's ``usage.cost``. Skipped once the stream + builder stamped litellm's own bill onto the response (its ``usage.cost`` then holds that bill), and for Responses + API objects, whose terminal ``usage.cost`` litellm also rewrites and whose upstream figure is billed through + ``set_response_cost_in_hidden_params``.""" + from litellm.types.utils import ModelResponse + + if ( + custom_llm_provider in OWN_REPORTED_COST_READER_PROVIDERS + or not isinstance(response, ModelResponse) + or _carries_litellm_bill(response) + ): return None cost: Final = provider_reported_usage_cost(_usage_field(response, "usage"), custom_llm_provider) return cost if cost is not None and math.isfinite(cost) and cost >= 0 else None diff --git a/tests/unit/litellm_core_utils/test_core_helpers.py b/tests/unit/litellm_core_utils/test_core_helpers.py index a4a3ae285b1..31f04da315f 100644 --- a/tests/unit/litellm_core_utils/test_core_helpers.py +++ b/tests/unit/litellm_core_utils/test_core_helpers.py @@ -803,3 +803,20 @@ class TestReceiveStampSurvivesASecondMetadataBucket: } assert get_litellm_metadata_from_kwargs(kwargs)["litellm_received_at"] is own + + +def test_billed_usage_cost_bills_any_chat_upstream_without_its_own_reader() -> None: + from types import SimpleNamespace + + from litellm.litellm_core_utils.core_helpers import billed_usage_cost + from litellm.types.utils import ModelResponse, Usage + + usage = Usage(prompt_tokens=100, completion_tokens=20, total_tokens=120, cost=0.037) + chat = ModelResponse(usage=usage) + assert billed_usage_cost(chat, "openai") == 0.037 + assert billed_usage_cost(chat, "openrouter") == 0.037 + assert billed_usage_cost(chat, "xai") is None + assert billed_usage_cost(SimpleNamespace(usage=usage), "openai") is None + stamped = ModelResponse(usage=usage) + stamped._hidden_params["response_cost"] = 0.037 + assert billed_usage_cost(stamped, "openai") is None diff --git a/tests/unit/litellm_core_utils/test_streaming_handler.py b/tests/unit/litellm_core_utils/test_streaming_handler.py index f4502937fce..41e8c50fa85 100644 --- a/tests/unit/litellm_core_utils/test_streaming_handler.py +++ b/tests/unit/litellm_core_utils/test_streaming_handler.py @@ -2659,7 +2659,7 @@ def test_perplexity_streaming_dict_cost_bills_through_its_own_calculator(): ) == pytest.approx(0.00503) -def test_openai_compatible_streaming_cost_is_priced_from_the_cost_map(): +def test_openai_compatible_streaming_reported_cost_is_billed_instead_of_the_cost_map(): import litellm from litellm.cost_calculator import ( get_response_cost_from_hidden_params, @@ -2681,7 +2681,7 @@ def test_openai_compatible_streaming_cost_is_priced_from_the_cost_map(): id="chatcmpl-openai-compatible", model=model, choices=[], - usage=Usage(completion_tokens=5, prompt_tokens=10, total_tokens=15, cost=3_144_000), + usage=Usage(completion_tokens=5, prompt_tokens=10, total_tokens=15, cost=0.037), ) CustomStreamWrapper._propagate_usage_cost_to_hidden_params(complete_response, "openai") @@ -2693,7 +2693,7 @@ def test_openai_compatible_streaming_cost_is_priced_from_the_cost_map(): custom_llm_provider="openai", call_type="completion", optional_params={}, - ) == pytest.approx(2e-5) + ) == pytest.approx(0.037) def test_xai_streaming_reported_cost_still_takes_the_margin(monkeypatch):