diff --git a/litellm/llms/openai/chat/gpt_5_transformation.py b/litellm/llms/openai/chat/gpt_5_transformation.py index 3f33d6183f..e3b3805023 100644 --- a/litellm/llms/openai/chat/gpt_5_transformation.py +++ b/litellm/llms/openai/chat/gpt_5_transformation.py @@ -223,16 +223,6 @@ class OpenAIGPT5Config(OpenAIGPTConfig): "max_tokens" ) - # gpt-5.4: reasoning_effort + tools is only supported in the Responses API - # Drop reasoning_effort when tools are present in chat completions - if self.is_model_gpt_5_4_model(model): - has_tools = bool( - non_default_params.get("tools") or optional_params.get("tools") - ) - if has_tools and effective_effort is not None: - non_default_params.pop("reasoning_effort", None) - optional_params.pop("reasoning_effort", None) - # gpt-5.1/5.2 support logprobs, top_p, top_logprobs only when reasoning_effort="none" supports_none = self._supports_reasoning_effort_level(model, "none") if supports_none: diff --git a/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py b/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py index 0f743b1a93..81ffa1aa42 100644 --- a/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py +++ b/tests/test_litellm/llms/openai/chat/test_openai_gpt_transformation.py @@ -9,6 +9,7 @@ import pytest sys.path.insert(0, os.path.abspath("../../../../..")) +from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config from litellm.llms.openai.chat.gpt_transformation import ( OpenAIChatCompletionStreamingHandler, OpenAIGPTConfig, diff --git a/tests/test_litellm/llms/openai/test_gpt5_transformation.py b/tests/test_litellm/llms/openai/test_gpt5_transformation.py index 7c731e4e00..d954798006 100644 --- a/tests/test_litellm/llms/openai/test_gpt5_transformation.py +++ b/tests/test_litellm/llms/openai/test_gpt5_transformation.py @@ -324,11 +324,10 @@ 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. +def test_gpt5_normalizes_reasoning_effort_dict_to_string(config: OpenAIConfig): + """Chat completion API expects reasoning_effort as a string, not a dict. Config/deployments may pass Responses API format: {'effort': 'high', 'summary': 'detailed'}. - We preserve the full dict so it reaches the Responses API transformation. """ params = config.map_openai_params( non_default_params={"reasoning_effort": {"effort": "high", "summary": "detailed"}}, @@ -366,10 +365,10 @@ def test_gpt5_xhigh_dict_accepted_for_supported_model(config: OpenAIConfig): def test_gpt5_none_dict_with_tools_no_tool_drop(config: OpenAIConfig): - """Dict with effort='none' and tools: reasoning_effort dropped for gpt-5.4. + """Dict with effort='none' and tools: no tool-drop, reasoning_effort preserved. - gpt-5.4 drops all reasoning_effort when tools are present, - since that combination is only supported in the Responses API. + Regression: effective_effort='none' must be used for tool-drop guard so + {"effort": "none", "summary": "detailed"} is not incorrectly treated as non-none. """ tools = [{"type": "function", "function": {"name": "test", "description": "test"}}] params = config.map_openai_params( @@ -378,7 +377,7 @@ def test_gpt5_none_dict_with_tools_no_tool_drop(config: OpenAIConfig): model="gpt-5.4", drop_params=False, ) - assert "reasoning_effort" not in params + assert params["reasoning_effort"] == {"effort": "none", "summary": "detailed"} assert params["tools"] == tools @@ -411,10 +410,10 @@ def test_gpt5_preserves_reasoning_effort_dict_with_summary_from_optional_params( model="gpt-5.4", drop_params=False, ) - assert params["reasoning_effort"] == {"effort": "medium", "summary": "detailed"} + assert params["reasoning_effort"] == "medium" -def test_gpt5_4_drops_reasoning_effort_when_user_sends_reasoning_and_tools(config: OpenAIConfig): +def test_gpt5_4_drops_reasoning_effort_when_tools_present(config: OpenAIConfig): """gpt-5.4: function calls not supported with reasoning_effort != 'none'. Drop reasoning_effort.""" tools = [{"type": "function", "function": {"name": "test", "description": "test"}}] params = config.map_openai_params( @@ -438,8 +437,8 @@ def test_gpt5_4_keeps_reasoning_effort_when_no_tools(config: OpenAIConfig): assert params["reasoning_effort"] == "high" -def test_gpt5_4_drops_reasoning_effort_none_with_tools(config: OpenAIConfig): - """reasoning_effort='none' is also dropped when tools are present for gpt-5.4.""" +def test_gpt5_4_keeps_reasoning_effort_none_with_tools(config: OpenAIConfig): + """reasoning_effort='none' is kept when tools are present.""" tools = [{"type": "function", "function": {"name": "test", "description": "test"}}] params = config.map_openai_params( non_default_params={"reasoning_effort": "none", "tools": tools}, @@ -447,7 +446,7 @@ def test_gpt5_4_drops_reasoning_effort_none_with_tools(config: OpenAIConfig): model="gpt-5.4", drop_params=False, ) - assert "reasoning_effort" not in params + assert params["reasoning_effort"] == "none" assert params["tools"] == tools