fix: body litellm_metadata values overwritten on /v1/messages

This commit is contained in:
Sahil 2026-04-07 01:51:22 +05:30
parent d251238bd7
commit 4043104d43
2 changed files with 84 additions and 1 deletions

View file

@ -667,7 +667,11 @@ class LiteLLMProxyRequestSetup:
)
if isinstance(data[_metadata_variable_name], dict):
data[_metadata_variable_name].update(metadata_from_headers)
# Only inject header-derived values for keys NOT already set by the user
# in the request body. This ensures body values take priority over headers.
for key, value in metadata_from_headers.items():
if key not in data[_metadata_variable_name]:
data[_metadata_variable_name][key] = value
return data
@staticmethod

View file

@ -1912,3 +1912,82 @@ async def test_bearer_token_not_in_debug_logs():
f"Bearer token leaked in debug logs. "
f"Found token in log output:\n{log_output[:500]}"
)
class TestAddLitellmMetadataFromRequestHeaders:
"""Tests for fix: https://github.com/BerriAI/litellm/issues/24945
On /v1/messages, user-supplied litellm_metadata values (e.g. trace_id) were
silently overwritten by proxy-injected header values. Body values should win.
"""
def test_should_preserve_body_trace_id_over_header_on_messages_route(self):
"""Body trace_id must not be overwritten by x-litellm-trace-id header."""
data = {
"model": "anthropic/claude-3-5-sonnet",
"messages": [{"role": "user", "content": "hi"}],
"litellm_metadata": {"trace_id": "from-body"},
}
headers = {"x-litellm-trace-id": "from-header", "content-type": "application/json"}
result = LiteLLMProxyRequestSetup.add_litellm_metadata_from_request_headers(
headers=headers,
data=data,
_metadata_variable_name="litellm_metadata",
)
assert result["litellm_metadata"]["trace_id"] == "from-body"
def test_should_use_header_trace_id_as_fallback_when_body_has_no_trace_id(self):
"""When body doesn't set trace_id, the header value should still be injected."""
data = {
"model": "anthropic/claude-3-5-sonnet",
"messages": [{"role": "user", "content": "hi"}],
"litellm_metadata": {},
}
headers = {"x-litellm-trace-id": "from-header", "content-type": "application/json"}
result = LiteLLMProxyRequestSetup.add_litellm_metadata_from_request_headers(
headers=headers,
data=data,
_metadata_variable_name="litellm_metadata",
)
assert result["litellm_metadata"]["trace_id"] == "from-header"
assert result["litellm_metadata"]["session_id"] == "from-header"
@pytest.mark.asyncio
async def test_should_preserve_body_trace_id_in_full_pipeline_on_messages_route(self):
"""Body trace_id must survive the full add_litellm_data_to_request pipeline."""
from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
mock_request = MagicMock(spec=Request)
mock_request.url.path = "/v1/messages"
mock_request.url.__str__ = lambda self: "http://localhost:4000/v1/messages"
mock_request.method = "POST"
mock_request.query_params = {}
mock_request.headers = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-1234",
"x-litellm-trace-id": "from-header",
}
mock_request.client = MagicMock()
mock_request.client.host = "127.0.0.1"
data = {
"model": "anthropic/claude-3-5-sonnet",
"messages": [{"role": "user", "content": "hi"}],
"litellm_metadata": {"trace_id": "from-body"},
}
result = await add_litellm_data_to_request(
data=data,
request=mock_request,
user_api_key_dict=UserAPIKeyAuth(api_key="hashed-key", metadata={}, team_metadata={}),
proxy_config=MagicMock(),
general_settings={},
version="test-version",
)
assert result.get("litellm_metadata", {}).get("trace_id") == "from-body"