diff --git a/litellm/llms/anthropic/pass_through/adapters/handler.py b/litellm/llms/anthropic/pass_through/adapters/handler.py index db43f7cd052..454fb43b416 100644 --- a/litellm/llms/anthropic/pass_through/adapters/handler.py +++ b/litellm/llms/anthropic/pass_through/adapters/handler.py @@ -412,7 +412,7 @@ class LiteLLMMessagesToCompletionTransformationHandler: return None if resolved_provider == "litellm_proxy": return None - from litellm.main import responses_api_bridge_check + from litellm.responses.bridge_check import responses_api_bridge_check web_search_options: Final = completion_kwargs.get("web_search_options") tools: Final = completion_kwargs.get("tools") diff --git a/litellm/llms/anthropic/pass_through/messages/handler.py b/litellm/llms/anthropic/pass_through/messages/handler.py index 47744d6e93b..7c3692c0622 100644 --- a/litellm/llms/anthropic/pass_through/messages/handler.py +++ b/litellm/llms/anthropic/pass_through/messages/handler.py @@ -45,7 +45,7 @@ _RESPONSES_API_PROVIDERS: Final = frozenset({"openai"}) def _bridges_to_responses_api(model: str, custom_llm_provider: str) -> bool: - from litellm.main import responses_api_bridge_check + from litellm.responses.bridge_check import responses_api_bridge_check model_info, _ = responses_api_bridge_check(model=model, custom_llm_provider=custom_llm_provider) return model_info.get("mode") == "responses" diff --git a/litellm/main.py b/litellm/main.py index e4381650954..88c1257ee2c 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -26,7 +26,6 @@ from copy import deepcopy from functools import partial from types import MappingProxyType from typing import TYPE_CHECKING, Any, Final, Literal, Optional, Protocol, Union, cast, get_args -from urllib.parse import urlsplit from litellm._logging import redact_string from litellm._uuid import uuid @@ -109,10 +108,6 @@ from litellm.litellm_core_utils.request_timeout_resolver import ( get_configured_request_timeout, ) from litellm.litellm_core_utils.tokenizer import Encoding as Tokenizer -from litellm.llms.azure_ai.common_utils import ( - azure_ai_supports_native_responses, - foundry_chat_rejects_function_tools_while_reasoning, -) from litellm.llms.base_llm import BaseConfig, BaseImageGenerationConfig from litellm.llms.base_llm.base_model_iterator import ( convert_model_response_to_streaming, @@ -167,7 +162,6 @@ from litellm.utils import ( create_tokenizer, get_llm_provider, get_model_info, - get_model_info_helper, get_non_default_completion_params, get_non_default_transcription_params, get_optional_params_embeddings, @@ -1073,142 +1067,12 @@ def mock_completion( raise Exception(f"Mock completion response failed - {e}") -_OPENAI_DEFAULT_API_BASE: Final = "https://api.openai.com/v1" -_OPENAI_API_HOST: Final = "api.openai.com" - - -def _is_openai_backed_api_base(api_base: str) -> bool: - hostname: Final = urlsplit(api_base).hostname - return hostname is not None and (hostname == _OPENAI_API_HOST or hostname.endswith(f".{_OPENAI_API_HOST}")) - - -def _resolve_openai_api_base(api_base: str | None) -> str: - """Effective OpenAI base a chat request will hit: arg > global > env > default. The bridge gate - and the ``_complete_custom_openai`` chat handler MUST resolve this identically, or a custom base - set via ``litellm.api_base`` or ``OPENAI_BASE_URL``/``OPENAI_API_BASE`` is invisible to the gate, - which then misreads it as the default OpenAI endpoint and bridges a request the backend can't serve.""" - return ( - api_base - or litellm.api_base - or get_secret_str("OPENAI_BASE_URL") - or get_secret_str("OPENAI_API_BASE") - or _OPENAI_DEFAULT_API_BASE - ) - - -def responses_api_bridge_check( - model: str, - custom_llm_provider: str, - web_search_options: OpenAIWebSearchOptions | None = None, - tools: Sequence[Mapping[str, object]] | None = None, - reasoning_effort: str | Mapping[str, object] | None = None, - reasoning_summary: object | None = None, - api_base: str | None = None, -) -> tuple[dict, str]: - model_info: dict[str, object] = {} - - # Global flag: route ALL OpenAI chat completions through Responses API. - # Returns early with minimal model_info; callers only inspect the "mode" key. - if litellm.route_all_chat_openai_to_responses and custom_llm_provider == "openai": - model = model.replace("responses/", "") - model_info["mode"] = "responses" - return model_info, model - - try: - model_info = cast( - dict, - get_model_info_helper(model=model, custom_llm_provider=custom_llm_provider), - ) - if model_info.get("mode") is None and model.startswith("responses/"): - model = model.replace("responses/", "") - mode = "responses" - model_info["mode"] = mode - - except Exception as e: - verbose_logger.debug("Error getting model info: %s", e) - - if model.startswith("responses/"): # handle azure models - `azure/responses/` - model = model.replace("responses/", "") - mode = "responses" - model_info["mode"] = mode - - if web_search_options is not None and custom_llm_provider == "xai": - model_info["mode"] = "responses" - model = model.replace("responses/", "") - - # OpenAI/Azure GPT-5 chat-completions that need Responses-only fields (e.g. - # ``reasoningSummary`` in ``extra_body``) must be bridged; Chat Completions rejects - # those keys. - # - # - gpt-5.4+: FUNCTION tools with reasoning active must be bridged. OpenAI enables - # reasoning by default for these models (unset reasoning_effort means medium - # server-side), and Chat Completions rejects function tools whenever reasoning is - # on ("Function tools with reasoning_effort are not supported ... use - # /v1/responses or set reasoning_effort to 'none'"), so only an explicit - # ``"none"`` keeps the request chat-servable. Custom (grammar) tools are served - # natively by Chat Completions with reasoning on, so custom-only requests stay on - # chat and keep their native custom tool_call response shape. - # - The UNSET-effort arm only fires against endpoints known to enforce that - # constraint (any api.openai.com host, or Azure OpenAI where api_base is - # always set): chat-only OpenAI-compatible backends registered under the openai - # provider with a custom api_base and gpt-5.4+ model names serve tools without - # reasoning fine and have no /responses route, so they keep pre-existing - # behavior (bridge only on an explicit reasoning_effort). - # - Azure AI Foundry's OpenAI v1 hosts (azure_ai provider) enforce it later in the series: - # an explicit effort with function tools is rejected from gpt-5.6 on, and the unset - # effort only from gpt-6 on (gpt-5.6 serves tools with reasoning silently off), so the - # azure_ai gate keys on those measured boundaries instead of gpt-5.4+. - # - Older GPT-5 names (e.g. ``gpt-5``, ``gpt-5.1``): bridge only when a reasoning - # summary alias is present with ``reasoning_effort`` (tools alone stay on chat). - has_function_tool: Final = any( - ( - tool.get("type") == "function" and (isinstance(tool.get("function"), dict) or "name" in tool) - if isinstance(tool, dict) - else getattr(tool, "type", None) == "function" - ) - for tool in (tools or ()) - ) - if isinstance(reasoning_effort, dict): - reasoning_active = reasoning_effort.get("effort") != "none" or reasoning_effort.get("summary") is not None - else: - reasoning_active = reasoning_effort != "none" - # The reasoning+tools constraint is enforced by the real OpenAI backend behind any api.openai.com - # host (the default URL or a PrivateLink hostname such as .privatelink.api.openai.com) and - # by Azure OpenAI through the azure provider. Resolve the effective OpenAI base arg>global>env>default - # exactly as the chat handler does, so a custom base set via litellm.api_base or - # OPENAI_BASE_URL/OPENAI_API_BASE isn't misread as the default and bridged to a /responses route it - # lacks. A whitespace-only base collapses to the default too. - resolved_api_base: Final = _resolve_openai_api_base(api_base).strip() - on_foundry_openai_endpoint: Final = custom_llm_provider == "azure_ai" and azure_ai_supports_native_responses( - model, api_base - ) - on_constraint_enforcing_endpoint: Final = ( - custom_llm_provider == "azure" or resolved_api_base == "" or _is_openai_backed_api_base(resolved_api_base) - ) - chat_rejects_function_tools: Final = ( - has_function_tool - and reasoning_active - and ( - foundry_chat_rejects_function_tools_while_reasoning(model, reasoning_effort) - if on_foundry_openai_endpoint - else ( - OpenAIGPT5Config.is_model_gpt_5_4_plus_model(model) - and (reasoning_effort is not None or on_constraint_enforcing_endpoint) - ) - ) - ) - if ( - (custom_llm_provider in ("openai", "azure") or on_foundry_openai_endpoint) - and model_info.get("mode") != "responses" - and OpenAIGPT5Config.is_model_gpt_5_model(model) - and not OpenAIGPT5Config.is_model_gpt_5_search_model(model) - and ((reasoning_effort is not None and reasoning_summary is not None) or chat_rejects_function_tools) - ): - model_info["mode"] = "responses" - model = model.replace("responses/", "") - - return model_info, model - +from litellm.responses.bridge_check import ( # noqa: E402 # re-export at the old definition site + _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 ( # noqa: E402 # re-export at the old definition site + responses_api_bridge_check as responses_api_bridge_check, # pyright: ignore[reportUnknownVariableType] # noqa: PLC0414 # re-export of moved function +) _ANTHROPIC_ONLY_TOOL_KEYS: Final = frozenset({"input_examples", "eager_input_streaming"}) diff --git a/litellm/responses/bridge_check.py b/litellm/responses/bridge_check.py new file mode 100644 index 00000000000..3b1e958ff86 --- /dev/null +++ b/litellm/responses/bridge_check.py @@ -0,0 +1,150 @@ +from collections.abc import Mapping, Sequence +from typing import Final, cast # noqa: TID251 # moved unchanged from litellm/main.py +from urllib.parse import urlsplit + +import litellm +from litellm._logging import verbose_logger +from litellm.llms.azure_ai.common_utils import ( + azure_ai_supports_native_responses, + foundry_chat_rejects_function_tools_while_reasoning, +) +from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import OpenAIWebSearchOptions +from litellm.utils import get_model_info_helper + +_OPENAI_DEFAULT_API_BASE: Final = "https://api.openai.com/v1" +_OPENAI_API_HOST: Final = "api.openai.com" + + +def _is_openai_backed_api_base(api_base: str) -> bool: + hostname: Final = urlsplit(api_base).hostname + return hostname is not None and (hostname == _OPENAI_API_HOST or hostname.endswith(f".{_OPENAI_API_HOST}")) + + +def _resolve_openai_api_base(api_base: str | None) -> str: + """Effective OpenAI base a chat request will hit: arg > global > env > default. The bridge gate + and the ``_complete_custom_openai`` chat handler MUST resolve this identically, or a custom base + set via ``litellm.api_base`` or ``OPENAI_BASE_URL``/``OPENAI_API_BASE`` is invisible to the gate, + which then misreads it as the default OpenAI endpoint and bridges a request the backend can't serve.""" + return ( + api_base + or litellm.api_base + or get_secret_str("OPENAI_BASE_URL") + or get_secret_str("OPENAI_API_BASE") + or _OPENAI_DEFAULT_API_BASE + ) + + +def responses_api_bridge_check( + model: str, + custom_llm_provider: str, + web_search_options: OpenAIWebSearchOptions | None = None, + tools: Sequence[Mapping[str, object]] | None = None, + reasoning_effort: str | Mapping[str, object] | None = None, + reasoning_summary: object | None = None, + api_base: str | None = None, +) -> tuple[dict, str]: + model_info: dict[str, object] = {} + + # Global flag: route ALL OpenAI chat completions through Responses API. + # Returns early with minimal model_info; callers only inspect the "mode" key. + if litellm.route_all_chat_openai_to_responses and custom_llm_provider == "openai": + model = model.replace("responses/", "") + model_info["mode"] = "responses" + return model_info, model + + try: + model_info = cast( + dict, + get_model_info_helper(model=model, custom_llm_provider=custom_llm_provider), + ) + if model_info.get("mode") is None and model.startswith("responses/"): + model = model.replace("responses/", "") + mode = "responses" + model_info["mode"] = mode + + except Exception as e: + verbose_logger.debug("Error getting model info: %s", e) + + if model.startswith("responses/"): # handle azure models - `azure/responses/` + model = model.replace("responses/", "") + mode = "responses" + model_info["mode"] = mode + + if web_search_options is not None and custom_llm_provider == "xai": + model_info["mode"] = "responses" + model = model.replace("responses/", "") + + # OpenAI/Azure GPT-5 chat-completions that need Responses-only fields (e.g. + # ``reasoningSummary`` in ``extra_body``) must be bridged; Chat Completions rejects + # those keys. + # + # - gpt-5.4+: FUNCTION tools with reasoning active must be bridged. OpenAI enables + # reasoning by default for these models (unset reasoning_effort means medium + # server-side), and Chat Completions rejects function tools whenever reasoning is + # on ("Function tools with reasoning_effort are not supported ... use + # /v1/responses or set reasoning_effort to 'none'"), so only an explicit + # ``"none"`` keeps the request chat-servable. Custom (grammar) tools are served + # natively by Chat Completions with reasoning on, so custom-only requests stay on + # chat and keep their native custom tool_call response shape. + # - The UNSET-effort arm only fires against endpoints known to enforce that + # constraint (any api.openai.com host, or Azure OpenAI where api_base is + # always set): chat-only OpenAI-compatible backends registered under the openai + # provider with a custom api_base and gpt-5.4+ model names serve tools without + # reasoning fine and have no /responses route, so they keep pre-existing + # behavior (bridge only on an explicit reasoning_effort). + # - Azure AI Foundry's OpenAI v1 hosts (azure_ai provider) enforce it later in the series: + # an explicit effort with function tools is rejected from gpt-5.6 on, and the unset + # effort only from gpt-6 on (gpt-5.6 serves tools with reasoning silently off), so the + # azure_ai gate keys on those measured boundaries instead of gpt-5.4+. + # - Older GPT-5 names (e.g. ``gpt-5``, ``gpt-5.1``): bridge only when a reasoning + # summary alias is present with ``reasoning_effort`` (tools alone stay on chat). + has_function_tool: Final = any( + ( + tool.get("type") == "function" and (isinstance(tool.get("function"), dict) or "name" in tool) + if isinstance(tool, dict) + else getattr(tool, "type", None) == "function" + ) + for tool in (tools or ()) + ) + if isinstance(reasoning_effort, dict): + reasoning_active = reasoning_effort.get("effort") != "none" or reasoning_effort.get("summary") is not None + else: + reasoning_active = reasoning_effort != "none" + # The reasoning+tools constraint is enforced by the real OpenAI backend behind any api.openai.com + # host (the default URL or a PrivateLink hostname such as .privatelink.api.openai.com) and + # by Azure OpenAI through the azure provider. Resolve the effective OpenAI base arg>global>env>default + # exactly as the chat handler does, so a custom base set via litellm.api_base or + # OPENAI_BASE_URL/OPENAI_API_BASE isn't misread as the default and bridged to a /responses route it + # lacks. A whitespace-only base collapses to the default too. + resolved_api_base: Final = _resolve_openai_api_base(api_base).strip() + on_foundry_openai_endpoint: Final = custom_llm_provider == "azure_ai" and azure_ai_supports_native_responses( + model, api_base + ) + on_constraint_enforcing_endpoint: Final = ( + custom_llm_provider == "azure" or resolved_api_base == "" or _is_openai_backed_api_base(resolved_api_base) + ) + chat_rejects_function_tools: Final = ( + has_function_tool + and reasoning_active + and ( + foundry_chat_rejects_function_tools_while_reasoning(model, reasoning_effort) + if on_foundry_openai_endpoint + else ( + OpenAIGPT5Config.is_model_gpt_5_4_plus_model(model) + and (reasoning_effort is not None or on_constraint_enforcing_endpoint) + ) + ) + ) + if ( + (custom_llm_provider in ("openai", "azure") or on_foundry_openai_endpoint) + and model_info.get("mode") != "responses" + and OpenAIGPT5Config.is_model_gpt_5_model(model) + and not OpenAIGPT5Config.is_model_gpt_5_search_model(model) + and ((reasoning_effort is not None and reasoning_summary is not None) or chat_rejects_function_tools) + ): + model_info["mode"] = "responses" + model = model.replace("responses/", "") + + return model_info, model diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 23351fdcfa9..5f0c40b3eab 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -837,7 +837,7 @@ def test_responses_api_bridge_check_strips_responses_prefix(): """Test that responses_api_bridge_check strips 'responses/' prefix and sets mode.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 4096} model_info, model = responses_api_bridge_check( @@ -871,7 +871,7 @@ def test_responses_api_bridge_check_gpt_5_4_tools_plus_reasoning_routes_to_respo """gpt-5.4 with both tools and reasoning_effort should route to Responses API.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -901,7 +901,7 @@ def test_responses_api_bridge_check_gpt_5_5_tools_plus_reasoning_routes_to_respo """gpt-5.5+ with both tools and reasoning_effort should route to Responses API.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.5-pro", @@ -918,7 +918,7 @@ def test_responses_api_bridge_check_azure_gpt_5_4_tools_plus_reasoning_routes_to """Azure gpt-5.4 with both tools and reasoning_effort should route to Responses API.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -939,7 +939,7 @@ def test_responses_api_bridge_check_azure_gpt_5_4_tools_with_default_reasoning_r """ from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -960,7 +960,7 @@ def test_responses_api_bridge_check_gpt_5_4_tools_with_default_reasoning_routes_ """ from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -1031,7 +1031,7 @@ def test_responses_api_bridge_check_gpt_5_6_tools_with_default_reasoning_routes_ monkeypatch.delenv("OPENAI_API_BASE", raising=False) monkeypatch.setattr(litellm, "api_base", None) - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model=model_name, @@ -1051,7 +1051,7 @@ def test_responses_api_bridge_check_gpt_5_4_tools_with_reasoning_none_stays_chat """ from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -1068,7 +1068,7 @@ def test_responses_api_bridge_check_reasoning_none_with_summary_still_routes_to_ """A reasoning summary is Responses-only regardless of effort value.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -1090,7 +1090,7 @@ def test_responses_api_bridge_check_gpt_5_4_custom_tools_only_stays_chat(): """ from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1107,7 +1107,7 @@ def test_responses_api_bridge_check_gpt_5_4_mixed_function_and_custom_tools_rout """One function tool in the mix is enough to make chat unservable with reasoning on.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1127,7 +1127,7 @@ def test_responses_api_bridge_check_gpt_5_4_flat_function_tool_routes_to_respons """Responses-style flat function tool defs still count as function tools.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1173,7 +1173,7 @@ def test_responses_api_bridge_check_dict_effort_none_stays_chat(): """The escape hatch must honor litellm's dict form: {"effort": "none"} means reasoning off.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1189,7 +1189,7 @@ def test_responses_api_bridge_check_dict_effort_none_stays_chat(): def test_responses_api_bridge_check_dict_effort_active_routes_to_responses(): from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1206,7 +1206,7 @@ def test_responses_api_bridge_check_dict_effort_none_with_summary_routes_to_resp """A summary inside the dict form is Responses-only even when effort is none.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1228,7 +1228,7 @@ def test_responses_api_bridge_check_blank_api_base_is_default_openai(blank_api_b """ from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1250,7 +1250,7 @@ def test_responses_api_bridge_check_custom_api_base_with_unset_effort_stays_chat """ from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1275,7 +1275,7 @@ def test_responses_api_bridge_check_custom_api_base_via_global_with_unset_effort from litellm.main import responses_api_bridge_check monkeypatch.setattr(litellm, "api_base", "http://vllm.internal:8000/v1") - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1302,7 +1302,7 @@ def test_responses_api_bridge_check_custom_api_base_via_env_with_unset_effort_st monkeypatch.delenv("OPENAI_BASE_URL", raising=False) monkeypatch.delenv("OPENAI_API_BASE", raising=False) monkeypatch.setenv(env_var, "http://vllm.internal:8000/v1") - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1396,7 +1396,7 @@ def test_responses_api_bridge_check_custom_api_base_with_explicit_effort_still_r """Explicit reasoning_effort keeps its pre-existing bridging behavior on any api_base.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.6", @@ -1414,7 +1414,7 @@ def test_responses_api_bridge_check_azure_with_api_base_and_unset_effort_routes( """Azure OpenAI always sets api_base and does enforce the constraint; keep bridging.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -1494,7 +1494,7 @@ def test_responses_api_bridge_check_older_gpt_5_tools_without_reasoning_stays_ch """Pre-5.4 GPT-5 names keep the old boundary: tools alone never bridge.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.1", @@ -1511,7 +1511,7 @@ def test_responses_api_bridge_check_gpt_5_4_reasoning_summary_without_tools_rout """gpt-5.4+ with reasoning_effort + reasoningSummary but no tools should bridge (AI SDK).""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5.4", @@ -1529,7 +1529,7 @@ def test_responses_api_bridge_check_gpt_5_reasoning_summary_routes_to_responses( """Bare ``gpt-5`` with reasoning_effort + reasoningSummary should bridge (not 5.4+).""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5", @@ -1547,7 +1547,7 @@ def test_responses_api_bridge_check_gpt_5_tools_without_summary_stays_chat(): """gpt-5 with tools + reasoning_effort but no summary should stay on chat.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 128000} model_info, model = responses_api_bridge_check( model="gpt-5", @@ -1881,7 +1881,7 @@ def test_responses_api_bridge_check_handles_exception(): """Test that responses_api_bridge_check handles exceptions and still processes responses/ models.""" from litellm.main import responses_api_bridge_check - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.side_effect = Exception("Model not found") model_info, model = responses_api_bridge_check(model="responses/custom-model", custom_llm_provider="custom") @@ -1909,7 +1909,7 @@ def test_responses_api_bridge_check_global_flag_does_not_affect_azure(): from litellm.main import responses_api_bridge_check with patch.object(litellm, "route_all_chat_openai_to_responses", True): - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 4096} model_info, model = responses_api_bridge_check( model="gpt-4o", @@ -1924,7 +1924,7 @@ def test_responses_api_bridge_check_global_flag_default_false(): from litellm.main import responses_api_bridge_check with patch.object(litellm, "route_all_chat_openai_to_responses", False): - with patch("litellm.main.get_model_info_helper") as mock_get_model_info: + with patch("litellm.responses.bridge_check.get_model_info_helper") as mock_get_model_info: mock_get_model_info.return_value = {"max_tokens": 4096} model_info, model = responses_api_bridge_check( model="gpt-4o",