From 13046cc74ae07a86da89f5037592c574c0c490ce Mon Sep 17 00:00:00 2001 From: Ahmed Allam <49919286+0xallam@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:24:48 -0700 Subject: [PATCH] fix: gate reasoning_effort by LiteLLM model registry (closes #517) (#523) Co-authored-by: Claude Opus 4.7 --- strix/config/models.py | 14 ++++++++++++++ strix/core/inputs.py | 10 ++++++++-- strix/core/runner.py | 5 ++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/strix/config/models.py b/strix/config/models.py index 9f1bdae..89ec38c 100644 --- a/strix/config/models.py +++ b/strix/config/models.py @@ -99,3 +99,17 @@ def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bo if model.startswith(("litellm/", "any-llm/")): return True return bool(settings.llm.api_base) + + +def model_supports_reasoning(model_name: str) -> bool: + import litellm + + name = model_name.strip().lower() + for prefix in ("litellm/", "any-llm/"): + if name.startswith(prefix): + name = name[len(prefix) :] + break + entry = litellm.model_cost.get(name) + if entry is None and "/" in name: + entry = litellm.model_cost.get(name.rsplit("/", 1)[1]) + return bool(entry and entry.get("supports_reasoning")) diff --git a/strix/core/inputs.py b/strix/core/inputs.py index 0913726..148bf09 100644 --- a/strix/core/inputs.py +++ b/strix/core/inputs.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any from agents.model_settings import ModelSettings from openai.types.shared import Reasoning -from strix.config.models import DEFAULT_MODEL_RETRY +from strix.config.models import DEFAULT_MODEL_RETRY, model_supports_reasoning if TYPE_CHECKING: @@ -108,13 +108,19 @@ def build_scope_context(scan_config: dict[str, Any]) -> dict[str, Any]: def make_model_settings( reasoning_effort: ReasoningEffort | None, + *, + model_name: str, ) -> ModelSettings: # Anthropic + DeepSeek thinking reject ``tool_choice="required"`` outright # when reasoning is enabled; OpenAI o-series accepts both but doesn't need # the safety net. When reasoning is on we let the model self-select tools # and rely on the system prompt + the ``_finish_tool_use_behavior`` callback # to keep the loop converging on a lifecycle tool. - use_reasoning = reasoning_effort is not None and reasoning_effort != "none" + use_reasoning = ( + reasoning_effort is not None + and reasoning_effort != "none" + and model_supports_reasoning(model_name) + ) model_settings = ModelSettings( parallel_tool_calls=False, tool_choice=None if use_reasoning else "required", diff --git a/strix/core/runner.py b/strix/core/runner.py index 7592c8f..e69e632 100644 --- a/strix/core/runner.py +++ b/strix/core/runner.py @@ -153,7 +153,10 @@ async def run_strix_scan( is_whitebox = any(t.get("type") == "local_code" for t in targets) skills = list(scan_config.get("skills") or []) root_task = build_root_task(scan_config) - model_settings = make_model_settings(settings.llm.reasoning_effort) + model_settings = make_model_settings( + settings.llm.reasoning_effort, + model_name=resolved_model, + ) run_config = RunConfig( model=resolved_model, model_settings=model_settings,