diff --git a/strix/agents/factory.py b/strix/agents/factory.py index f559033..6a5af17 100644 --- a/strix/agents/factory.py +++ b/strix/agents/factory.py @@ -395,7 +395,6 @@ def build_strix_agent( instructions=instructions, tools=tools, tool_use_behavior=_finish_tool_use_behavior, - reset_tool_choice=interactive, model=None, capabilities=[ Filesystem( diff --git a/strix/config/models.py b/strix/config/models.py index 7f949b4..feedbe7 100644 --- a/strix/config/models.py +++ b/strix/config/models.py @@ -59,12 +59,7 @@ DEFAULT_MODEL_RETRY = ModelRetrySettings( def configure_sdk_model_defaults(settings: Settings) -> None: - """Apply Strix config to SDK-native defaults. - - OpenAI-compatible base URLs are handled by the SDK OpenAI provider. - Non-OpenAI providers should use the SDK's native ``litellm/`` or - ``any-llm/`` routing, produced by :func:`normalize_model_name`. - """ + """Apply Strix config to SDK-native defaults.""" llm = settings.llm set_tracing_disabled(True) _configure_litellm_compatibility() @@ -85,7 +80,7 @@ def _mirror_api_key_to_provider_env(model_name: str | None, api_key: str) -> Non return import litellm - name = normalize_model_name(model_name) + name = model_name.strip() for prefix in ("litellm/", "any-llm/"): if name.lower().startswith(prefix): name = name[len(prefix) :] @@ -114,50 +109,9 @@ def _configure_litellm_default(name: str, value: str) -> None: setattr(litellm, name, value) -def normalize_model_name(model_name: str) -> str: - """Normalize friendly Strix model names to SDK-native model ids.""" - model = model_name.strip() - if not model: - return model - - if "/" in model: - return model - - lower = model.lower() - if lower.startswith("claude"): - return f"anthropic/{model}" - if lower.startswith("gemini"): - return f"gemini/{model}" - - return model - - def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bool: """Return whether the resolved SDK route can only receive JSON function tools.""" model = model_name.strip().lower() if "/" in model and not model.startswith("openai/"): return True return bool(settings.llm.api_base) - - -def _model_cost_entry(model_name: str) -> dict[str, object] | None: - 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 entry - - -def model_supports_reasoning(model_name: str) -> bool: - entry = _model_cost_entry(model_name) - return bool(entry and entry.get("supports_reasoning")) - - -def model_known_to_registry(model_name: str) -> bool: - return _model_cost_entry(model_name) is not None diff --git a/strix/core/inputs.py b/strix/core/inputs.py index 58da3e6..b82d888 100644 --- a/strix/core/inputs.py +++ b/strix/core/inputs.py @@ -8,11 +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, - model_known_to_registry, - model_supports_reasoning, -) +from strix.config.models import DEFAULT_MODEL_RETRY if TYPE_CHECKING: @@ -110,30 +106,13 @@ 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 we let the model self-select tools and rely on - # the system prompt + the ``_finish_tool_use_behavior`` callback to keep - # the loop converging. When the user opted into reasoning but the model - # is unknown to LiteLLM's registry (e.g. a private DeepSeek SKU, a fresh - # release the registry hasn't picked up), drop ``tool_choice`` too — - # server-side thinking-mode endpoints reject it and we can't confirm. - user_wants_reasoning = reasoning_effort is not None and reasoning_effort != "none" - confirmed_reasoning = model_supports_reasoning(model_name) - drop_tool_choice = user_wants_reasoning and ( - confirmed_reasoning or not model_known_to_registry(model_name) - ) +def make_model_settings(reasoning_effort: ReasoningEffort | None) -> ModelSettings: model_settings = ModelSettings( parallel_tool_calls=False, - tool_choice=None if drop_tool_choice else "required", retry=DEFAULT_MODEL_RETRY, include_usage=True, ) - if user_wants_reasoning and confirmed_reasoning: + if reasoning_effort is not None and reasoning_effort != "none": model_settings = model_settings.resolve( ModelSettings(reasoning=Reasoning(effort=reasoning_effort)), ) diff --git a/strix/core/runner.py b/strix/core/runner.py index 4c0c3fb..d042cdf 100644 --- a/strix/core/runner.py +++ b/strix/core/runner.py @@ -17,7 +17,6 @@ from strix.config import load_settings from strix.config.models import ( StrixProvider, configure_sdk_model_defaults, - normalize_model_name, uses_chat_completions_tool_schema, ) from strix.core.agents import AgentCoordinator @@ -91,7 +90,7 @@ async def run_strix_scan( settings = load_settings() configure_sdk_model_defaults(settings) - resolved_model = normalize_model_name(model or settings.llm.model or "") + resolved_model = (model or settings.llm.model or "").strip() if not resolved_model: raise RuntimeError( "No LLM model configured. Set STRIX_LLM env or pass model= to run_strix_scan().", @@ -154,10 +153,7 @@ 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_name=resolved_model, - ) + model_settings = make_model_settings(settings.llm.reasoning_effort) run_config = RunConfig( model=resolved_model, model_provider=StrixProvider(), diff --git a/strix/interface/main.py b/strix/interface/main.py index 8ef2fd2..ac3a86f 100644 --- a/strix/interface/main.py +++ b/strix/interface/main.py @@ -5,7 +5,6 @@ Strix Agent Interface import argparse import asyncio -import contextlib import shutil import sys from datetime import UTC, datetime @@ -23,7 +22,7 @@ from strix.config import ( load_settings, persist_current, ) -from strix.config.models import StrixProvider, configure_sdk_model_defaults, normalize_model_name +from strix.config.models import StrixProvider, configure_sdk_model_defaults from strix.core.paths import run_dir_for, runtime_state_dir from strix.interface.cli import run_cli from strix.interface.tui import run_tui @@ -216,7 +215,7 @@ async def warm_up_llm() -> None: configure_sdk_model_defaults(settings) llm = settings.llm - model = StrixProvider().get_model(normalize_model_name(llm.model or "")) + model = StrixProvider().get_model((llm.model or "").strip()) await asyncio.wait_for( model.get_response( system_instructions="You are a helpful assistant.", @@ -232,7 +231,7 @@ async def warm_up_llm() -> None: ), timeout=llm.timeout, ) - logger.info("LLM warm-up succeeded for model %s", normalize_model_name(llm.model or "")) + logger.info("LLM warm-up succeeded for model %s", (llm.model or "").strip()) except Exception as e: logger.exception("LLM warm-up failed") @@ -243,24 +242,6 @@ async def warm_up_llm() -> None: error_text.append("Please check your configuration and try again.\n", style="white") error_text.append(f"\nError: {e}", style="dim white") - raw_model = "" - resolved = "" - with contextlib.suppress(Exception): - raw_model = (load_settings().llm.model or "").strip() - resolved = normalize_model_name(raw_model) - err_lc = str(e).lower() - unprefixed = bool(resolved) and "/" not in resolved - looks_openai = "platform.openai.com" in err_lc or "openai" in err_lc - if unprefixed and looks_openai: - error_text.append( - f"\n\nHint: '{raw_model}' has no provider prefix, so the SDK " - f"routed it through OpenAI by default. For non-OpenAI providers " - f"use the '/' form, e.g. " - f"'anthropic/claude-opus-4-7', 'deepseek/deepseek-reasoner', " - f"'openai/gpt-5.4'.", - style="yellow", - ) - panel = Panel( error_text, title="[bold white]STRIX", diff --git a/strix/report/dedupe.py b/strix/report/dedupe.py index af468a9..ca2b1fa 100644 --- a/strix/report/dedupe.py +++ b/strix/report/dedupe.py @@ -8,14 +8,13 @@ from typing import TYPE_CHECKING, Any from agents.model_settings import ModelSettings from agents.models.interface import ModelTracing -from agents.models.multi_provider import MultiProvider from openai.types.responses import ResponseOutputMessage from strix.config import load_settings from strix.config.models import ( DEFAULT_MODEL_RETRY, + StrixProvider, configure_sdk_model_defaults, - normalize_model_name, ) from strix.report.state import get_global_report_state @@ -188,8 +187,8 @@ async def check_duplicate( ) configure_sdk_model_defaults(settings) - resolved_model = normalize_model_name(model_name) - model = MultiProvider().get_model(resolved_model) + resolved_model = model_name.strip() + model = StrixProvider().get_model(resolved_model) response = await model.get_response( system_instructions=DEDUPE_SYSTEM_PROMPT, input=user_msg,