From 0a8bf4ec9e7c98b70fffa770d54fe2d9035d9171 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 13 Apr 2026 18:03:27 +0530 Subject: [PATCH] feat(responses): rename bridge opt-in to use_chat_completions_api - Add use_chat_completions_api (keep use_responses_api_bridge as deprecated alias) - Support openai/chat_completions/ model prefix for the same behavior - Forward use_chat_completions_api in file_search emulation inner calls - Update response_api.md and extend unit tests Made-with: Cursor --- docs/my-website/docs/response_api.md | 29 +++++++-- litellm/responses/main.py | 44 +++++++++++-- litellm/types/router.py | 6 +- .../test_responses_api_bridge_flag.py | 65 ++++++++++++++++--- 4 files changed, 124 insertions(+), 20 deletions(-) diff --git a/docs/my-website/docs/response_api.md b/docs/my-website/docs/response_api.md index 36c9ee1351..94f5c3e52f 100644 --- a/docs/my-website/docs/response_api.md +++ b/docs/my-website/docs/response_api.md @@ -1509,11 +1509,15 @@ curl http://localhost:4000/v1/responses \ If you're using an **OpenAI-compatible third-party provider** (e.g. llama.cpp, vLLM, LM Studio) via `openai/` prefix with a custom `api_base`, LiteLLM will normally forward `/responses` requests directly to that endpoint. If the provider only supports `/chat/completions`, the request will fail. -Set `use_responses_api_bridge: true` to force the `/responses` → `/chat/completions` bridge for these models. +Use any of these to force the `/responses` → `/chat/completions` bridge: + +1. **`use_chat_completions_api: true`** (recommended) — makes it explicit that LiteLLM will call the provider’s chat-completions API. +2. **`openai/chat_completions/`** — same pattern as `responses/` on chat completions: the model id encodes the routing choice. +3. **`use_responses_api_bridge: true`** — deprecated alias for `use_chat_completions_api` (kept for backward compatibility). #### Python SDK Usage -```python showLineNumbers title="Force bridge for custom openai/ endpoint" +```python showLineNumbers title="Force bridge for custom openai/ endpoint (flag)" import litellm response = litellm.responses( @@ -1521,7 +1525,22 @@ response = litellm.responses( input="Hello!", api_base="http://localhost:8080", api_key="fake-key", - use_responses_api_bridge=True, + use_chat_completions_api=True, +) + +print(response) +``` + +Or encode it in the model id: + +```python showLineNumbers title="Force bridge via openai/chat_completions/ model prefix" +import litellm + +response = litellm.responses( + model="openai/chat_completions/my-custom-model", + input="Hello!", + api_base="http://localhost:8080", + api_key="fake-key", ) print(response) @@ -1538,9 +1557,11 @@ model_list: model: openai/my-custom-model api_base: http://localhost:8080/v1 api_key: fake-key - use_responses_api_bridge: true + use_chat_completions_api: true ``` +Alternatively set `model: openai/chat_completions/my-custom-model` instead of the flag. + **Start Proxy:** ```bash showLineNumbers title="Start LiteLLM Proxy" diff --git a/litellm/responses/main.py b/litellm/responses/main.py index 80bd319569..91e173a7a8 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -643,6 +643,29 @@ def _apply_prompt_management_to_responses_call( return input, model, custom_llm_provider +# Opt-in via model id (mirrors the `responses/` prefix pattern on chat completions). +_OPENAI_CHAT_COMPLETIONS_RESPONSES_MODEL_PREFIX = "openai/chat_completions/" + + +def _normalize_openai_chat_completions_responses_model(model: str) -> tuple[str, bool]: + """ + Strip `openai/chat_completions/` → `openai/` and return True when the + prefix was applied (same effect as use_chat_completions_api=True). + """ + if not model.startswith(_OPENAI_CHAT_COMPLETIONS_RESPONSES_MODEL_PREFIX): + return model, False + remainder = model[len(_OPENAI_CHAT_COMPLETIONS_RESPONSES_MODEL_PREFIX) :] + if not remainder: + return model, False + return f"openai/{remainder}", True + + +def _pop_use_chat_completions_api_kw(kwargs: Dict[str, Any]) -> bool: + """Pop bridge flags; True if either requests the chat-completions path.""" + use_cc = kwargs.pop("use_chat_completions_api", None) + return bool(use_cc) + + def _resolve_model_provider_for_responses( model: str, custom_llm_provider: Optional[str], @@ -754,7 +777,7 @@ def responses( litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None) _is_async = kwargs.pop("aresponses", False) is True - use_responses_api_bridge = kwargs.pop("use_responses_api_bridge", None) + use_chat_completions_api = _pop_use_chat_completions_api_kw(kwargs) # Convert text_format to text parameter if provided text = ResponsesAPIRequestUtils.convert_text_format_to_text_param( @@ -777,6 +800,15 @@ def responses( mock_response=litellm_params.mock_response ) + _stripped_model, _from_chat_completions_prefix = ( + _normalize_openai_chat_completions_responses_model(model) + ) + model = _stripped_model + local_vars["model"] = model + use_chat_completions_api = ( + use_chat_completions_api or _from_chat_completions_prefix + ) + model, custom_llm_provider = _resolve_model_provider_for_responses( model=model, custom_llm_provider=custom_llm_provider, @@ -872,7 +904,7 @@ def responses( if _has_file_search_tool(tools) and ( responses_api_provider_config is None - or use_responses_api_bridge is True + or use_chat_completions_api is True or not responses_api_provider_config.supports_native_file_search() ): from litellm.responses.file_search.emulated_handler import ( @@ -907,7 +939,11 @@ def responses( "extra_body": extra_body, "timeout": timeout, "custom_llm_provider": custom_llm_provider, - **({"use_responses_api_bridge": True} if use_responses_api_bridge else {}), + **( + {"use_chat_completions_api": True} + if use_chat_completions_api + else {} + ), **{k: v for k, v in kwargs.items() if k not in _internal_skip}, } if _is_async: @@ -922,7 +958,7 @@ def responses( **emulated_kwargs, ) - if responses_api_provider_config is None or use_responses_api_bridge is True: + if responses_api_provider_config is None or use_chat_completions_api is True: return litellm_completion_transformation_handler.response_api_handler( model=model, input=input, diff --git a/litellm/types/router.py b/litellm/types/router.py index d608f30249..6f483c883d 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -199,7 +199,7 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams): budget_duration: Optional[str] = None use_in_pass_through: Optional[bool] = False use_litellm_proxy: Optional[bool] = False - use_responses_api_bridge: Optional[bool] = None + use_chat_completions_api: Optional[bool] = None model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) merge_reasoning_content_in_choices: Optional[bool] = False model_info: Optional[Dict] = None @@ -319,8 +319,8 @@ class LiteLLMParamsTypedDict(TypedDict, total=False): configurable_clientside_auth_params: CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS # for allowing api base switching on finetuned models ## DROP PARAMS ## drop_params: Optional[bool] - ## RESPONSES API BRIDGE ## - use_responses_api_bridge: Optional[bool] + ## RESPONSES API → CHAT COMPLETIONS BRIDGE ## + use_chat_completions_api: Optional[bool] ## UNIFIED PROJECT/REGION ## region_name: Optional[str] ## VERTEX AI ## diff --git a/tests/test_litellm/responses/test_responses_api_bridge_flag.py b/tests/test_litellm/responses/test_responses_api_bridge_flag.py index 727692d55b..e635e12560 100644 --- a/tests/test_litellm/responses/test_responses_api_bridge_flag.py +++ b/tests/test_litellm/responses/test_responses_api_bridge_flag.py @@ -1,6 +1,7 @@ """ -Tests for the `use_responses_api_bridge` flag that allows openai/ models -with custom api_base to opt-in to the /responses → /chat/completions bridge. +Tests for forcing the /responses → /chat/completions bridge for `openai/` models +(via `use_chat_completions_api`, deprecated `use_responses_api_bridge`, or the +`openai/chat_completions/` model id). 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. @@ -19,7 +20,7 @@ from litellm.types.llms.openai import ResponseAPIUsage, ResponsesAPIResponse class TestUseResponsesApiBridgeFlag: - """Test that use_responses_api_bridge forces the chat completions bridge.""" + """Test that bridge opt-in forces the chat completions path.""" @patch( "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler" @@ -28,8 +29,7 @@ class TestUseResponsesApiBridgeFlag: "litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config" ) def test_bridge_used_when_flag_is_true(self, mock_get_config, mock_bridge_handler): - """When use_responses_api_bridge=True, the bridge handler should be called - even though the provider (openai) has native responses API support.""" + """When use_responses_api_bridge=True (deprecated alias), the bridge runs.""" # Setup: provider config returns a non-None config (native support exists) mock_get_config.return_value = litellm.OpenAIResponsesAPIConfig() @@ -44,6 +44,51 @@ class TestUseResponsesApiBridgeFlag: mock_bridge_handler.assert_called_once() + @patch( + "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler" + ) + @patch( + "litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config" + ) + def test_bridge_used_when_use_chat_completions_api_true( + self, mock_get_config, mock_bridge_handler + ): + """When use_chat_completions_api=True, the bridge handler should be called.""" + mock_get_config.return_value = litellm.OpenAIResponsesAPIConfig() + mock_bridge_handler.return_value = MagicMock() + + litellm.responses( + model="openai/my-custom-model", + input="Hello", + use_chat_completions_api=True, + litellm_logging_obj=MagicMock(), + ) + + mock_bridge_handler.assert_called_once() + + @patch( + "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler" + ) + @patch( + "litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config" + ) + def test_bridge_used_when_model_uses_chat_completions_prefix( + self, mock_get_config, mock_bridge_handler + ): + """`openai/chat_completions/` normalizes to `openai/` and uses the bridge.""" + mock_get_config.return_value = litellm.OpenAIResponsesAPIConfig() + mock_bridge_handler.return_value = MagicMock() + + litellm.responses( + model="openai/chat_completions/my-custom-model", + input="Hello", + litellm_logging_obj=MagicMock(), + ) + + mock_bridge_handler.assert_called_once() + # Model string is provider-normalized after resolution; prefix only forces the bridge. + assert mock_bridge_handler.call_args.kwargs["model"].endswith("my-custom-model") + @patch("litellm.responses.main.base_llm_http_handler.response_api_handler") @patch( "litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config" @@ -84,9 +129,10 @@ class TestUseResponsesApiBridgeFlag: ) call_kwargs = mock_bridge_handler.call_args - # The flag should not appear in the kwargs passed to the bridge handler + # Bridge flags should not appear in the kwargs passed to the bridge handler all_kwargs = call_kwargs.kwargs if call_kwargs.kwargs else {} assert "use_responses_api_bridge" not in all_kwargs + assert "use_chat_completions_api" not in all_kwargs @patch( "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler" @@ -145,12 +191,12 @@ class TestUseResponsesApiBridgeFlag: litellm_logging_obj=MagicMock(), ) - # Verify _call_aresponses was called with use_responses_api_bridge=True + # Verify _call_aresponses was called with use_chat_completions_api=True mock_call_aresponses.assert_called_once() call_kwargs = mock_call_aresponses.call_args.kwargs assert ( - call_kwargs.get("use_responses_api_bridge") is True - ), "use_responses_api_bridge flag should be forwarded to inner aresponses call" + call_kwargs.get("use_chat_completions_api") is True + ), "use_chat_completions_api should be forwarded to inner aresponses call" @patch( "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler" @@ -223,6 +269,7 @@ class TestUseResponsesApiBridgeFlag: for call in mock_bridge_handler.call_args_list: all_kwargs = call.kwargs if call.kwargs else {} assert "use_responses_api_bridge" not in all_kwargs + assert "use_chat_completions_api" not in all_kwargs assert result is not None assert result.id is not None