mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-26 06:03:19 +00:00
Merge be110a0b68 into 2e427c6247
This commit is contained in:
commit
e009ea4326
4 changed files with 71 additions and 28 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue