mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Merge pull request #24783 from Sameerlite/Sameerlite/responses-bridge-optin
feat(responses): add use_responses_api_bridge flag for openai/ models with custom api_base
This commit is contained in:
commit
8394167711
4 changed files with 171 additions and 1 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 ##
|
||||
|
|
|
|||
107
tests/test_litellm/responses/test_responses_api_bridge_flag.py
Normal file
107
tests/test_litellm/responses/test_responses_api_bridge_flag.py
Normal file
|
|
@ -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()
|
||||
Loading…
Add table
Reference in a new issue