fix(openai): take the early Responses route decision on the request minus additional_drop_params

Filter the keys named in additional_drop_params out of the top level and extra_body before the hoisted
bridge check so a dropped reasoning_effort cannot move a chat-only custom endpoint onto /responses

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
mateo 2026-10-31 23:03:03 +00:00
parent 846907a3ff
commit 4ffb145435
4 changed files with 97 additions and 2 deletions

View file

@ -1315,6 +1315,7 @@ from litellm.responses.bridge_check import ( # noqa: E402 # re-export at the o
_resolve_openai_api_base as _resolve_openai_api_base, # pyright: ignore[reportPrivateUsage] # noqa: PLC0414 # re-export of moved helper
)
from litellm.responses.bridge_check import (
bridge_check_request_params,
bridge_reasoning_effort,
keep_registered_mode,
str_keyed_mapping,
@ -6045,13 +6046,16 @@ def completion(
if dynamic_api_key is not None:
api_key = dynamic_api_key
if responses_api_model_info.get("mode") != "responses":
route_request_params: Final = bridge_check_request_params(routing_kwargs)
responses_api_model_info, model = responses_api_bridge_check(
model=model,
custom_llm_provider=custom_llm_provider,
web_search_options=web_search_options,
tools=tools,
reasoning_effort=bridge_reasoning_effort(resolved_reasoning_effort, routing_kwargs.get("extra_body")),
reasoning_summary=peek_reasoning_summary_aliases(routing_kwargs),
reasoning_effort=bridge_reasoning_effort(
resolved_reasoning_effort, route_request_params.get("extra_body")
),
reasoning_summary=peek_reasoning_summary_aliases(route_request_params),
api_base=api_base,
deployment_model_info=deployment_model_info,
)

View file

@ -102,6 +102,24 @@ def str_keyed_mapping(value: object) -> Mapping[str, object] | None:
)
def bridge_check_request_params(request_params: Mapping[str, object]) -> Mapping[str, object]:
"""The request as ``get_optional_params`` will hand it to the provider: every key named in
``additional_drop_params`` removed at the top level and inside ``extra_body``, so the Responses route
decision taken before that call reads the same effort and summary alias the parsed params will carry."""
raw_dropped: Final = request_params.get("additional_drop_params")
dropped_params: Final = (
cast(Sequence[object], raw_dropped) # cast-ok: isinstance only narrows to Sequence[Unknown]
if isinstance(raw_dropped, Sequence) and not isinstance(raw_dropped, str)
else ()
)
dropped: Final = frozenset(param for param in dropped_params if isinstance(param, str))
kept: Final = {key: value for key, value in request_params.items() if key not in dropped}
extra_body: Final = str_keyed_mapping(kept.get("extra_body"))
if extra_body is None:
return kept
return {**kept, "extra_body": {key: value for key, value in extra_body.items() if key not in dropped}}
def bridge_reasoning_effort(
reasoning_effort: str | Mapping[str, object] | None, extra_body: object
) -> str | Mapping[str, object] | None:

View file

@ -5,6 +5,7 @@ import pytest
from integration._support.client import Gateway
from integration._support.provider import SharedProvider
from integration.translation.case import TranslationTestCase
from integration.translation.chat_completions.bases.openai import GPT_5_6_SOL_TEST_CASE as GPT_5_6_SOL_CHAT_TEST_CASE
from integration.translation.chat_completions.bases.openai_responses import GPT_5_6_SOL_TEST_CASE
from integration.translation.runner import assert_translation
@ -78,10 +79,47 @@ GPT_5_6_SOL_LATE_BRIDGE_WEB_SEARCH_TEST_CASE: Final = replace(
expected_litellm_response={**GPT_5_6_SOL_TEST_CASE.expected_litellm_response, "model": "openai/gpt-5.6-sol"},
)
# The rig's deployment has a custom api_base, which the bridge treats as a chat-only endpoint unless the
# request names an effort. An effort the caller drops through additional_drop_params must not pick the
# Responses route, so this request stays on /chat/completions exactly as before the early route decision
GPT_5_6_SOL_DROPPED_EFFORT_STAYS_ON_CHAT_TEST_CASE: Final = replace(
GPT_5_6_SOL_CHAT_TEST_CASE,
scenario="dropped_extra_body_effort_stays_on_chat",
litellm_request={
**GPT_5_6_SOL_CHAT_TEST_CASE.litellm_request,
"extra_body": {"reasoning_effort": "low"},
"additional_drop_params": ["reasoning_effort"],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}
],
},
expected_provider_request={
**GPT_5_6_SOL_CHAT_TEST_CASE.expected_provider_request,
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}
],
},
)
CASES: Final = (
GPT_5_6_SOL_WEB_SEARCH_TEST_CASE,
GPT_5_6_SOL_WEB_SEARCH_USER_LOCATION_TEST_CASE,
GPT_5_6_SOL_LATE_BRIDGE_WEB_SEARCH_TEST_CASE,
GPT_5_6_SOL_DROPPED_EFFORT_STAYS_ON_CHAT_TEST_CASE,
)

View file

@ -0,0 +1,35 @@
from litellm.responses.bridge_check import bridge_check_request_params
def test_a_dropped_effort_inside_extra_body_is_not_read_by_the_route_decision():
request = {
"extra_body": {"reasoning_effort": "low", "reasoningSummary": "auto"},
"additional_drop_params": ["reasoning_effort"],
"tools": [{"type": "function", "function": {"name": "get_weather"}}],
}
params = bridge_check_request_params(request)
assert params["extra_body"] == {"reasoningSummary": "auto"}, params
assert params["tools"] is request["tools"]
def test_a_dropped_top_level_alias_is_removed_too():
params = bridge_check_request_params({"reasoningSummary": "auto", "additional_drop_params": ["reasoningSummary"]})
assert "reasoningSummary" not in params, params
def test_a_request_without_drops_is_returned_whole():
request = {"extra_body": {"reasoning_effort": "low"}, "tools": []}
params = bridge_check_request_params(request)
assert params == request
assert params["extra_body"] == {"reasoning_effort": "low"}
def test_a_non_list_additional_drop_params_drops_nothing():
request = {"extra_body": {"reasoning_effort": "low"}, "additional_drop_params": "reasoning_effort"}
assert bridge_check_request_params(request)["extra_body"] == {"reasoning_effort": "low"}