Files
2026-04-24 17:31:54 +08:00

198 lines
6.1 KiB
Python

"""
Thinking-control parameter registry.
This module only decides which request parameter should be sent to suppress
model reasoning. Transport, retry, and final output filtering live elsewhere.
Keep rules conservative: add a request parameter only when the provider/model
family has a known control surface. Unsupported parameters are removed by the
OpenAI-compatible degradation path.
"""
import re
from copy import deepcopy
from typing import Any, Dict, List
THINKING_CONTROL_RULES: List[Dict[str, Any]] = [
{
"name": "zai_glm_thinking",
"description": "GLM/Z.AI models with thinking.type control",
"patterns": [
r"glm[-_/.]?(4\.5|4\.6|4\.7|5)",
r"glm[-_/.]?4\.5v",
],
"params": {"thinking": {"type": "disabled"}},
"sources": ["Z.AI Thinking Mode"],
},
{
"name": "qwen_enable_thinking",
"description": "Qwen3/Qwen-VL endpoints that accept enable_thinking",
"patterns": [
r"qwen[-_/.]?3(?!.*r1)",
r"qwen.*[-_/.]?vl",
],
"params": {"enable_thinking": False},
"sources": ["Qwen/DashScope OpenAI-compatible API"],
},
{
"name": "deepseek_thinking",
"description": "DeepSeek endpoints that accept thinking.type",
"patterns": [
r"deepseek[-_/.]?(chat|v3)(?!.*r1)",
],
"params": {"thinking": {"type": "disabled"}},
"sources": ["DeepSeek thinking mode API"],
},
{
"name": "gemini_flash_reasoning",
"description": "Gemini Flash/Lite via OpenAI-compatible proxies",
"patterns": [
r"gemini[-_/.]?2\.(0|5)[-_/.]?(flash|lite)",
],
"params": {"reasoning_effort": "none"},
"sources": ["OpenAI-compatible Gemini proxies"],
},
]
OLLAMA_NATIVE_RULES = {
"parameter_name": "think",
"disable_value": False,
"enable_value": True,
"supported_patterns": [
r"deepseek.*(r1|v3)",
r"qwen.*(3|r1|thinking|vl)",
r".*thinking",
],
"sources": ["Ollama /api/chat think parameter"],
}
OLLAMA_NATIVE_EXCLUDE_PATTERNS = [
# Community abliterated/uncensored vision variants often expose Qwen names
# but do not consistently follow Ollama's native thinking contract.
r"abliterated",
r"uncensored",
]
EXCLUDE_PATTERNS = [
# Google states these cannot be fully disabled; final filtering is the fallback.
r"gemini[-_/.]?2\.5[-_/.]?pro",
r"gemini[-_/.]?3[-_/.]?pro",
# xAI reasoning models currently do not expose a reliable off switch.
r"grok.*reason",
r"grok[-_/.]?4",
# Ollama GPT-OSS accepts only low/medium/high and cannot be fully disabled.
r"gpt[-_/.]?oss",
# Reasoning-only families should not be forced with guessed parameters.
r".*speciale",
]
def _matches(patterns: List[str], model_lower: str) -> bool:
return any(re.search(pattern, model_lower) for pattern in patterns)
def build_thinking_suppression(
provider: str,
model: str,
disable_thinking: bool = True,
) -> Dict[str, Any]:
"""Return request parameters for thinking control, or an empty dict."""
if not model:
return {}
model_lower = model.strip().lower()
provider_lower = provider.strip().lower() if provider else ""
if _matches(EXCLUDE_PATTERNS, model_lower):
return {}
if provider_lower == "ollama":
if _matches(OLLAMA_NATIVE_EXCLUDE_PATTERNS, model_lower):
return {}
if _matches(OLLAMA_NATIVE_RULES["supported_patterns"], model_lower):
value = (
OLLAMA_NATIVE_RULES["disable_value"]
if disable_thinking
else OLLAMA_NATIVE_RULES["enable_value"]
)
return {OLLAMA_NATIVE_RULES["parameter_name"]: value}
return {}
if not disable_thinking:
return {}
for rule in THINKING_CONTROL_RULES:
if _matches(rule["patterns"], model_lower):
return deepcopy(rule["params"])
return {}
def should_append_no_thinking_instruction(
provider: str,
model: str,
disable_thinking: bool = True,
) -> bool:
if not disable_thinking or not model:
return False
provider_lower = provider.strip().lower() if provider else ""
model_lower = model.strip().lower()
if provider_lower == "ollama" and _matches(OLLAMA_NATIVE_EXCLUDE_PATTERNS, model_lower):
return False
return True
def get_rule_info(provider: str, model: str) -> Dict[str, Any]:
"""Return matching rule details for diagnostics."""
if not model:
return {"matched": False}
model_lower = model.strip().lower()
provider_lower = provider.strip().lower() if provider else ""
if _matches(EXCLUDE_PATTERNS, model_lower):
return {
"matched": True,
"rule_name": "excluded",
"description": "Known model family without a reliable disable parameter",
"params": {},
"sources": [],
}
if provider_lower == "ollama":
if _matches(OLLAMA_NATIVE_EXCLUDE_PATTERNS, model_lower):
return {
"matched": True,
"rule_name": "ollama_native_excluded",
"description": "Community model variant without reliable native thinking control",
"params": {},
"sources": [],
}
if _matches(OLLAMA_NATIVE_RULES["supported_patterns"], model_lower):
return {
"matched": True,
"rule_name": "ollama_native",
"description": "Ollama native /api/chat thinking control",
"params": {OLLAMA_NATIVE_RULES["parameter_name"]: OLLAMA_NATIVE_RULES["disable_value"]},
"sources": OLLAMA_NATIVE_RULES["sources"],
}
return {"matched": False}
for rule in THINKING_CONTROL_RULES:
if _matches(rule["patterns"], model_lower):
return {
"matched": True,
"rule_name": rule["name"],
"description": rule["description"],
"params": deepcopy(rule["params"]),
"sources": rule.get("sources", []),
}
return {"matched": False}