From 0c116d9122f4fde6a925614ef551fdaed5c2176b Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Sat, 19 Sep 2026 23:22:14 +0300 Subject: [PATCH 1/3] 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, From 59c53898da221f3f7617cbe5ee10314a593dc366 Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Wed, 23 Sep 2026 11:33:56 +0300 Subject: [PATCH 2/3] \fix(tests): drop unnecessary docstring per repo comment policy" --- tests/test_litellm/litellm_core_utils/test_litellm_logging.py | 2 -- 1 file changed, 2 deletions(-) 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 2f9256b16be..ceff4f69ff8 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -4171,8 +4171,6 @@ def test_process_hidden_params_uses_hidden_params_cost_after_failure_handler_zer 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 e4da6304f236b82eba11fb2adb9970365d27c84c Mon Sep 17 00:00:00 2001 From: onatozmenn Date: Wed, 23 Sep 2026 11:55:52 +0300 Subject: [PATCH 3/3] \fix(logging): keep uncomputed hidden cost silent while falling through to calculator" --- litellm/litellm_core_utils/litellm_logging.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index dc4324470a3..18ce456dd14 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -2405,7 +2405,16 @@ class Logging(LiteLLMLoggingBaseClass): # Do not preserve 0 from failure_handler on intermediate router retries. pass else: - self.model_call_details["response_cost"] = self._response_cost_calculator(result=logging_result) + if "response_cost" in hidden_params: + warned = self.zero_cost_warned + self.zero_cost_warned = True + try: + self.model_call_details["response_cost"] = self._response_cost_calculator(result=logging_result) + finally: + self.model_call_details["zero_cost_diagnostic"] = None + self.zero_cost_warned = warned + else: + self.model_call_details["response_cost"] = self._response_cost_calculator(result=logging_result) if not build_logging_payload: return