From 25674b52f429f8523e43e3b19bff7855f4020645 Mon Sep 17 00:00:00 2001 From: Gabriel Ruel-Fiset Date: Tue, 9 Jun 2026 15:10:05 -0400 Subject: [PATCH] Fix Bedrock reasoning for adaptive-thinking models (Claude Opus 4.8) Models such as Claude Opus 4.8 on Bedrock reject the legacy "thinking.type=enabled" payload that LiteLLM emits when it converts the "reasoning_effort" argument, returning: "thinking.type.enabled" is not supported for this model. Use "thinking.type.adaptive" and "output_config.effort" to control thinking behavior. LiteLLM's model metadata already flags these models with "supports_adaptive_thinking", but its request conversion still sends the old shape. Detect that flag and pass the adaptive-thinking API via extra_args (thinking.type=adaptive + output_config.effort) instead of the reasoning_effort scalar. All other models keep their existing behavior. --- strix/config/models.py | 18 +++++++++++++++++- strix/core/inputs.py | 25 +++++++++++++++++++++---- 2 files changed, 38 insertions(+), 5 deletions(-) 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