diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index fc81f07c7f2..a1ed2287e7c 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -121,7 +121,7 @@ def _trace_id_from_otel_span(span: "OtelSpan | None") -> str | None: def add_otel_trace_id_to_request( data: dict[str, object], _metadata_variable_name: str, parent_otel_span: "OtelSpan | None" ) -> None: - if "litellm_trace_id" in data: + if data.get("litellm_trace_id"): return metadata: Final = data.get(_metadata_variable_name) if isinstance(metadata, dict) and metadata.get("trace_id"): @@ -131,7 +131,7 @@ def add_otel_trace_id_to_request( return data["litellm_trace_id"] = trace_id # rebind-ok: data is an out-param if isinstance(metadata, dict): - metadata["trace_id"] = trace_id + metadata["trace_id"] = trace_id # rebind-ok: metadata is the request's own out-param dict def _session_id_from_baggage(baggage: str) -> str | None: diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py index 81ec555b59c..1573845b8e3 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -3605,6 +3605,23 @@ async def test_add_litellm_data_to_request_otel_span_does_not_override_caller_tr assert from_body["metadata"]["trace_id"] == "body-trace" +@pytest.mark.asyncio +@pytest.mark.parametrize("empty_trace_id", [None, ""]) +async def test_add_litellm_data_to_request_otel_span_fills_empty_body_trace_id(empty_trace_id): + """A serialized-but-empty litellm_trace_id in the body (null or "") carries + no identity, so it must not block the OTel server span fallback.""" + otel_trace_id = 0x4BF92F3577B34DA6A3CE929D0E0E4736 + data = await add_litellm_data_to_request( + data={"model": "gpt-5.6", "litellm_trace_id": empty_trace_id}, + request=_request_mock_without_trace_headers(), + user_api_key_dict=UserAPIKeyAuth(api_key="hashed-key", parent_otel_span=_otel_span_with_trace_id(otel_trace_id)), + proxy_config=MagicMock(), + general_settings={}, + ) + assert data["litellm_trace_id"] == format(otel_trace_id, "032x") + assert data["metadata"]["trace_id"] == format(otel_trace_id, "032x") + + @pytest.mark.asyncio @pytest.mark.parametrize("parent_otel_span", [None, "invalid_span", "not_a_span"]) async def test_add_litellm_data_to_request_no_trace_id_without_valid_otel_span(parent_otel_span):