Merge pull request #41144 from BerriAI/litellm_responses_bridge_filters_unknown_params

fix(responses): filter bridged kwargs like the native Responses path
This commit is contained in:
Yassin Kortam 2026-09-15 11:32:38 -07:00 committed by GitHub
commit 41b5d47c71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 134 additions and 3 deletions

View file

@ -1,9 +1,10 @@
import asyncio
import contextvars
from collections.abc import Coroutine, Generator, Iterable, Mapping
from collections.abc import Coroutine, Generator, Iterable, Mapping, Sequence
from contextlib import contextmanager
from dataclasses import dataclass
from functools import partial
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, Literal, NoReturn, Optional, TypeAlias, cast
import httpx
@ -15,7 +16,7 @@ from litellm._logging import verbose_logger
from litellm.completion_extras.litellm_responses_transformation.transformation import (
LiteLLMResponsesTransformationHandler,
)
from litellm.constants import request_timeout
from litellm.constants import DEFAULT_CHAT_COMPLETION_PARAM_VALUES, request_timeout
from litellm.integrations.anthropic_cache_control_hook import CARRY_UNMATCHED_MESSAGE_POINTS
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.litellm_core_utils.core_helpers import normalize_drop_params
@ -52,6 +53,7 @@ from litellm.llms.openai.data_residency import infer_openai_data_residency
from litellm.secret_managers.main import get_secret_str
from litellm.types.responses.main import *
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import all_litellm_params
from litellm.utils import (
ProviderConfigManager,
client,
@ -408,6 +410,25 @@ def _bridges_to_chat_completions(
return responses_api_provider_config is None or use_chat_completions_api is True
def _bridge_kwargs(
kwargs: Mapping[str, object],
responses_api_provider_config: BaseResponsesAPIConfig | None,
allowed_openai_params: Sequence[str] | None,
) -> Mapping[str, object]:
if responses_api_provider_config is None:
return kwargs
forwarded_keys: Final = frozenset(
(
*litellm.OPENAI_CHAT_COMPLETION_PARAMS,
*DEFAULT_CHAT_COMPLETION_PARAM_VALUES,
*all_litellm_params,
*GenericLiteLLMParams.model_fields,
*(allowed_openai_params or ()),
)
)
return MappingProxyType({key: value for key, value in kwargs.items() if key in forwarded_keys})
_ResponsesCompatibilityFailure: TypeAlias = Literal["encrypted_task_unsupported"]
@ -1281,6 +1302,7 @@ def responses(
return _file_search_dispatch
if _bridges_to_chat_completions(responses_api_provider_config, use_chat_completions_api):
bridge_kwargs: Final = _bridge_kwargs(kwargs, responses_api_provider_config, allowed_openai_params)
return litellm_completion_transformation_handler.response_api_handler(
model=model,
input=input,
@ -1292,7 +1314,7 @@ def responses(
extra_body=extra_body,
timeout=timeout if timeout is not None else request_timeout,
allowed_openai_params=allowed_openai_params,
**kwargs,
**bridge_kwargs,
)
# Get optional parameters for the responses API

View file

@ -6,12 +6,14 @@ Includes file_search emulation: the flag must be forwarded on inner aresponses
calls so routed requests do not hit a custom api_base /v1/responses endpoint.
"""
import json
from importlib import import_module
from typing import Final
from unittest.mock import MagicMock, patch
import httpx
import pytest
import respx
import litellm
from litellm.llms.custom_httpx.http_handler import HTTPHandler
@ -189,6 +191,113 @@ class TestUseResponsesApiBridgeFlag:
"reasoning_effort"
]
@pytest.mark.parametrize(
("model", "upstream_url", "use_chat_completions_api", "allowed_openai_params", "expected_chat_template_kwargs"),
[
pytest.param(
"openai/my-custom-model",
"https://api.openai.com/v1/chat/completions",
True,
None,
None,
id="native-config-drops-unknown-param",
),
pytest.param(
"openai/my-custom-model",
"https://api.openai.com/v1/chat/completions",
True,
["chat_template_kwargs"],
{"thinking": True},
id="native-config-keeps-allowed-param",
),
pytest.param(
"together_ai/my-custom-model",
"https://api.together.ai/v1/chat/completions",
False,
None,
{"thinking": True},
id="no-native-config-keeps-passthrough",
),
],
)
def test_bridge_forwards_same_params_as_native_dispatch(
self,
model: str,
upstream_url: str,
use_chat_completions_api: bool,
allowed_openai_params: list[str] | None,
expected_chat_template_kwargs: dict[str, bool] | None,
respx_mock: respx.MockRouter,
):
upstream: Final = respx_mock.post(upstream_url).mock(
return_value=httpx.Response(
status_code=200,
json={
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "my-custom-model",
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"}
],
"usage": {"prompt_tokens": 9, "completion_tokens": 1, "total_tokens": 10},
},
)
)
response: Final = litellm.responses(
model=model,
input="Hello",
use_chat_completions_api=use_chat_completions_api,
allowed_openai_params=allowed_openai_params,
chat_template_kwargs={"thinking": True},
drop_params=True,
api_key="fake-provider-api-key",
num_retries=0,
)
assert upstream.call_count == 1
request_body: Final = json.loads(upstream.calls[0].request.read())
assert request_body.get("chat_template_kwargs") == expected_chat_template_kwargs
assert request_body["messages"] == [{"role": "user", "content": "Hello"}]
assert response.output[0].content[0].text == "Answer"
def test_bridge_keeps_deployment_credentials_while_dropping_unknown_params(self, respx_mock: respx.MockRouter):
upstream: Final = respx_mock.post(
"https://example-resource.openai.azure.com/openai/deployments/my-deployment/chat/completions",
params={"api-version": "2024-10-21"},
).mock(
return_value=httpx.Response(
status_code=200,
json={
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "my-deployment",
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"}
],
"usage": {"prompt_tokens": 9, "completion_tokens": 1, "total_tokens": 10},
},
)
)
litellm.responses(
model="azure/my-deployment",
input="Hello",
use_chat_completions_api=True,
api_base="https://example-resource.openai.azure.com",
api_version="2024-10-21",
azure_ad_token="fake-azure-ad-token",
chat_template_kwargs={"thinking": True},
num_retries=0,
)
assert upstream.call_count == 1
request: Final = upstream.calls[0].request
assert request.headers["authorization"] == "Bearer fake-azure-ad-token"
assert "chat_template_kwargs" not in json.loads(request.read())
@patch.object(import_module("litellm.responses.file_search.emulated_handler"), "_call_aresponses")
@patch.object(
import_module("litellm.responses.main").ProviderConfigManager, "get_provider_responses_api_config"