Drop tool_choice for registry-unknown reasoning-effort runs

When the user opts into reasoning_effort but the configured model
isn't in litellm.model_cost at all (private SKUs, fresh releases the
registry hasn't picked up — e.g. deepseek/deepseek-v4-pro), we can't
confirm thinking support and were sending tool_choice="required",
which thinking-mode endpoints reject ("Thinking mode does not support
this tool_choice").

Add model_known_to_registry() and split the decision: when the user
wants reasoning AND the model is either confirmed-reasoning OR
unknown-to-registry, drop tool_choice. The Reasoning(effort=...) param
still only attaches for confirmed-reasoning models, so we don't send
reasoning hints to known non-reasoning models.

Known non-reasoning models (gpt-4o, registry-confirmed) keep
tool_choice="required" unchanged.
This commit is contained in:
0xallam
2026-06-08 00:18:44 +03:00
committed by Ahmed Allam
parent dee2a03d07
commit 712c64f630
2 changed files with 28 additions and 13 deletions
+10 -1
View File
@@ -121,7 +121,7 @@ def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bo
return bool(settings.llm.api_base) return bool(settings.llm.api_base)
def model_supports_reasoning(model_name: str) -> bool: def _model_cost_entry(model_name: str) -> dict[str, object] | None:
import litellm import litellm
name = model_name.strip().lower() name = model_name.strip().lower()
@@ -132,4 +132,13 @@ def model_supports_reasoning(model_name: str) -> bool:
entry = litellm.model_cost.get(name) entry = litellm.model_cost.get(name)
if entry is None and "/" in name: if entry is None and "/" in name:
entry = litellm.model_cost.get(name.rsplit("/", 1)[1]) 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")) 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
+18 -12
View File
@@ -8,7 +8,11 @@ from typing import TYPE_CHECKING, Any
from agents.model_settings import ModelSettings from agents.model_settings import ModelSettings
from openai.types.shared import Reasoning 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_known_to_registry,
model_supports_reasoning,
)
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -111,23 +115,25 @@ def make_model_settings(
*, *,
model_name: str, model_name: str,
) -> ModelSettings: ) -> ModelSettings:
# Anthropic + DeepSeek thinking reject ``tool_choice="required"`` outright # Anthropic + DeepSeek thinking reject ``tool_choice="required"`` outright;
# when reasoning is enabled; OpenAI o-series accepts both but doesn't need # when reasoning is enabled we let the model self-select tools and rely on
# the safety net. When reasoning is on we let the model self-select tools # the system prompt + the ``_finish_tool_use_behavior`` callback to keep
# and rely on the system prompt + the ``_finish_tool_use_behavior`` callback # the loop converging. When the user opted into reasoning but the model
# to keep the loop converging on a lifecycle tool. # is unknown to LiteLLM's registry (e.g. a private DeepSeek SKU, a fresh
use_reasoning = ( # release the registry hasn't picked up), drop ``tool_choice`` too —
reasoning_effort is not None # server-side thinking-mode endpoints reject it and we can't confirm.
and reasoning_effort != "none" user_wants_reasoning = reasoning_effort is not None and reasoning_effort != "none"
and model_supports_reasoning(model_name) confirmed_reasoning = model_supports_reasoning(model_name)
drop_tool_choice = user_wants_reasoning and (
confirmed_reasoning or not model_known_to_registry(model_name)
) )
model_settings = ModelSettings( model_settings = ModelSettings(
parallel_tool_calls=False, parallel_tool_calls=False,
tool_choice=None if use_reasoning else "required", tool_choice=None if drop_tool_choice else "required",
retry=DEFAULT_MODEL_RETRY, retry=DEFAULT_MODEL_RETRY,
include_usage=True, include_usage=True,
) )
if use_reasoning: if user_wants_reasoning and confirmed_reasoning:
model_settings = model_settings.resolve( model_settings = model_settings.resolve(
ModelSettings(reasoning=Reasoning(effort=reasoning_effort)), ModelSettings(reasoning=Reasoning(effort=reasoning_effort)),
) )