diff --git a/strix/config/models.py b/strix/config/models.py index f5826433..1f480ba7 100644 --- a/strix/config/models.py +++ b/strix/config/models.py @@ -138,7 +138,7 @@ def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bo return not model_supports_reasoning(model_name) -def model_supports_reasoning(model_name: str) -> bool: +def _model_cost_entry(model_name: str) -> dict[str, object] | None: import litellm name = model_name.strip().lower() @@ -149,9 +149,25 @@ def model_supports_reasoning(model_name: str) -> bool: 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_supports_adaptive_thinking(model_name: str) -> bool: + """Whether the model uses Anthropic's newer adaptive-thinking API. + + Models like Claude Opus 4.8 reject the legacy ``thinking.type=enabled`` shape + that LiteLLM emits when it converts ``reasoning_effort``. They require + ``thinking.type=adaptive`` plus ``output_config.effort`` instead. + """ + entry = _model_cost_entry(model_name) + return bool(entry and entry.get("supports_adaptive_thinking")) + + def is_known_openai_bare_model(model_name: str) -> bool: import litellm diff --git a/strix/core/inputs.py b/strix/core/inputs.py index b86daa4c..9bdc4d9a 100644 --- a/strix/core/inputs.py +++ b/strix/core/inputs.py @@ -8,7 +8,11 @@ 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_supports_reasoning +from strix.config.models import ( + DEFAULT_MODEL_RETRY, + model_supports_adaptive_thinking, + model_supports_reasoning, +) if TYPE_CHECKING: @@ -121,9 +125,22 @@ def make_model_settings( and reasoning_effort != "none" and model_supports_reasoning(model_name) ): - model_settings = model_settings.resolve( - ModelSettings(reasoning=Reasoning(effort=reasoning_effort)), - ) + if model_supports_adaptive_thinking(model_name): + # Newer Anthropic models (e.g. Claude Opus 4.8) reject the legacy + # ``thinking.type=enabled`` shape that LiteLLM emits from + # ``reasoning_effort``. Send the adaptive-thinking API instead. + model_settings = model_settings.resolve( + ModelSettings( + extra_args={ + "thinking": {"type": "adaptive"}, + "output_config": {"effort": reasoning_effort}, + }, + ), + ) + else: + model_settings = model_settings.resolve( + ModelSettings(reasoning=Reasoning(effort=reasoning_effort)), + ) return model_settings