diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 6e5dcb0303d..aa8d23a8d98 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -230,7 +230,9 @@ def _get_spend_logs_metadata( BATCH_COST_REQUEST_ID_SUFFIX: Final = "_batch_cost" -def get_provider_response_id(response_obj: Mapping[str, object], kwargs: Mapping[str, object]) -> str | None: +def get_provider_response_id( + response_obj: Mapping[str, object], kwargs: Mapping[str, object], litellm_call_id: str | None +) -> str | None: """The id the provider minted for this response: the response's own, else the one the standard logging payload resolved. Never the proxy's call id, which is not a provider identity.""" standard_logging_payload: Final = kwargs.get("standard_logging_object") @@ -242,7 +244,7 @@ def get_provider_response_id(response_obj: Mapping[str, object], kwargs: Mapping ( candidate for candidate in candidate_ids - if isinstance(candidate, str) and candidate and candidate != kwargs.get("litellm_call_id") + if isinstance(candidate, str) and candidate and candidate != litellm_call_id ), None, ) @@ -379,6 +381,10 @@ def get_logging_payload(kwargs, response_obj, start_time, end_time) -> SpendLogs response_obj = {"result": str(response_obj)} # standardize this function to be used across, s3, dynamoDB, langfuse logging litellm_params: Final = kwargs.get("litellm_params", {}) + litellm_call_id: Final = cast( + str | None, + kwargs.get("litellm_call_id") or litellm_params.get("litellm_call_id"), + ) metadata: Final = get_litellm_metadata_from_kwargs(kwargs) completion_start_time: Final = kwargs.get("completion_start_time", end_time) call_type: Final = kwargs.get("call_type") @@ -412,7 +418,7 @@ def get_logging_payload(kwargs, response_obj, start_time, end_time) -> SpendLogs usage = _combined_usage.model_dump() id = get_spend_logs_id(call_type or "acompletion", response_obj_dict, kwargs) - provider_response_id: Final = get_provider_response_id(response_obj_dict, kwargs) + provider_response_id: Final = get_provider_response_id(response_obj_dict, kwargs, litellm_call_id) standard_logging_payload: Final = cast(StandardLoggingPayload | None, kwargs.get("standard_logging_object", None)) end_user_id = get_end_user_id_for_cost_tracking(litellm_params) @@ -484,10 +490,6 @@ def get_logging_payload(kwargs, response_obj, start_time, end_time) -> SpendLogs if rejected_as_unknown_model or failed_with_prompt_shaped_model else resolved_model ) - litellm_call_id: Final = cast( - str | None, - kwargs.get("litellm_call_id") or litellm_params.get("litellm_call_id"), - ) # clean up litellm metadata clean_metadata = _get_spend_logs_metadata( diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py index e3eb08792fe..c765a961617 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py @@ -1425,6 +1425,23 @@ def test_get_logging_payload_falls_back_to_the_standard_logging_payload_response assert payload["request_id"] == (standard_logging_id or "call-1") +def test_get_logging_payload_recognises_the_call_id_the_row_itself_resolves(): + """The row resolves its call id from litellm_params when kwargs carry none at the top level; + a response id equal to that call id is the proxy's own identity, so metadata.response_id stays empty.""" + payload = get_logging_payload( + kwargs={ + "model": "gpt-4o-mini", + "litellm_params": {"litellm_call_id": "call-1", "metadata": {"user_api_key": "test-key"}}, + }, + response_obj={"id": "call-1", "choices": []}, + start_time=datetime.datetime.now(timezone.utc), + end_time=datetime.datetime.now(timezone.utc), + ) + + assert payload["litellm_call_id"] == "call-1" + assert json.loads(payload["metadata"])["response_id"] is None + + @patch("litellm.proxy.proxy_server.master_key", None) @patch("litellm.proxy.proxy_server.general_settings", {}) def test_get_logging_payload_includes_overhead_in_spend_logs_metadata():