From 0ff30cce6ee9ad6650fdcbb27be48c04a3fba3a2 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 30 Mar 2026 16:38:52 +0530 Subject: [PATCH 1/2] feat(responses): add use_responses_api_bridge flag for openai/ models with custom api_base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows openai/-prefixed models with a custom api_base pointing to a third-party OpenAI-compatible provider to opt-in to the /responses → /chat/completions bridge, rather than forwarding requests natively to /v1/responses (which may not be supported by the provider). Co-Authored-By: Claude Sonnet 4.6 --- litellm/responses/main.py | 4 +- litellm/types/router.py | 3 + .../test_responses_api_bridge_flag.py | 107 ++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 tests/test_litellm/responses/test_responses_api_bridge_flag.py diff --git a/litellm/responses/main.py b/litellm/responses/main.py index c82574278ba..1e97951c50c 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -754,6 +754,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) # Convert text_format to text parameter if provided text = ResponsesAPIRequestUtils.convert_text_format_to_text_param( @@ -871,6 +872,7 @@ def responses( if _has_file_search_tool(tools) and ( responses_api_provider_config is None + or use_responses_api_bridge is True or not responses_api_provider_config.supports_native_file_search() ): from litellm.responses.file_search.emulated_handler import ( @@ -919,7 +921,7 @@ def responses( **emulated_kwargs, ) - if responses_api_provider_config is None: + if responses_api_provider_config is None or use_responses_api_bridge 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 4257628e7cb..d608f302492 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -199,6 +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 model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) merge_reasoning_content_in_choices: Optional[bool] = False model_info: Optional[Dict] = None @@ -318,6 +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] ## 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 new file mode 100644 index 00000000000..51c6ea58f34 --- /dev/null +++ b/tests/test_litellm/responses/test_responses_api_bridge_flag.py @@ -0,0 +1,107 @@ +""" +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. +""" + +import os +import sys +from unittest.mock import MagicMock, patch + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +import litellm + + +class TestUseResponsesApiBridgeFlag: + """Test that use_responses_api_bridge forces the chat completions bridge.""" + + @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_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.""" + # Setup: provider config returns a non-None config (native support exists) + mock_get_config.return_value = litellm.OpenAIResponsesAPIConfig() + + mock_bridge_handler.return_value = MagicMock() + + litellm.responses( + model="openai/my-custom-model", + input="Hello", + use_responses_api_bridge=True, + litellm_logging_obj=MagicMock(), + ) + + mock_bridge_handler.assert_called_once() + + @patch("litellm.responses.main.base_llm_http_handler.response_api_handler") + @patch( + "litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config" + ) + def test_native_forwarding_when_flag_absent( + self, mock_get_config, mock_native_handler + ): + """When use_responses_api_bridge is not set, openai/ models should use + native responses API forwarding (existing behavior).""" + mock_get_config.return_value = litellm.OpenAIResponsesAPIConfig() + mock_native_handler.return_value = MagicMock() + + litellm.responses( + model="openai/gpt-4o", + input="Hello", + litellm_logging_obj=MagicMock(), + ) + + mock_native_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_flag_does_not_leak_into_kwargs(self, mock_get_config, mock_bridge_handler): + """The use_responses_api_bridge flag should be popped from kwargs and not + passed through to the bridge handler.""" + mock_get_config.return_value = litellm.OpenAIResponsesAPIConfig() + mock_bridge_handler.return_value = MagicMock() + + litellm.responses( + model="openai/my-custom-model", + input="Hello", + use_responses_api_bridge=True, + litellm_logging_obj=MagicMock(), + ) + + call_kwargs = mock_bridge_handler.call_args + # The flag 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 + + @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_provider_config_none( + self, mock_get_config, mock_bridge_handler + ): + """When the provider has no native responses API config (returns None), + the bridge should be used regardless of the flag (existing behavior).""" + mock_get_config.return_value = None + mock_bridge_handler.return_value = MagicMock() + + litellm.responses( + model="anthropic/claude-3-haiku", + input="Hello", + litellm_logging_obj=MagicMock(), + ) + + mock_bridge_handler.assert_called_once() From 0fbfc96af38410f3657effe37d9f2d3b9305b522 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 30 Mar 2026 16:41:58 +0530 Subject: [PATCH 2/2] docs(responses): add use_responses_api_bridge opt-in bridge docs --- docs/my-website/docs/response_api.md | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/my-website/docs/response_api.md b/docs/my-website/docs/response_api.md index 0c428000c72..36c9ee13515 100644 --- a/docs/my-website/docs/response_api.md +++ b/docs/my-website/docs/response_api.md @@ -1505,6 +1505,64 @@ curl http://localhost:4000/v1/responses \ +### Opt-in bridge for `openai/` models with custom `api_base` + +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. + +#### Python SDK Usage + +```python showLineNumbers title="Force bridge for custom openai/ endpoint" +import litellm + +response = litellm.responses( + model="openai/my-custom-model", + input="Hello!", + api_base="http://localhost:8080", + api_key="fake-key", + use_responses_api_bridge=True, +) + +print(response) +``` + +#### LiteLLM Proxy Usage + +**Setup Config:** + +```yaml showLineNumbers title="config.yaml — bridge for custom openai/ endpoint" +model_list: +- model_name: my-local-model + litellm_params: + model: openai/my-custom-model + api_base: http://localhost:8080/v1 + api_key: fake-key + use_responses_api_bridge: true +``` + +**Start Proxy:** + +```bash showLineNumbers title="Start LiteLLM Proxy" +litellm --config /path/to/config.yaml + +# RUNNING on http://0.0.0.0:4000 +``` + +**Make Request:** + +```bash showLineNumbers title="Request via bridge" +curl http://localhost:4000/v1/responses \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-1234" \ + -d '{ + "model": "my-local-model", + "input": "Hello!" + }' +``` + +This is particularly useful when connecting clients that hardcode the `/responses` endpoint (e.g. OpenAI Codex CLI with `wire_api = "responses"`) to local or third-party OpenAI-compatible providers that only expose `/chat/completions`. + ## Server-side compaction For long-running conversations, you can enable **server-side compaction** so that when the rendered context size crosses a threshold, the server automatically runs compaction in-stream and emits a compaction item—no separate `POST /v1/responses/compact` call is required.