diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index d8e70d3b125..09ab110bf86 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -2404,7 +2404,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: @@ -2413,7 +2413,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 diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index 4b25da2ff79..578fdb36bfc 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -4296,6 +4296,39 @@ 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(): + 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,