Merge pull request #23926 from Chesars/fix/azure-gpt5-4-responses-api-routing

fix(azure): auto-route gpt-5.4+ tools+reasoning to Responses API
This commit is contained in:
Cesar Garcia 2026-03-18 01:14:50 -03:00 • committed by GitHub
commit 6a5b0058e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 74 additions and 24 deletions

View file

@ -594,9 +594,26 @@ Expected Response
:::tip gpt-5.4: reasoning_effort + function tools
LiteLLM drops `reasoning_effort` from `gpt-5.4` requests to `litellm.completion()` that include tools, since that combination is supported in the Responses API.
When `gpt-5.4+` requests to `litellm.completion()` include both `reasoning_effort` and `tools`, LiteLLM **automatically routes** the request through the Responses API bridge. This works for both **OpenAI** (`openai/gpt-5.4`) and **Azure** (`azure/gpt-5.4`) providers — no extra configuration needed.
If you need reasoning **and** tools together, use `openai/responses/gpt-5.4` to route through the Responses API instead. See [Responses API Bridge](/docs/providers/openai#openai-chat-completion-to-responses-api-bridge) for details.
You can also route explicitly via `openai/responses/gpt-5.4` or `azure/responses/gpt-5.4`. See [Responses API Bridge](/docs/providers/openai#openai-chat-completion-to-responses-api-bridge) for details.
**Azure custom deployment names:** Auto-routing relies on the deployment name matching the `gpt-5.4*` pattern. If you use a custom deployment name (e.g. `"my-reasoning-model"`), enable routing via:
**SDK:**
```python
litellm.completion(model="azure/responses/my-reasoning-model", ...)
```
**Proxy config:**
```yaml
model_list:
- model_name: my-reasoning-model
litellm_params:
model: azure/my-reasoning-model
model_info:
mode: responses
```
:::

View file

@ -131,14 +131,9 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
if result_effort == "none" and not supports_none:
result.pop("reasoning_effort")
# Azure Chat Completions: gpt-5.4+ does not support tools + reasoning together.
# Drop reasoning_effort when both are present (OpenAI routes to Responses API; Azure does not).
if self.is_model_gpt_5_4_plus_model(model):
has_tools = bool(
non_default_params.get("tools") or optional_params.get("tools")
)
if has_tools and result_effort not in (None, "none"):
result.pop("reasoning_effort", None)
# Azure gpt-5.4+ with tools + reasoning_effort is now routed to the
# Responses API bridge (same as OpenAI), so we no longer need to drop
# reasoning_effort here. See: responses_api_bridge_check() in main.py.
return result

View file

@ -955,16 +955,6 @@ def responses_api_bridge_check(
model_info["mode"] = "responses"
model = model.replace("responses/", "")
# OpenAI gpt-5.4+ chat-completions calls with both tools + reasoning_effort
# must be bridged to Responses API.
if (
custom_llm_provider == "openai"
and OpenAIGPT5Config.is_model_gpt_5_4_plus_model(model)
and tools
and reasoning_effort is not None
):
model_info["mode"] = "responses"
model = model.replace("responses/", "")
except Exception as e:
verbose_logger.debug("Error getting model info: {}".format(e))
@ -974,6 +964,19 @@ def responses_api_bridge_check(
model = model.replace("responses/", "")
mode = "responses"
model_info["mode"] = mode
# OpenAI/Azure gpt-5.4+ chat-completions calls with both tools + reasoning_effort
# must be bridged to Responses API.
if (
custom_llm_provider in ("openai", "azure")
and OpenAIGPT5Config.is_model_gpt_5_4_plus_model(model)
and tools
and reasoning_effort is not None
and model_info.get("mode") != "responses"
):
model_info["mode"] = "responses"
model = model.replace("responses/", "")
return model_info, model

View file

@ -192,10 +192,11 @@ def test_azure_gpt5_1_series_temperature_handling(config: AzureOpenAIGPT5Config)
assert params["temperature"] == 0.6
def test_azure_gpt5_4_drops_reasoning_effort_when_tools_present(config: AzureOpenAIGPT5Config):
"""Azure Chat Completions: gpt-5.4+ drops reasoning_effort when tools are present.
def test_azure_gpt5_4_preserves_reasoning_effort_when_tools_present(config: AzureOpenAIGPT5Config):
"""Azure GPT-5.4+ no longer drops reasoning_effort when tools are present.
OpenAI routes tools+reasoning to Responses API; Azure does not, so we drop reasoning_effort.
Both OpenAI and Azure now route tools+reasoning to the Responses API bridge,
so reasoning_effort must be preserved in map_openai_params.
"""
tools = [{"type": "function", "function": {"name": "test", "description": "test"}}]
params = config.map_openai_params(
@ -205,7 +206,7 @@ def test_azure_gpt5_4_drops_reasoning_effort_when_tools_present(config: AzureOpe
drop_params=False,
api_version="2024-05-01-preview",
)
assert "reasoning_effort" not in params
assert params.get("reasoning_effort") == "high"
assert params["tools"] == tools

View file

@ -661,6 +661,40 @@ def test_responses_api_bridge_check_gpt_5_5_tools_plus_reasoning_routes_to_respo
assert model_info.get("mode") == "responses"
def test_responses_api_bridge_check_azure_gpt_5_4_tools_plus_reasoning_routes_to_responses():
"""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:
mock_get_model_info.return_value = {"max_tokens": 128000}
model_info, model = responses_api_bridge_check(
model="gpt-5.4",
custom_llm_provider="azure",
tools=[{"type": "function", "function": {"name": "get_capital"}}],
reasoning_effort="high",
)
assert model == "gpt-5.4"
assert model_info.get("mode") == "responses"
def test_responses_api_bridge_check_azure_gpt_5_4_tools_without_reasoning_stays_chat():
"""Azure gpt-5.4 with tools only should not be force-routed to Responses API."""
from litellm.main import responses_api_bridge_check
with patch("litellm.main._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",
custom_llm_provider="azure",
tools=[{"type": "function", "function": {"name": "get_capital"}}],
reasoning_effort=None,
)
assert model == "gpt-5.4"
assert model_info.get("mode") != "responses"
def test_responses_api_bridge_check_gpt_5_4_tools_without_reasoning_stays_chat():
"""gpt-5.4 with tools only should not be force-routed to Responses API."""
from litellm.main import responses_api_bridge_check