diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 1b1e3c70110..6172f3a9158 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -1604,10 +1604,13 @@ async def add_litellm_data_to_request( continue data.pop(_internal_key, None) _reject_url_valued_destinations(data) - for _metadata_field in ("metadata", "litellm_metadata"): - if (_raw_metadata := data.get(_metadata_field)) is not None: - data.pop(_metadata_field) - data[_metadata_field] = _normalized_metadata_object(_metadata_field, _raw_metadata) + _raw_metadata_by_field: Final = { + _metadata_field: data.pop(_metadata_field) + for _metadata_field in ("metadata", "litellm_metadata") + if data.get(_metadata_field) is not None + } + for _metadata_field, _raw_metadata in _raw_metadata_by_field.items(): + data[_metadata_field] = _normalized_metadata_object(_metadata_field, _raw_metadata) # Strip spoofable auth metadata from user-supplied metadata dict _user_metadata = data.get("metadata") if isinstance(_user_metadata, dict): 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 f6b801d3122..d3b1e089489 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -464,6 +464,28 @@ async def test_add_litellm_data_to_request_rejects_non_object_metadata(field, va assert field not in data +@pytest.mark.asyncio +async def test_add_litellm_data_to_request_removes_every_invalid_metadata_field_before_raising(): + """When both fields are invalid, the raise for the first must not leave the + second invalid value in data, or failure-logging hooks that inspect the body + can crash on it and mask the 400 as a 500.""" + data = {"input_file_id": "file-abc", "metadata": "abc", "litellm_metadata": "xyz"} + + with pytest.raises(ProxyException) as exc_info: + await add_litellm_data_to_request( + data=data, + request=_batches_request_mock(), + user_api_key_dict=UserAPIKeyAuth(api_key="hashed-key"), + proxy_config=MagicMock(), + general_settings={}, + version="test-version", + ) + + assert exc_info.value.param == "metadata" + assert "metadata" not in data + assert "litellm_metadata" not in data + + @pytest.mark.asyncio async def test_add_litellm_data_to_request_parses_json_object_string_litellm_metadata(): data = {"input_file_id": "file-abc", "litellm_metadata": json.dumps({"cost_centre": "research"})}