Consolidates #4162 + #3944. Aux path: skip the reasoning-disable inject for routes that 400 on it via _route_rejects_reasoning_extra — hostname-matched OpenAI / Azure OpenAI / Azure AI Foundry (+ provider aliases openai*/azure*/ azure-foundry/azure-ai*) and OpenRouter Anthropic mandatory-reasoning; keep it for local/OpenRouter-non-anthropic/other reasoning routes (#4161). Agent path: rely on the existing agent.reasoning_config={enabled:False} + _build_api_kwargs() (route-correct per provider profile) rather than re-injecting a generic disable on top of it; only MiniMax reasoning_split is added (unchanged from master). Co-authored-by: Rod Boev <rod.boev@gmail.com>
This commit is contained in:
1 parent
dfb4007ae8
commit
bc5fceb88a
3 files changed
+123
-5
No files matched your search
+61
-5
@@ -2413,6 +2413,48 @@ def _is_minimax_route(provider: str = '', model: str = '', base_url: str = '') -
|
||||
return 'minimax' in text or 'minimaxi.com' in text
|
||||
|
||||
|
||||
def _route_rejects_reasoning_extra(provider: str = '', model: str = '', base_url: str = '') -> bool:
|
||||
"""Routes known to reject an ``extra_body`` ``reasoning`` parameter with HTTP 400.
|
||||
|
||||
Title generation injects ``extra_body={"reasoning": {"enabled": False}}`` to
|
||||
suppress thinking on reasoning-capable models (#2083). But OpenAI Chat
|
||||
Completions (and Azure OpenAI) reject unknown top-level params with a 400, so
|
||||
that inject silently fails the title call and falls back to a low-quality
|
||||
heuristic title (#4161). Skip the inject for those routes.
|
||||
|
||||
OpenRouter Anthropic mandatory-reasoning models (Claude Sonnet 4.6 / Opus 4.8)
|
||||
are reasoning-capable but reject a reasoning *disable* — title gen only needs
|
||||
reasoning off, so skip the inject for them too rather than risk the same 400.
|
||||
"""
|
||||
provider_lower = str(provider or '').strip().lower()
|
||||
model_lower = str(model or '').strip().lower()
|
||||
# Hostname-based match (not substring) so a proxy URL that merely *contains*
|
||||
# one of these strings in a path segment isn't mis-classified.
|
||||
host = ''
|
||||
try:
|
||||
from urllib.parse import urlsplit
|
||||
host = (urlsplit(str(base_url or '').strip()).hostname or '').lower()
|
||||
except Exception:
|
||||
host = ''
|
||||
if host == 'api.openai.com' or host.endswith('.openai.azure.com'):
|
||||
return True
|
||||
# Azure AI Foundry chat-completions hosts (also reject the reasoning param).
|
||||
if host.endswith('.services.ai.azure.com') or host.endswith('.cognitiveservices.azure.com'):
|
||||
return True
|
||||
if provider_lower in ('openai', 'openai-api', 'openai-codex'):
|
||||
return True
|
||||
if (
|
||||
provider_lower in ('azure', 'azure-foundry', 'azure-ai-foundry', 'azure-ai')
|
||||
or provider_lower.startswith('azure/')
|
||||
or provider_lower.startswith('azure-')
|
||||
):
|
||||
return True
|
||||
if (host == 'openrouter.ai' or host.endswith('.openrouter.ai')) and model_lower.startswith('anthropic/'):
|
||||
# Anthropic on OpenRouter: mandatory-reasoning families reject a disable.
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_aux_title_config() -> dict:
|
||||
"""Return title_generation auxiliary config, or an empty dict on errors."""
|
||||
try:
|
||||
@@ -2594,7 +2636,9 @@ def generate_title_raw_via_aux(
|
||||
if not caller_supplied_route:
|
||||
api_key = str(configured.get('api_key', '') or '').strip()
|
||||
base_max_tokens = _title_completion_budget(provider, model, base_url)
|
||||
reasoning_extra = {"reasoning": {"enabled": False}}
|
||||
reasoning_extra = {}
|
||||
if not _route_rejects_reasoning_extra(provider, model, base_url):
|
||||
reasoning_extra["reasoning"] = {"enabled": False}
|
||||
if _is_minimax_route(provider, model, base_url):
|
||||
reasoning_extra["reasoning_split"] = True
|
||||
try:
|
||||
@@ -2619,7 +2663,7 @@ def generate_title_raw_via_aux(
|
||||
max_tokens=max_tokens,
|
||||
temperature=0.2,
|
||||
timeout=_timeout,
|
||||
extra_body=reasoning_extra,
|
||||
extra_body=reasoning_extra or None,
|
||||
)
|
||||
raw, empty_status = _extract_title_response(resp, aux=True)
|
||||
if raw:
|
||||
@@ -2707,10 +2751,22 @@ def generate_title_raw_via_agent(agent, user_text: str, assistant_text: str) ->
|
||||
api_kwargs.pop('tools', None)
|
||||
api_kwargs['temperature'] = 0.1
|
||||
api_kwargs['timeout'] = 15.0
|
||||
# Reasoning suppression for title gen is already handled
|
||||
# route-correctly by `_build_api_kwargs()` from the
|
||||
# `agent.reasoning_config = {"enabled": False}` set above —
|
||||
# each provider profile applies (or deliberately omits) the
|
||||
# disable in the form its endpoint accepts (OpenAI/Nous omit
|
||||
# the field; LM Studio uses top-level reasoning_effort;
|
||||
# OpenRouter Anthropic mandatory-reasoning is omitted). Do NOT
|
||||
# re-inject a generic `reasoning:{enabled:False}` here — that
|
||||
# re-adds a 400-rejected param on top of the profile output
|
||||
# (#4161). MiniMax still needs reasoning_split, which the
|
||||
# profile path does not add.
|
||||
_tg_extra = dict(api_kwargs.get('extra_body') or {})
|
||||
if _is_minimax_route(getattr(agent, 'provider', ''), getattr(agent, 'model', ''), getattr(agent, 'base_url', '')):
|
||||
extra_body = dict(api_kwargs.get('extra_body') or {})
|
||||
extra_body['reasoning_split'] = True
|
||||
api_kwargs['extra_body'] = extra_body
|
||||
_tg_extra['reasoning_split'] = True
|
||||
if _tg_extra:
|
||||
api_kwargs['extra_body'] = _tg_extra
|
||||
if 'max_completion_tokens' in api_kwargs:
|
||||
api_kwargs['max_completion_tokens'] = max_tokens
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user