From 2e7040240d201f433d0fe42f922dbfbe953c6c8b Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:10:01 -0700 Subject: [PATCH] feat(config): accept STRIX_REASONING_EFFORT=max for providers that support it (#956) Co-authored-by: Ahmed Allam --- docs/advanced/configuration.mdx | 2 +- strix/config/models.py | 11 +++++++---- strix/config/settings.py | 2 +- strix/core/inputs.py | 18 +++++++++++++++++- strix/interface/main.py | 4 ++-- tests/test_inputs.py | 11 +++++++++++ 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/docs/advanced/configuration.mdx b/docs/advanced/configuration.mdx index af98b8b8..027f6d47 100644 --- a/docs/advanced/configuration.mdx +++ b/docs/advanced/configuration.mdx @@ -36,7 +36,7 @@ Configure Strix using environment variables or a config file. - Control thinking effort for reasoning models. Valid values: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`. Defaults to `medium` for quick scan mode. + Control thinking effort for reasoning models. Valid values: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Defaults to `medium` for quick scan mode. diff --git a/strix/config/models.py b/strix/config/models.py index 1c84ef3c..a3bb481a 100644 --- a/strix/config/models.py +++ b/strix/config/models.py @@ -83,10 +83,13 @@ class _CodexResponsesModel(OpenAIResponsesModel): effort = self._reasoning_effort if effort and effort != "none": # Clamp to efforts the backend accepts. - if effort == "minimal": - effort = "low" - elif effort == "xhigh": - effort = "high" + match effort: + case "minimal": + effort = "low" + case "xhigh" | "max": + effort = "high" + case _: + pass overrides = overrides.resolve(ModelSettings(reasoning=Reasoning(effort=effort))) return model_settings.resolve(overrides) diff --git a/strix/config/settings.py b/strix/config/settings.py index e53d125c..0f26cd62 100644 --- a/strix/config/settings.py +++ b/strix/config/settings.py @@ -8,7 +8,7 @@ from pydantic import AliasChoices, Field from pydantic_settings import BaseSettings, SettingsConfigDict -ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh"] +ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"] _BASE_CONFIG = SettingsConfigDict( case_sensitive=False, diff --git a/strix/core/inputs.py b/strix/core/inputs.py index 34a2d4b3..81f46076 100644 --- a/strix/core/inputs.py +++ b/strix/core/inputs.py @@ -147,7 +147,7 @@ def make_model_settings( and model_supports_reasoning(model_name) ): model_settings = model_settings.resolve( - ModelSettings(reasoning=Reasoning(effort=reasoning_effort)), + _reasoning_settings(reasoning_effort, model_settings.extra_args), ) if force_required_tool_choice and _accepts_required_tool_choice(model_name): model_settings = model_settings.resolve(ModelSettings(tool_choice="required")) @@ -162,6 +162,22 @@ def make_model_settings( return model_settings +def _reasoning_settings( + effort: ReasoningEffort, + extra_args: dict[str, Any] | None, +) -> ModelSettings: + """``max`` is not in the OpenAI SDK's ``Reasoning.effort`` enum, so send it as + a raw body field instead — also keeping it clear of LiteLLM's DeepSeek mapping, + which collapses every ``reasoning_effort`` level to plain thinking-enabled. + Providers that don't support ``max`` reject the request. + """ + if effort != "max": + return ModelSettings(reasoning=Reasoning(effort=effort)) + return ModelSettings( + extra_args={**(extra_args or {}), "extra_body": {"reasoning_effort": "max"}}, + ) + + def _prompt_cache_extra_args(model_name: str) -> dict[str, Any] | None: """LiteLLM ``cache_control_injection_points`` for Claude prompt caching. diff --git a/strix/interface/main.py b/strix/interface/main.py index 4d88beda..abe77fa3 100644 --- a/strix/interface/main.py +++ b/strix/interface/main.py @@ -171,8 +171,8 @@ def validate_environment() -> None: error_text.append("• ", style="white") error_text.append("STRIX_REASONING_EFFORT", style="bold cyan") error_text.append( - " - Reasoning effort level: none, minimal, low, medium, high, xhigh " - "(default: high)\n", + " - Reasoning effort level: none, minimal, low, medium, high, xhigh, " + "max (default: high)\n", style="white", ) diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 871ea149..83a431ad 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -129,6 +129,17 @@ def test_prompt_cache_kept_for_non_bedrock_claude_even_if_unmapped(monkeypatch: ] +def test_max_reasoning_effort_sent_as_raw_body_field() -> None: + # "max" is absent from the OpenAI SDK's Reasoning enum, and LiteLLM's DeepSeek + # mapping collapses every effort to thinking-enabled, so it has to ride along + # as a raw body field to reach the provider. + settings = make_model_settings( + "max", model_name="deepseek/deepseek-v4-flash", request_timeout=30 + ) + assert settings.reasoning is None + assert settings.extra_args == {"timeout": 30, "extra_body": {"reasoning_effort": "max"}} + + def test_conversation_tail_breakpoint_moves_with_appended_transcript() -> None: # LiteLLM must place the index=-1 cache_control on the last message however # long the transcript grows.