From d93d5a466bc8c0f60f93be2c86732ccd117ed192 Mon Sep 17 00:00:00 2001 From: mateo Date: Sat, 31 Oct 2026 22:16:24 +0000 Subject: [PATCH 1/4] fix(openai): keep web_search_options when a chat completions request bridges to the Responses API Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/main.py | 12 ++++++-- .../chat_completions/params/__init__.py | 0 ...ions_params_openai_responses_web_search.py | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 tests/integration/translation/chat_completions/params/__init__.py create mode 100644 tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py diff --git a/litellm/main.py b/litellm/main.py index 78767b03eb7..14a1e912ce8 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -6048,15 +6048,21 @@ def completion( bridges_to_responses_api: Final = ( responses_api_model_info.get("mode") == "responses" and not skip_responses_api_bridge ) - allowed_openai_params: Final[list[str] | None] = ( - [*(kwargs.get("allowed_openai_params") or []), "reasoning_effort"] + bridge_allowed_params: Final[tuple[str, ...]] = ( + ("reasoning_effort", "web_search_options") if bridges_to_responses_api - or reasoning_effort_needs_allowing( + else ("reasoning_effort",) + if reasoning_effort_needs_allowing( chat_reasoning.effort, model=model, custom_llm_provider=custom_llm_provider, base_model=base_model if isinstance(base_model, str) else None, ) + else () + ) + allowed_openai_params: Final[list[str] | None] = ( + [*(kwargs.get("allowed_openai_params") or []), *bridge_allowed_params] + if bridge_allowed_params else kwargs.get("allowed_openai_params") ) optional_param_args: Final = { diff --git a/tests/integration/translation/chat_completions/params/__init__.py b/tests/integration/translation/chat_completions/params/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py b/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py new file mode 100644 index 00000000000..799b2a13d12 --- /dev/null +++ b/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py @@ -0,0 +1,28 @@ +from dataclasses import replace +from typing import Final + +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_responses import GPT_5_6_SOL_TEST_CASE +from integration.translation.runner import assert_translation + +GPT_5_6_SOL_WEB_SEARCH_TEST_CASE: Final = replace( + GPT_5_6_SOL_TEST_CASE, + scenario="web_search_options", + litellm_request={**GPT_5_6_SOL_TEST_CASE.litellm_request, "web_search_options": {"search_context_size": "low"}}, + expected_provider_request={ + **GPT_5_6_SOL_TEST_CASE.expected_provider_request, + "tools": [{"type": "web_search", "search_context_size": "low"}], + }, +) + +CASES: Final = (GPT_5_6_SOL_WEB_SEARCH_TEST_CASE,) + + +@pytest.mark.parametrize("case", CASES, ids=lambda case: case.id) +def test_chat_completions_params_openai_responses_web_search( + case: TranslationTestCase, gateway: Gateway, provider: SharedProvider +) -> None: + assert_translation(case, gateway, provider) From 846907a3ff80b618b7f9b6442a6ac5f87c5189a8 Mon Sep 17 00:00:00 2001 From: mateo Date: Sat, 31 Oct 2026 22:45:26 +0000 Subject: [PATCH 2/4] fix(openai): bridge web_search_options on late-selected Responses routes and flatten user_location Run the tools-plus-reasoning bridge decision before the optional-params gate so a gpt-5.6 chat request with function tools keeps web_search_options, and send the Responses web_search tool its flat user_location shape instead of the Chat Completions nested approximate object Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../transformation.py | 20 ++++++ litellm/main.py | 29 ++++---- ...ions_params_openai_responses_web_search.py | 66 ++++++++++++++++++- ...responses_transformation_transformation.py | 47 +++++++++++++ 4 files changed, 144 insertions(+), 18 deletions(-) diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index bb59b4617af..62d2ab64deb 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -1858,10 +1858,30 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): web_search_tool: Final[dict[str, object]] = {"type": "web_search"} if isinstance(web_search_options, dict): web_search_tool.update(web_search_options) + typed_options: Final = cast(Mapping[str, object], web_search_options) # cast-ok: caller-supplied dict + user_location: Final = self._responses_web_search_user_location(typed_options.get("user_location")) + if user_location is not None: + web_search_tool["user_location"] = user_location # Cast to Any to match the expected union type for tools list items tools.append(cast(Any, web_search_tool)) + @staticmethod + def _responses_web_search_user_location(user_location: object) -> Mapping[str, object] | None: + """Chat Completions nests the location under ``approximate``; the Responses web_search tool + takes ``{"type": "approximate", "city": ..., "country": ...}`` flat and rejects the nested key.""" + if not isinstance(user_location, dict): + return None + typed_location: Final = cast(Mapping[object, object], user_location) # cast-ok: caller-supplied dict + approximate: Final = typed_location.get("approximate") + if not isinstance(approximate, dict): + return None + typed_approximate: Final = cast(Mapping[object, object], approximate) # cast-ok: caller-supplied dict + return { + "type": "approximate", + **{str(key): value for key, value in typed_approximate.items() if value is not None}, + } + def transform_response_format_to_text_format(self, response_format: object) -> "ResponseText | None": return self._transform_response_format_to_text_format(response_format) diff --git a/litellm/main.py b/litellm/main.py index 14a1e912ce8..a9f0f8bfccc 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -6044,6 +6044,17 @@ def completion( if dynamic_api_key is not None: api_key = dynamic_api_key + if responses_api_model_info.get("mode") != "responses": + 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), + api_base=api_base, + deployment_model_info=deployment_model_info, + ) # check if user passed in any of the OpenAI optional params bridges_to_responses_api: Final = ( responses_api_model_info.get("mode") == "responses" and not skip_responses_api_bridge @@ -6329,27 +6340,11 @@ def completion( ), ) - ## RESPONSES API BRIDGE LOGIC ## - check if model has 'mode: responses' in litellm.model_cost map - # Only run the second bridge check if the first one didn't already - # detect responses mode (e.g. via the "responses/" prefix). The second - # check handles cases like gpt-5.4+ with tools+reasoning_effort or - # reasoningSummary/reasoning_summary without tools (AI SDK) that the first - # (early) check doesn't cover. + ## RESPONSES API BRIDGE LOGIC ## - route the request over the Responses API when selected above _reasoning_summary_for_bridge: Final = peek_reasoning_summary_aliases(optional_params) typed_deployment_model_info: Final = str_keyed_mapping( cast(object, model_info) # cast-ok: model_info is the untyped dict kwarg ) - if responses_api_model_info.get("mode") != "responses": - 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, optional_params.get("extra_body")), - reasoning_summary=_reasoning_summary_for_bridge, - api_base=api_base, - deployment_model_info=typed_deployment_model_info, - ) # Use base_model (the true underlying model) for Azure model-type # detection when the deployment name differs from the model name. diff --git a/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py b/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py index 799b2a13d12..bdcef8db036 100644 --- a/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py +++ b/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py @@ -18,7 +18,71 @@ GPT_5_6_SOL_WEB_SEARCH_TEST_CASE: Final = replace( }, ) -CASES: Final = (GPT_5_6_SOL_WEB_SEARCH_TEST_CASE,) +GPT_5_6_SOL_WEB_SEARCH_USER_LOCATION_TEST_CASE: Final = replace( + GPT_5_6_SOL_TEST_CASE, + scenario="web_search_options_user_location", + litellm_request={ + **GPT_5_6_SOL_TEST_CASE.litellm_request, + "web_search_options": { + "search_context_size": "medium", + "user_location": {"type": "approximate", "approximate": {"country": "US", "city": "San Francisco"}}, + }, + }, + expected_provider_request={ + **GPT_5_6_SOL_TEST_CASE.expected_provider_request, + "tools": [ + { + "type": "web_search", + "search_context_size": "medium", + "user_location": {"type": "approximate", "country": "US", "city": "San Francisco"}, + } + ], + }, +) + +# No `responses/` prefix: a gpt-5.6 chat request with function tools and a reasoning effort is bridged by the +# tools-plus-reasoning rule rather than the deployment's mode, and web_search_options must survive that too +GPT_5_6_SOL_LATE_BRIDGE_WEB_SEARCH_TEST_CASE: Final = replace( + GPT_5_6_SOL_TEST_CASE, + scenario="late_bridge_web_search_options", + litellm_request={ + **GPT_5_6_SOL_TEST_CASE.litellm_request, + "model": "openai/gpt-5.6-sol", + "reasoning_effort": "low", + "web_search_options": {"search_context_size": "low"}, + "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_TEST_CASE.expected_provider_request, + "tools": [ + { + "name": "get_weather", + "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}, + "strict": None, + "type": "function", + "description": "Get weather", + }, + {"type": "web_search", "search_context_size": "low"}, + ], + "reasoning": {"effort": "low"}, + }, + expected_litellm_response={**GPT_5_6_SOL_TEST_CASE.expected_litellm_response, "model": "openai/gpt-5.6-sol"}, +) + +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, +) @pytest.mark.parametrize("case", CASES, ids=lambda case: case.id) diff --git a/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py b/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py index 626e554d476..e83aa7e0b17 100644 --- a/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py +++ b/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py @@ -258,3 +258,50 @@ def test_translate_responses_chunk_passthrough_chat_completion_chunk(): assert result.choices[0].delta.content == "Hi! How can I help?" assert result.choices[0].finish_reason is None + + +class _LoggingObj: + pass + + +def test_transform_request_flattens_chat_web_search_user_location_for_the_responses_tool(): + """Chat Completions nests the location under `approximate`; OpenAI's Responses web_search tool rejects that + key ("Unknown parameter: 'tools[0].user_location.approximate'") and takes the fields flat.""" + handler = LiteLLMResponsesTransformationHandler() + + result = handler.transform_request( + model="gpt-5.6-sol", + messages=[{"role": "user", "content": "What is a top local headline today?"}], + optional_params={ + "web_search_options": { + "search_context_size": "medium", + "user_location": {"type": "approximate", "approximate": {"country": "US", "city": "San Francisco"}}, + } + }, + litellm_params={}, + headers={}, + litellm_logging_obj=_LoggingObj(), + ) + + assert result["tools"] == [ + { + "type": "web_search", + "search_context_size": "medium", + "user_location": {"type": "approximate", "country": "US", "city": "San Francisco"}, + } + ], result["tools"] + + +def test_transform_request_keeps_a_web_search_option_without_a_location_as_sent(): + handler = LiteLLMResponsesTransformationHandler() + + result = handler.transform_request( + model="gpt-5.6-sol", + messages=[{"role": "user", "content": "What is the top headline today?"}], + optional_params={"web_search_options": {"search_context_size": "low"}}, + litellm_params={}, + headers={}, + litellm_logging_obj=_LoggingObj(), + ) + + assert result["tools"] == [{"type": "web_search", "search_context_size": "low"}], result["tools"] From 4ffb145435043c302780935c102f80d05e79409e Mon Sep 17 00:00:00 2001 From: mateo Date: Sat, 31 Oct 2026 23:03:03 +0000 Subject: [PATCH 3/4] 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> --- litellm/main.py | 8 +++- litellm/responses/bridge_check.py | 18 +++++++++ ...ions_params_openai_responses_web_search.py | 38 +++++++++++++++++++ tests/unit/responses/test_bridge_check.py | 35 +++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 tests/unit/responses/test_bridge_check.py diff --git a/litellm/main.py b/litellm/main.py index a9f0f8bfccc..fa7705231ca 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -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, ) diff --git a/litellm/responses/bridge_check.py b/litellm/responses/bridge_check.py index 826de8cfdc9..39a283c72d8 100644 --- a/litellm/responses/bridge_check.py +++ b/litellm/responses/bridge_check.py @@ -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: diff --git a/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py b/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py index bdcef8db036..068c1bd189b 100644 --- a/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py +++ b/tests/integration/translation/chat_completions/params/test_chat_completions_params_openai_responses_web_search.py @@ -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, ) diff --git a/tests/unit/responses/test_bridge_check.py b/tests/unit/responses/test_bridge_check.py new file mode 100644 index 00000000000..a0b09024aad --- /dev/null +++ b/tests/unit/responses/test_bridge_check.py @@ -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"} From 39de3bcbab66aa4820265df2de4fd18e3d84bcde Mon Sep 17 00:00:00 2001 From: mateo Date: Sat, 31 Oct 2026 23:06:59 +0000 Subject: [PATCH 4/4] test(responses): shard the bridge check tests and mark their locals Final Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .github/workflows/test-unit.yml | 1 + ...ellm_responses_transformation_transformation.py | 10 ++++++---- tests/unit/responses/test_bridge_check.py | 14 ++++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 2c05ee827a3..ca779997360 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -735,6 +735,7 @@ jobs: tests/unit/responses/test_no_duplicate_spend_logs.py tests/unit/responses/test_null_test_fix.py tests/unit/responses/test_responses_api_bridge_flag.py + tests/unit/responses/test_bridge_check.py tests/unit/responses/test_responses_api_lifecycle.py tests/unit/responses/test_responses_api_request_body.py tests/unit/responses/test_responses_prompt_management.py diff --git a/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py b/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py index e83aa7e0b17..cd423abe042 100644 --- a/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py +++ b/tests/unit/completion_extras/test_litellm_responses_transformation_transformation.py @@ -2,6 +2,8 @@ Test for response_format to text.format conversion in completion -> responses bridge """ +from typing import Final + import pytest from litellm.completion_extras.litellm_responses_transformation.transformation import ( LiteLLMResponsesTransformationHandler, @@ -267,9 +269,9 @@ class _LoggingObj: def test_transform_request_flattens_chat_web_search_user_location_for_the_responses_tool(): """Chat Completions nests the location under `approximate`; OpenAI's Responses web_search tool rejects that key ("Unknown parameter: 'tools[0].user_location.approximate'") and takes the fields flat.""" - handler = LiteLLMResponsesTransformationHandler() + handler: Final = LiteLLMResponsesTransformationHandler() - result = handler.transform_request( + result: Final = handler.transform_request( model="gpt-5.6-sol", messages=[{"role": "user", "content": "What is a top local headline today?"}], optional_params={ @@ -293,9 +295,9 @@ def test_transform_request_flattens_chat_web_search_user_location_for_the_respon def test_transform_request_keeps_a_web_search_option_without_a_location_as_sent(): - handler = LiteLLMResponsesTransformationHandler() + handler: Final = LiteLLMResponsesTransformationHandler() - result = handler.transform_request( + result: Final = handler.transform_request( model="gpt-5.6-sol", messages=[{"role": "user", "content": "What is the top headline today?"}], optional_params={"web_search_options": {"search_context_size": "low"}}, diff --git a/tests/unit/responses/test_bridge_check.py b/tests/unit/responses/test_bridge_check.py index a0b09024aad..d7cee7a16ba 100644 --- a/tests/unit/responses/test_bridge_check.py +++ b/tests/unit/responses/test_bridge_check.py @@ -1,35 +1,37 @@ +from typing import Final + 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 = { + request: Final = { "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) + params: Final = 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"]}) + params: Final = 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": []} + request: Final = {"extra_body": {"reasoning_effort": "low"}, "tools": []} - params = bridge_check_request_params(request) + params: Final = 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"} + request: Final = {"extra_body": {"reasoning_effort": "low"}, "additional_drop_params": "reasoning_effort"} assert bridge_check_request_params(request)["extra_body"] == {"reasoning_effort": "low"}