From e4b9584ea65ce6b0cd4d157553cd799d54ba0904 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 01:46:44 +0000 Subject: [PATCH] fix(cost): stamp the bill on a plain reply only where the upstream reported usage.cost A reply whose upstream sent no usage.cost keeps its usage untouched, as on main, so SDK callers comparing the usage dict to the upstream's see no added key and logging payloads gain no additional_usage_values.cost --- .../llm_response_utils/response_metadata.py | 5 ++-- tests/unit/test_cost_calculator.py | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/llm_response_utils/response_metadata.py b/litellm/litellm_core_utils/llm_response_utils/response_metadata.py index 4379b937394..9a548018bb4 100644 --- a/litellm/litellm_core_utils/llm_response_utils/response_metadata.py +++ b/litellm/litellm_core_utils/llm_response_utils/response_metadata.py @@ -185,11 +185,12 @@ class ResponseMetadata: self._update_hidden_params(new_params) def _stamp_billed_usage_cost(self, response_cost: object) -> None: - """A client reads what litellm billed from usage.cost, streamed or not, priced deployment or not.""" + """A usage.cost the upstream reported becomes what litellm billed, so a client and the spend log agree on a + priced deployment; a reply without one keeps its usage as the upstream sent it.""" if not isinstance(self.result, (ModelResponse, ResponsesAPIResponse)): return usage: Final = getattr(self.result, "usage", None) - if not isinstance(usage, (Usage, ResponseAPIUsage)): + if not isinstance(usage, (Usage, ResponseAPIUsage)) or getattr(usage, "cost", None) is None: return stamp_billable_usage_cost(self.result, usage, response_cost) diff --git a/tests/unit/test_cost_calculator.py b/tests/unit/test_cost_calculator.py index 83724d7a2fc..c4266ec3e75 100644 --- a/tests/unit/test_cost_calculator.py +++ b/tests/unit/test_cost_calculator.py @@ -10787,6 +10787,33 @@ def test_the_client_reads_what_litellm_billed_from_usage_cost_on_every_route( assert logging_obj.response_cost_calculator(result=response, ignore_stamped_cost=True) == pytest.approx(bill) +def test_a_plain_reply_without_an_upstream_usage_cost_keeps_none_after_pricing(monkeypatch: pytest.MonkeyPatch) -> None: + """Clients comparing the usage dict to the upstream's byte for byte must not find a cost litellm added.""" + monkeypatch.setitem( + litellm.model_cost, + _PRICED_DEPLOYMENT_ID, + {"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": 2e-6, "output_cost_per_token": 6e-6}, + ) + response: Final = ModelResponse( + id="chatcmpl-unreported", + model="reported-cost-fixture", + choices=[Choices(index=0, message=Message(role="assistant", content="hi"), finish_reason="stop")], + usage=Usage(prompt_tokens=100, completion_tokens=20, total_tokens=120), + ) + + update_response_metadata( + result=response, + logging_obj=_priced_logging(False, "completion"), + model=_REPORTED_COST_FIXTURE_MODEL, + kwargs={}, + start_time=datetime.datetime(2026, 1, 1), + end_time=datetime.datetime(2026, 1, 1, 0, 0, 1), + ) + + assert response.usage is not None and getattr(response.usage, "cost", None) is None + assert response._hidden_params["response_cost"] == pytest.approx(100 * 2e-6 + 20 * 6e-6) + + def test_provider_reported_usage_cost_splits_input_and_output_by_the_token_math_ratio( monkeypatch: pytest.MonkeyPatch, ) -> None: