diff --git a/lib/crewai/src/crewai/llms/providers/openai/completion.py b/lib/crewai/src/crewai/llms/providers/openai/completion.py index e2d4c3e40..478cec15e 100644 --- a/lib/crewai/src/crewai/llms/providers/openai/completion.py +++ b/lib/crewai/src/crewai/llms/providers/openai/completion.py @@ -1929,14 +1929,15 @@ class OpenAICompletion(BaseLLM): # request; the first occurrence is recovered in `_call_completions`. # Checked after the tools block on purpose: `reasoning_effort` can also # arrive through `additional_params`, which bypasses the typed field. + # An absent `reasoning_effort` is not safe either: these models apply a + # server-side default, so sending nothing is rejected exactly like sending + # "high". Only an explicit "none" is accepted alongside tools. if ( params.get("tools") - and params.get("reasoning_effort") not in (None, "none") + and params.get("reasoning_effort") != "none" and not self._supports_reasoning_effort_with_tools(self.model) ): - requested = params["reasoning_effort"] - # Explicit "none" rather than removing the key: gpt-5.6-* apply a - # server-side default, so omitting it fails the same way. + requested = params.get("reasoning_effort") params["reasoning_effort"] = "none" logging.debug( 'Forcing reasoning_effort="none" (requested %r) for model %r: ' diff --git a/lib/crewai/tests/llms/openai/test_gpt56_tools_reasoning_effort.py b/lib/crewai/tests/llms/openai/test_gpt56_tools_reasoning_effort.py index 0e25beaa9..b612c87f6 100644 --- a/lib/crewai/tests/llms/openai/test_gpt56_tools_reasoning_effort.py +++ b/lib/crewai/tests/llms/openai/test_gpt56_tools_reasoning_effort.py @@ -108,6 +108,20 @@ class TestLearnedFastPath: assert params["tools"] assert params["tool_choice"] == "auto" + def test_absent_reasoning_effort_is_also_forced_to_none(self): + """Sending nothing is rejected exactly like sending "high". + + These models apply a server-side default, so a learned model with no + `reasoning_effort` set would 400 on every call and never benefit from the + cache. + """ + llm = build("gpt-5.6-sol") + llm._remember_reasoning_effort_conflict() + + params = llm._prepare_completion_params(MESSAGES, tools=TOOLS) + + assert params["reasoning_effort"] == "none" + def test_learning_applies_to_the_additional_params_leak(self): """additional_params bypasses the typed field, so it must be checked too.""" llm = build("gpt-5.5", additional_params={"reasoning_effort": "medium"})