fix(streaming): provider-gate title-gen reasoning extra_body (#4161, #2083)

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:
Rod Boev authored and nesquena-hermes committed 2026-06-14 05:22:47 +00:00
1 parent dfb4007ae8
commit bc5fceb88a
3 files changed
+123 -5

No files matched your search

+61 -5
View File
@@ -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: