Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,8 @@ class ChatProviderTemplate(TypedDict):
"timeout": 120,
"proxy": "",
"custom_headers": {},
"deepseek_thinking_enabled": True,
Comment thread
piexian marked this conversation as resolved.
"deepseek_reasoning_effort": "high",
},
"Zhipu": {
"id": "zhipu",
Expand Down Expand Up @@ -1983,6 +1985,17 @@ class ChatProviderTemplate(TypedDict):
"type": "bool",
"hint": "关闭 Ollama 思考模式。",
},
"deepseek_thinking_enabled": {
"description": "开启思考模式",
"type": "bool",
"hint": "控制思考开关。",
},
"deepseek_reasoning_effort": {
"description": "思考强度",
"type": "string",
"options": ["high", "max"],
Comment thread
piexian marked this conversation as resolved.
"hint": "仅开启思考模式生效,可选 high / max。",
},
"custom_extra_body": {
"description": "自定义请求体参数",
"type": "dict",
Expand Down
80 changes: 75 additions & 5 deletions astrbot/core/provider/sources/openai_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,13 +487,62 @@ def __init__(self, provider_config, provider_settings) -> None:
self.set_model(model)

self.reasoning_key = "reasoning_content"
self._deepseek_reasoning_effort_cached: str | None = None
self._deepseek_reasoning_effort_cached_source: Any = None
self._deepseek_reasoning_effort_cache_ready = False

def _ollama_disable_thinking_enabled(self) -> bool:
value = self.provider_config.get("ollama_disable_thinking", False)
@staticmethod
def _config_flag_enabled(value: Any, default: bool = False) -> bool:
if value is None:
return default
if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes", "on"}
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
return True
if normalized in {"0", "false", "no", "off"}:
return False
return default
return bool(value)

def _ollama_disable_thinking_enabled(self) -> bool:
return self._config_flag_enabled(
self.provider_config.get("ollama_disable_thinking", False)
)

def _deepseek_thinking_enabled(self) -> bool:
return self._config_flag_enabled(
self.provider_config.get("deepseek_thinking_enabled", True),
default=True,
)

def _deepseek_reasoning_effort(self) -> str:
value = self.provider_config.get("deepseek_reasoning_effort", "high")
if (
self._deepseek_reasoning_effort_cache_ready
and self._deepseek_reasoning_effort_cached_source == value
and self._deepseek_reasoning_effort_cached is not None
):
return self._deepseek_reasoning_effort_cached

normalized_value = "high"
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in {"high", "max"}:
normalized_value = normalized
elif value not in (None, ""):
logger.warning(
f"Invalid DeepSeek reasoning effort: {value}, falling back to high"
)
elif value not in (None, ""):
logger.warning(
f"Invalid DeepSeek reasoning effort: {value}, falling back to high"
)

self._deepseek_reasoning_effort_cached = normalized_value
self._deepseek_reasoning_effort_cached_source = value
self._deepseek_reasoning_effort_cache_ready = True
return normalized_value

def _apply_provider_specific_extra_body_overrides(
self, extra_body: dict[str, Any]
) -> None:
Expand All @@ -508,6 +557,27 @@ def _apply_provider_specific_extra_body_overrides(
extra_body.pop("think", None)
extra_body["reasoning_effort"] = "none"

def _apply_provider_specific_request_overrides(
self,
payloads: dict,
extra_body: dict[str, Any],
) -> None:
if self.provider_config.get("provider") == "deepseek":
thinking_enabled = self._deepseek_thinking_enabled()
extra_body.pop("reasoning", None)
extra_body.pop("think", None)
extra_body.pop("reasoning_effort", None)
extra_body["thinking"] = {
"type": "enabled" if thinking_enabled else "disabled"
}
if thinking_enabled:
# Provider config is the canonical DeepSeek reasoning setting.
payloads["reasoning_effort"] = self._deepseek_reasoning_effort()
else:
payloads.pop("reasoning_effort", None)

Comment thread
piexian marked this conversation as resolved.
self._apply_provider_specific_extra_body_overrides(extra_body)

async def get_models(self):
try:
models_str = []
Expand Down Expand Up @@ -580,7 +650,7 @@ async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
custom_extra_body = self.provider_config.get("custom_extra_body", {})
if isinstance(custom_extra_body, dict):
extra_body.update(custom_extra_body)
self._apply_provider_specific_extra_body_overrides(extra_body)
self._apply_provider_specific_request_overrides(payloads, extra_body)

model = payloads.get("model", "").lower()

Expand Down Expand Up @@ -634,7 +704,7 @@ async def _query_stream(
to_del.append(key)
for key in to_del:
del payloads[key]
self._apply_provider_specific_extra_body_overrides(extra_body)
self._apply_provider_specific_request_overrides(payloads, extra_body)

self._sanitize_assistant_messages(payloads)

Expand Down
9 changes: 9 additions & 0 deletions dashboard/src/composables/useProviderSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,15 @@ export function useProviderSources(options: UseProviderSourcesOptions) {
source.ollama_disable_thinking = false
}

if (source.provider === 'deepseek') {
if (source.deepseek_thinking_enabled === undefined) {
source.deepseek_thinking_enabled = true
}
if (!source.deepseek_reasoning_effort) {
source.deepseek_reasoning_effort = 'high'
}
}

return source
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,14 @@
"description": "Disable thinking mode",
"hint": "Close Ollama thinking mode."
},
"deepseek_thinking_enabled": {
"description": "Enable thinking mode",
"hint": "Controls thinking on/off."
},
"deepseek_reasoning_effort": {
"description": "Reasoning effort",
"hint": "Takes effect only when thinking is enabled. Options: high / max."
},
"custom_extra_body": {
"description": "Custom request body parameters",
"hint": "Add extra parameters to requests, such as temperature, top_p, max_tokens, etc.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,14 @@
"description": "关闭思考模式",
"hint": "关闭 Ollama 思考模式。"
},
"deepseek_thinking_enabled": {
"description": "开启思考模式",
"hint": "控制思考开关。"
},
"deepseek_reasoning_effort": {
"description": "思考强度",
"hint": "仅开启思考模式生效,可选 high / max。"
},
"custom_extra_body": {
"description": "自定义请求体参数",
"hint": "用于在请求时添加额外的参数,如 temperature、top_p、max_tokens 等。",
Expand Down
148 changes: 148 additions & 0 deletions tests/test_openai_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -1724,3 +1724,151 @@ async def fake_create(**kwargs):
assert messages[1] == {"role": "user", "content": "again"}
finally:
await provider.terminate()


@pytest.mark.asyncio
async def test_apply_provider_specific_request_overrides_disables_deepseek_thinking():
provider = _make_provider(
{
"provider": "deepseek",
"deepseek_thinking_enabled": False,
"deepseek_reasoning_effort": "max",
}
)
try:
payloads = {
"model": "deepseek-v4-pro",
"messages": [{"role": "user", "content": "hello"}],
"reasoning_effort": "max",
}
extra_body = {
"reasoning": {"effort": "high"},
"reasoning_effort": "high",
"think": True,
}

provider._apply_provider_specific_request_overrides(payloads, extra_body)

assert "reasoning_effort" not in payloads
assert extra_body["thinking"] == {"type": "disabled"}
assert "reasoning" not in extra_body
assert "reasoning_effort" not in extra_body
assert "think" not in extra_body
finally:
await provider.terminate()


@pytest.mark.asyncio
async def test_apply_provider_specific_request_overrides_prefers_deepseek_config_reasoning_effort():
provider = _make_provider(
{
"provider": "deepseek",
"deepseek_thinking_enabled": True,
"deepseek_reasoning_effort": "max",
}
)
try:
payloads = {
"model": "deepseek-v4-pro",
"messages": [{"role": "user", "content": "hello"}],
"reasoning_effort": "high",
}
extra_body = {
"reasoning_effort": "high",
"thinking": {"type": "disabled"},
}

provider._apply_provider_specific_request_overrides(payloads, extra_body)

assert payloads["reasoning_effort"] == "max"
assert extra_body["thinking"] == {"type": "enabled"}
assert "reasoning_effort" not in extra_body
finally:
await provider.terminate()


@pytest.mark.asyncio
async def test_deepseek_reasoning_effort_warns_once_for_invalid_config(monkeypatch):
provider = _make_provider(
{
"provider": "deepseek",
"deepseek_reasoning_effort": "invalid",
}
)
warnings: list[str] = []
try:
monkeypatch.setattr(
"astrbot.core.provider.sources.openai_source.logger.warning",
warnings.append,
)

assert provider._deepseek_reasoning_effort() == "high"
assert provider._deepseek_reasoning_effort() == "high"
assert warnings == [
"Invalid DeepSeek reasoning effort: invalid, falling back to high"
]
finally:
await provider.terminate()


@pytest.mark.asyncio
async def test_query_injects_deepseek_thinking_and_reasoning_effort(monkeypatch):
provider = _make_provider(
{
"provider": "deepseek",
"deepseek_thinking_enabled": True,
"deepseek_reasoning_effort": "max",
"custom_extra_body": {
"thinking": {"type": "disabled"},
"reasoning_effort": "high",
"temperature": 0.1,
},
}
)
try:
captured_kwargs = {}

async def fake_create(**kwargs):
captured_kwargs.update(kwargs)
return ChatCompletion.model_validate(
{
"id": "chatcmpl-test",
"object": "chat.completion",
"created": 0,
"model": "deepseek-v4-pro",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "ok",
},
"finish_reason": "stop",
}
],
"usage": {
"prompt_tokens": 1,
"completion_tokens": 1,
"total_tokens": 2,
},
}
)

monkeypatch.setattr(provider.client.chat.completions, "create", fake_create)

await provider._query(
payloads={
"model": "deepseek-v4-pro",
"messages": [{"role": "user", "content": "hello"}],
"reasoning_effort": "high",
},
tools=None,
)

extra_body = captured_kwargs["extra_body"]
assert captured_kwargs["reasoning_effort"] == "max"
assert extra_body["thinking"] == {"type": "enabled"}
assert "reasoning_effort" not in extra_body
assert extra_body["temperature"] == 0.1
finally:
await provider.terminate()
Loading