Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

148 lines
5.7 KiB
Python

"""Reasoning/thinking parameters for OpenAI-compatible provider calls."""
from __future__ import annotations
from typing import Any
_THINKING_STYLE_MAP = {
"thinking_type": lambda enabled: {"thinking": {"type": "enabled" if enabled else "disabled"}},
"enable_thinking": lambda enabled: {"enable_thinking": enabled},
"reasoning_split": lambda enabled: {"reasoning_split": enabled},
}
_PROVIDER_THINKING_STYLES = {
"deepseek": "thinking_type",
"volcengine": "thinking_type",
"volcengine_coding_plan": "thinking_type",
"byteplus": "thinking_type",
"byteplus_coding_plan": "thinking_type",
"dashscope": "enable_thinking",
"minimax": "reasoning_split",
}
_PROVIDER_REASONING_PATTERNS = {
"deepseek": ("deepseek-v4-pro", "deepseek-reasoner"),
"dashscope": ("qwen3", "qwen-3", "qwq", "qwen-plus"),
}
# Models that ship with thinking enabled by default and burn the entire
# `max_tokens` budget on reasoning unless we explicitly turn it off via the
# top-level ``reasoning_effort`` field. Substring match — also catches the
# ``models/<id>`` prefix some clients use.
_PROVIDER_DEFAULT_OFF_PATTERNS: dict[str, tuple[str, ...]] = {
"gemini": ("gemini-2.5", "gemini-3"),
}
_CUSTOM_MODEL_THINKING_STYLES: tuple[tuple[tuple[str, ...], str], ...] = (
(("qwen3", "qwen-3", "qwq", "qwen-plus"), "enable_thinking"),
(("deepseek-v4-pro", "deepseek-reasoner"), "thinking_type"),
)
_THINKING_DISABLED_BY_DEFAULT: tuple[tuple[str, str], ...] = (("deepseek", "deepseek-v4-flash"),)
def _spec_name(spec: Any, binding: str | None) -> str:
return str(getattr(spec, "name", None) or binding or "").strip().lower()
def _matches(model_name: str, patterns: tuple[str, ...]) -> bool:
model_lower = model_name.lower()
return any(pattern.lower() in model_lower for pattern in patterns)
def _custom_thinking_style(model_name: str) -> tuple[str, tuple[str, ...]]:
for patterns, style in _CUSTOM_MODEL_THINKING_STYLES:
if _matches(model_name, patterns):
return style, patterns
return "", ()
def _disable_thinking_by_default(provider_name: str, model_name: str) -> bool:
normalized = model_name.strip().lower()
return any(
provider_name == provider and pattern in normalized
for provider, pattern in _THINKING_DISABLED_BY_DEFAULT
)
def default_reasoning_effort_for(provider: str | None, model: str | None) -> str | None:
"""Return the implicit ``reasoning_effort`` for ``provider``/``model``, if any.
Used by callers that don't go through :func:`build_openai_compatible_reasoning_kwargs`
(currently the openai-SDK path in ``executors.py`` and the aiohttp fallback
in ``cloud_provider.py``). Returns ``None`` when no default applies — the
caller should leave the field unset in that case.
The single source of truth is :data:`_PROVIDER_DEFAULT_OFF_PATTERNS` so all
three execution paths agree on which models need thinking disabled by default.
"""
provider_name = (provider or "").strip().lower()
off_patterns = _PROVIDER_DEFAULT_OFF_PATTERNS.get(provider_name)
if off_patterns and _matches(model or "", off_patterns):
return "none"
return None
def build_openai_compatible_reasoning_kwargs(
*,
spec: Any,
binding: str | None,
model: str | None,
reasoning_effort: str | None,
) -> dict[str, Any]:
"""Return reasoning kwargs for OpenAI-compatible Chat Completions calls.
Some OpenAI-compatible providers expose thinking controls through
``extra_body`` instead of the top-level ``reasoning_effort`` field. Direct
``custom`` bindings need model-family inference because their endpoint is
user supplied and therefore cannot be identified by provider name alone.
"""
provider_name = _spec_name(spec, binding)
model_name = model or ""
thinking_style = str(getattr(spec, "thinking_style", "") or "")
patterns = tuple(getattr(spec, "reasoning_model_patterns", ()) or ())
if not thinking_style:
thinking_style = _PROVIDER_THINKING_STYLES.get(provider_name, "")
if not patterns:
patterns = _PROVIDER_REASONING_PATTERNS.get(provider_name, ())
if provider_name == "custom":
custom_style, custom_patterns = _custom_thinking_style(model_name)
if custom_style:
thinking_style = custom_style
patterns = custom_patterns
resolved_effort = reasoning_effort
if resolved_effort is None:
if patterns and _matches(model_name, patterns):
resolved_effort = "high"
else:
resolved_effort = default_reasoning_effort_for(provider_name, model_name)
semantic_effort: str | None = None
if isinstance(resolved_effort, str):
semantic_effort = resolved_effort.lower()
if semantic_effort == "minimum":
semantic_effort = "minimal"
kwargs: dict[str, Any] = {}
if resolved_effort:
suppress_top_level = bool(
thinking_style and (semantic_effort == "minimal" or thinking_style == "enable_thinking")
)
if not suppress_top_level:
kwargs["reasoning_effort"] = resolved_effort
if thinking_style and resolved_effort is not None:
thinking_enabled = semantic_effort != "minimal"
extra = _THINKING_STYLE_MAP.get(thinking_style, lambda _enabled: None)(thinking_enabled)
if extra:
kwargs.setdefault("extra_body", {}).update(extra)
elif thinking_style and _disable_thinking_by_default(provider_name, model_name):
extra = _THINKING_STYLE_MAP.get(thinking_style, lambda _enabled: None)(False)
if extra:
kwargs.setdefault("extra_body", {}).update(extra)
return kwargs
__all__ = [
"build_openai_compatible_reasoning_kwargs",
"default_reasoning_effort_for",
]