This commit is contained in:
与世无争剑宇子 2026-10-19 19:53:24 +00:00 • committed by GitHub
commit e009ea4326
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 28 deletions

View file

@ -2678,7 +2678,10 @@ def _complete_custom_openai(
if extra_headers is not None and not use_base_llm_http_handler:
optional_params["extra_headers"] = extra_headers
if litellm.enable_preview_features and metadata is not None: # [PREVIEW] allow metadata to be passed to OPENAI
# OpenAI accepts request metadata independently of preview features. Other
# providers keep the existing preview gate because compatible providers may
# reject the field.
if metadata is not None and (custom_llm_provider == "openai" or litellm.enable_preview_features):
openai_metadata: Final = get_requester_metadata(metadata)
if openai_metadata is not None:
optional_params["metadata"] = openai_metadata

View file

@ -10751,10 +10751,12 @@ def get_requester_metadata(metadata: Mapping[str, object]):
return None
requester_metadata: Final = metadata.get("requester_metadata")
if isinstance(requester_metadata, dict):
cleaned_metadata = add_openai_metadata(requester_metadata)
if cleaned_metadata:
return cleaned_metadata
if "requester_metadata" in metadata:
# The proxy-populated requester snapshot is authoritative. Falling back
# to the outer dictionary would expose auth fields such as
# user_api_key_hash when the snapshot has no forwardable strings.
cleaned_metadata = add_openai_metadata(requester_metadata) if isinstance(requester_metadata, dict) else None
return cleaned_metadata if cleaned_metadata else None
cleaned_metadata = add_openai_metadata(metadata)
if cleaned_metadata:

View file

@ -7680,34 +7680,30 @@ def test_completion_novita_ai_dynamic_params(api_key):
"enable_preview_features",
[True, False],
)
def test_completion_openai_metadata(monkeypatch, enable_preview_features):
def test_completion_openai_metadata(
monkeypatch, enable_preview_features, respx_mock: respx.MockRouter
):
from openai import OpenAI
client = OpenAI()
litellm.set_verbose = True
monkeypatch.setattr(litellm, "enable_preview_features", enable_preview_features)
with patch.object(
client, "post", return_value=MagicMock()
) as mock_completion:
try:
resp = litellm.completion(
model="openai/gpt-3.5-turbo",
messages=[{"role": "user", "content": "Hello world"}],
metadata={"my-test-key": "my-test-value"},
client=client,
)
except Exception as e:
print(f"Error: {e}")
respx_mock.post("https://api.openai.com/v1/chat/completions").mock(
return_value=_mocked_openai_chat_response("gpt-3.5-turbo")
)
mock_completion.assert_called_once()
if enable_preview_features:
assert mock_completion.call_args.kwargs["body"]["metadata"] == {
"my-test-key": "my-test-value"
}
else:
assert "metadata" not in mock_completion.call_args.kwargs["body"]
litellm.completion(
model="openai/gpt-3.5-turbo",
messages=[{"role": "user", "content": "Hello world"}],
metadata={
"session_id": "abc-123",
"hidden_params": {"api_key": "should-not-leak"},
"non_string": 123,
},
client=client,
)
request_body = json.loads(respx_mock.calls[0].request.content)
assert request_body["metadata"] == {"session_id": "abc-123"}
AZURE_TTS_BASE: Final = "https://tts.example.azure.com"

View file

@ -57,6 +57,27 @@ def test_get_requester_metadata_returns_none_for_empty():
assert get_requester_metadata(metadata) is None
def test_get_requester_metadata_does_not_fall_back_to_sensitive_auth_metadata():
metadata = {
"requester_metadata": {
"hidden_params": "secret",
"non_string": 123,
},
"user_api_key": "sk-proxy-secret",
"user_api_key_hash": "opaque-custom-auth-token",
"user_api_key_auth": {"api_key": "sk-proxy-secret"},
"session_id": "should-not-leak",
}
assert get_requester_metadata(metadata) is None
def test_get_requester_metadata_uses_outer_metadata_without_requester_snapshot():
assert get_requester_metadata({"session_id": "abc-123", "non_string": 123}) == {
"session_id": "abc-123"
}
@patch("litellm.main.openai_chat_completions.completion")
def test_requester_metadata_forwarded_to_openai(mock_completion):
mock_completion.return_value = MagicMock()
@ -87,6 +108,27 @@ def test_requester_metadata_forwarded_to_openai(mock_completion):
assert sent_metadata == {"custom_meta_key": "value"}
@patch("litellm.main.openai_chat_completions.completion")
def test_sensitive_auth_metadata_is_not_forwarded_with_empty_requester_snapshot(
mock_completion, monkeypatch
):
mock_completion.return_value = MagicMock()
monkeypatch.setattr(litellm, "api_key", "sk-test")
litellm.completion(
model="gpt-4o",
messages=[{"role": "user", "content": "hi"}],
metadata={
"requester_metadata": {"non_string": 123},
"user_api_key": "sk-proxy-secret",
"user_api_key_hash": "opaque-custom-auth-token",
"session_id": "should-not-leak",
},
)
assert "metadata" not in mock_completion.call_args.kwargs["optional_params"]
def test_get_optional_params_with_allowed_openai_params():
"""
Test if use can dynamically pass in allowed_openai_params to override default behavior