mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-05 18:24:49 +00:00
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> 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
This commit is contained in:
@@ -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/<model_name>`** — 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"
|
||||
|
||||
@@ -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/<name>` → `openai/<name>` 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,
|
||||
|
||||
@@ -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 ##
|
||||
|
||||
@@ -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>` 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/<name>` normalizes to `openai/<name>` 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user