From 0c116d9122f4fde6a925614ef551fdaed5c2176b Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Sat, 19 Sep 2026 23:22:14 +0300 Subject: [PATCH] fix(logging): fall through to cost calculator on None hidden response_cost --- litellm/litellm_core_utils/litellm_logging.py | 2 +- .../test_litellm_logging.py | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 8e28a0d543d..dc4324470a3 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -2396,7 +2396,7 @@ class Logging(LiteLLMLoggingBaseClass): if self.model_call_details.get("cache_hit") is True: self.model_call_details["response_cost"] = 0.0 - elif "response_cost" in hidden_params: + elif hidden_params.get("response_cost") is not None: self.model_call_details["response_cost"] = hidden_params["response_cost"] self._record_zero_cost_diagnostic(logging_result, hidden_params["response_cost"]) elif (existing_cost := self.model_call_details.get("response_cost")) is not None and existing_cost != 0: diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 5bdc0e9f8b8..2f9256b16be 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -4170,6 +4170,41 @@ def test_process_hidden_params_uses_hidden_params_cost_after_failure_handler_zer assert slo.get("response_cost") == passthrough_cost +def test_process_hidden_params_falls_through_on_none_response_cost(): + """A present-but-None response_cost in _hidden_params must not be copied + as the cost; it falls through to the calculator (issue #41976).""" + from datetime import datetime + + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.types.utils import ModelResponse, Usage + + logging_obj = LiteLLMLoggingObj( + model="openai/gpt-4o-mini", + messages=[{"role": "user", "content": "hi"}], + stream=False, + call_type="responses", + start_time=datetime.now(), + litellm_call_id="test-none-hidden-cost", + function_id="test-none-hidden-cost", + ) + logging_obj.model_call_details["litellm_params"] = {"model": "openai/gpt-4o-mini"} + logging_obj.optional_params = {} + + result = ModelResponse( + id="success", + choices=[{"message": {"role": "assistant", "content": "ok"}}], + usage=Usage(prompt_tokens=100, completion_tokens=10, total_tokens=110), + ) + result._hidden_params = {"response_cost": None} + + logging_obj._process_hidden_params_and_response_cost(result, datetime.now(), datetime.now()) + + cost = logging_obj.model_call_details.get("response_cost") + assert cost is not None and cost > 0 + slo = logging_obj.model_call_details.get("standard_logging_object") or {} + assert slo.get("response_cost", 0) > 0 + + def test_function_setup_litellm_metadata_populates_metadata(): """ Test that function_setup() properly handles litellm_metadata (used by /v1/messages,