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:
+10
-1
@@ -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
@@ -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)),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user