Reserve reasoning for responses via chat completion

This commit is contained in:
Sameer Kankute
2026-03-13 14:19:40 +05:30
parent 288b08fd25
commit 9b3ffd04ef
4 changed files with 54 additions and 19 deletions
@@ -188,11 +188,9 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
) or optional_params.get("reasoning_effort")
effective_effort = _get_effort_level(raw_reasoning_effort)
# Normalize to string for Chat Completions API when dict has only "effort".
# Preserve full dict (e.g. {"effort": "high", "summary": "detailed"}) for Responses API.
if isinstance(raw_reasoning_effort, dict) and set(
raw_reasoning_effort.keys()
) <= {"effort"}:
# Normalize dict reasoning_effort to string for Chat Completions API.
# Example: {"effort": "high", "summary": "detailed"} -> "high"
if isinstance(raw_reasoning_effort, dict) and "effort" in raw_reasoning_effort:
normalized = _normalize_reasoning_effort_for_chat_completion(
raw_reasoning_effort
)
+5 -1
View File
@@ -955,7 +955,7 @@ 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
# OpenAI gpt-5.4+ chat-completions calls with both tools + reasoning_effort
# must be bridged to Responses API.
if (
custom_llm_provider == "openai"
@@ -1617,6 +1617,10 @@ def completion( # type: ignore # noqa: PLR0915
if model_info.get("mode") == "responses":
from litellm.completion_extras import responses_api_bridge
if isinstance(reasoning_effort, dict) and "summary" in reasoning_effort:
optional_params = dict(optional_params)
optional_params["reasoning_effort"] = reasoning_effort
return responses_api_bridge.completion(
model=model,
messages=messages,
@@ -324,19 +324,15 @@ def test_gpt5_4_pro_allows_reasoning_effort_xhigh(config: OpenAIConfig):
assert params["reasoning_effort"] == "xhigh"
def test_gpt5_preserves_reasoning_effort_dict_with_summary(config: OpenAIConfig):
"""Dict with summary/generate_summary is preserved for Responses API.
Config/deployments may pass Responses API format: {'effort': 'high', 'summary': 'detailed'}.
We preserve the full dict so it reaches the Responses API transformation.
"""
def test_gpt5_normalizes_reasoning_effort_dict_with_summary(config: OpenAIConfig):
"""Dict with summary/generate_summary is normalized for chat completions."""
params = config.map_openai_params(
non_default_params={"reasoning_effort": {"effort": "high", "summary": "detailed"}},
optional_params={},
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == {"effort": "high", "summary": "detailed"}
assert params["reasoning_effort"] == "high"
def test_gpt5_xhigh_dict_triggers_validation(config: OpenAIConfig):
@@ -362,7 +358,7 @@ def test_gpt5_xhigh_dict_accepted_for_supported_model(config: OpenAIConfig):
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == {"effort": "xhigh", "summary": "detailed"}
assert params["reasoning_effort"] == "xhigh"
def test_gpt5_none_dict_with_tools_no_tool_drop(config: OpenAIConfig):
@@ -378,7 +374,7 @@ def test_gpt5_none_dict_with_tools_no_tool_drop(config: OpenAIConfig):
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == {"effort": "none", "summary": "detailed"}
assert params["reasoning_effort"] == "none"
assert params["tools"] == tools
@@ -398,20 +394,20 @@ def test_gpt5_none_dict_with_sampling_params_allowed(config: OpenAIConfig):
model="gpt-5.1",
drop_params=False,
)
assert params["reasoning_effort"] == {"effort": "none", "summary": "detailed"}
assert params["reasoning_effort"] == "none"
assert params["logprobs"] is True
assert params["top_p"] == 0.9
def test_gpt5_preserves_reasoning_effort_dict_with_summary_from_optional_params(config: OpenAIConfig):
"""reasoning_effort dict with summary in optional_params is preserved."""
def test_gpt5_normalizes_reasoning_effort_dict_with_summary_from_optional_params(config: OpenAIConfig):
"""reasoning_effort dict with summary in optional_params is normalized."""
params = config.map_openai_params(
non_default_params={},
optional_params={"reasoning_effort": {"effort": "medium", "summary": "detailed"}},
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == {"effort": "medium", "summary": "detailed"}
assert params["reasoning_effort"] == "medium"
def test_gpt5_4_pro_rejects_non_default_temperature(config: OpenAIConfig):
+37
View File
@@ -678,6 +678,43 @@ def test_responses_api_bridge_check_gpt_5_4_tools_without_reasoning_stays_chat()
assert model_info.get("mode") != "responses"
@patch("litellm.completion_extras.responses_api_bridge.completion")
def test_gpt_5_4_responses_bridge_preserves_reasoning_summary_dict(
mock_responses_completion,
):
"""When routed to Responses, preserve reasoning_effort summary dict."""
mock_responses_completion.return_value = MagicMock()
import litellm
litellm.completion(
model="gpt-5.4",
messages=[{"role": "user", "content": "What is the capital of France?"}],
tools=[
{
"type": "function",
"function": {
"name": "get_capital",
"description": "Get the capital of a country",
"parameters": {
"type": "object",
"properties": {"country": {"type": "string"}},
},
},
}
],
reasoning_effort={"effort": "xhigh", "summary": "detailed"},
api_key="fake-key",
)
assert mock_responses_completion.called is True
optional_params = mock_responses_completion.call_args.kwargs["optional_params"]
assert optional_params["reasoning_effort"] == {
"effort": "xhigh",
"summary": "detailed",
}
def test_responses_api_bridge_check_handles_exception():
"""Test that responses_api_bridge_check handles exceptions and still processes responses/ models."""
from litellm.main import responses_api_bridge_check