mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(responses): honor allowed_openai_params for non-spec fields on native /v1/responses
This commit is contained in:
parent
daf22ec871
commit
0f8d8a9c29
3 changed files with 92 additions and 3 deletions
|
|
@ -1028,7 +1028,10 @@ def responses(
|
|||
local_vars["reasoning"] = _mapped
|
||||
# Get ResponsesAPIOptionalRequestParams with only valid parameters
|
||||
response_api_optional_params: ResponsesAPIOptionalRequestParams = (
|
||||
ResponsesAPIRequestUtils.get_requested_response_api_optional_param(local_vars)
|
||||
ResponsesAPIRequestUtils.get_requested_response_api_optional_param(
|
||||
local_vars,
|
||||
allowed_openai_params=allowed_openai_params,
|
||||
)
|
||||
)
|
||||
|
||||
_file_search_dispatch = _responses_try_dispatch_emulated_file_search(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import (
|
|||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Type,
|
||||
Union,
|
||||
cast,
|
||||
|
|
@ -185,19 +186,26 @@ class ResponsesAPIRequestUtils:
|
|||
@staticmethod
|
||||
def get_requested_response_api_optional_param(
|
||||
params: Dict[str, Any],
|
||||
allowed_openai_params: Sequence[str] | None = None,
|
||||
) -> ResponsesAPIOptionalRequestParams:
|
||||
"""
|
||||
Filter parameters to only include those defined in ResponsesAPIOptionalRequestParams.
|
||||
Filter parameters to only include those defined in ResponsesAPIOptionalRequestParams,
|
||||
plus any request/deployment level `allowed_openai_params` the caller opted into.
|
||||
|
||||
Args:
|
||||
params: Dictionary of parameters to filter
|
||||
allowed_openai_params: Extra request fields to keep even though they are not
|
||||
part of the Responses API spec
|
||||
|
||||
Returns:
|
||||
ResponsesAPIOptionalRequestParams instance with only the valid parameters
|
||||
"""
|
||||
from litellm.types.utils import all_litellm_params
|
||||
from litellm.utils import PreProcessNonDefaultParams
|
||||
|
||||
valid_keys = get_type_hints(ResponsesAPIOptionalRequestParams).keys()
|
||||
valid_keys = frozenset(get_type_hints(ResponsesAPIOptionalRequestParams)) | frozenset(
|
||||
param for param in allowed_openai_params or () if param not in all_litellm_params
|
||||
)
|
||||
custom_llm_provider = params.pop("custom_llm_provider", None)
|
||||
special_params = params.pop("kwargs", {})
|
||||
|
||||
|
|
|
|||
|
|
@ -246,6 +246,84 @@ async def test_aresponses_keeps_include_obfuscation_in_stream_options():
|
|||
assert request_body["stream_options"] == {"include_obfuscation": False}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aresponses_allowed_openai_params_forwards_extension_param():
|
||||
"""
|
||||
allowed_openai_params must preserve request fields that are not part of the
|
||||
Responses API spec, so OpenAI-compatible upstreams that require them still
|
||||
receive them. Regression test for https://github.com/BerriAI/litellm/issues/34926
|
||||
"""
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_post:
|
||||
mock_post.return_value = MockResponse(
|
||||
_minimal_responses_api_payload("resp_allowed_params_test", "gpt-5.5"), 200
|
||||
)
|
||||
|
||||
await litellm.aresponses(
|
||||
model="openai/gpt-5.5",
|
||||
api_key="fake-api-key",
|
||||
input="ping",
|
||||
allowed_openai_params=["client_metadata"],
|
||||
client_metadata={"probe": "keep-me"},
|
||||
)
|
||||
|
||||
mock_post.assert_called_once()
|
||||
post_kwargs = mock_post.call_args.kwargs
|
||||
request_body = post_kwargs["json"] if "json" in post_kwargs else json.loads(post_kwargs["data"])
|
||||
assert request_body["client_metadata"] == {"probe": "keep-me"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aresponses_drops_extension_param_without_allowlist():
|
||||
"""Without an allowlist, unknown extension params stay off the wire."""
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_post:
|
||||
mock_post.return_value = MockResponse(
|
||||
_minimal_responses_api_payload("resp_no_allowlist_test", "gpt-5.5"), 200
|
||||
)
|
||||
|
||||
await litellm.aresponses(
|
||||
model="openai/gpt-5.5",
|
||||
api_key="fake-api-key",
|
||||
input="ping",
|
||||
client_metadata={"probe": "keep-me"},
|
||||
)
|
||||
|
||||
mock_post.assert_called_once()
|
||||
post_kwargs = mock_post.call_args.kwargs
|
||||
request_body = post_kwargs["json"] if "json" in post_kwargs else json.loads(post_kwargs["data"])
|
||||
assert "client_metadata" not in request_body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aresponses_allowed_openai_params_cannot_leak_litellm_params():
|
||||
"""allowed_openai_params must never promote internal litellm params (e.g. credentials) into the request body."""
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_post:
|
||||
mock_post.return_value = MockResponse(
|
||||
_minimal_responses_api_payload("resp_no_credential_leak_test", "gpt-5.5"), 200
|
||||
)
|
||||
|
||||
await litellm.aresponses(
|
||||
model="openai/gpt-5.5",
|
||||
api_key="fake-api-key",
|
||||
input="ping",
|
||||
allowed_openai_params=["api_key", "api_base"],
|
||||
)
|
||||
|
||||
mock_post.assert_called_once()
|
||||
post_kwargs = mock_post.call_args.kwargs
|
||||
request_body = post_kwargs["json"] if "json" in post_kwargs else json.loads(post_kwargs["data"])
|
||||
assert "api_key" not in request_body
|
||||
assert "api_base" not in request_body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aresponses_request_level_drop_params_drops_bedrock_mantle_service_tier(
|
||||
monkeypatch,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue