From 3faec9355d33b1608405c5958862dc13cd40a755 Mon Sep 17 00:00:00 2001 From: kawayiYokami <289104862@qq.com> Date: Thu, 25 Dec 2025 04:14:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20DeepSeek=20?= =?UTF-8?q?=E6=80=9D=E8=80=83=E4=B8=AD=E8=B0=83=E7=94=A8=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/agent/message.py | 2 + .../agent/runners/tool_loop_agent_runner.py | 1 + astrbot/core/config/default.py | 15 ++- astrbot/core/provider/manager.py | 4 + .../core/provider/sources/deepseek_source.py | 112 ++++++++++++++++++ 5 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 astrbot/core/provider/sources/deepseek_source.py diff --git a/astrbot/core/agent/message.py b/astrbot/core/agent/message.py index d69bc6a81..9354ae3a5 100644 --- a/astrbot/core/agent/message.py +++ b/astrbot/core/agent/message.py @@ -174,6 +174,8 @@ class AssistantMessageSegment(Message): """A message segment from the assistant.""" role: Literal["assistant"] = "assistant" + reasoning_content: str | None = None + """The reasoning content from the assistant, if available (e.g., DeepSeek thinking mode).""" class ToolCallMessageSegment(Message): diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py index 7eb90f3fc..5cc50016a 100644 --- a/astrbot/core/agent/runners/tool_loop_agent_runner.py +++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py @@ -217,6 +217,7 @@ async def step(self): tool_calls_info=AssistantMessageSegment( tool_calls=llm_resp.to_openai_to_calls_model(), content=llm_resp.completion_text, + reasoning_content=llm_resp.reasoning_content or "", ), tool_calls_result=tool_call_result_blocks, ) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index b45917026..6010320af 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -887,6 +887,7 @@ class ChatProviderTemplate(TypedDict): "gm_resp_image_modal": False, "gm_native_search": False, "gm_native_coderunner": False, + "ds_thinking_tool_call": False, "gm_url_context": False, "gm_safety_settings": { "harassment": "BLOCK_MEDIUM_AND_ABOVE", @@ -932,13 +933,14 @@ class ChatProviderTemplate(TypedDict): "DeepSeek": { "id": "deepseek", "provider": "deepseek", - "type": "openai_chat_completion", + "type": "deepseek_chat_completion", "provider_type": "chat_completion", "enable": True, "key": [], "api_base": "https://api.deepseek.com/v1", "timeout": 120, "custom_headers": {}, + "ds_thinking_tool_call": False, }, "Zhipu": { "id": "zhipu", @@ -1709,6 +1711,11 @@ class ChatProviderTemplate(TypedDict): "type": "bool", "hint": "启用后所有函数工具将全部失效", }, + "ds_thinking_tool_call": { + "description": "思考中调用工具", + "type": "bool", + "hint": "启用后,DeepSeek 模型可以在思考过程中调用工具,实现多轮思考-工具调用循环。", + }, "gm_url_context": { "description": "启用URL上下文功能", "type": "bool", @@ -2511,6 +2518,12 @@ class ChatProviderTemplate(TypedDict): "provider_settings.agent_runner_type": "local", }, }, + "provider_settings.ds_thinking_tool_call": { + "description": "思考中调用工具", + "type": "bool", + "condition": {"provider": "deepseek"}, + "hint": "启用后,DeepSeek 模型可以在思考过程中调用工具,实现多轮思考-工具调用循环。仅对 DeepSeek 模型生效。", + }, "provider_settings.identifier": { "description": "用户识别", "type": "bool", diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 0dff2c8ed..9384723ca 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -275,6 +275,10 @@ def dynamic_import_provider(self, type: str): from .sources.openai_source import ( ProviderOpenAIOfficial as ProviderOpenAIOfficial, ) + case "deepseek_chat_completion": + from .sources.deepseek_source import ( + ProviderDeepSeek as ProviderDeepSeek, + ) case "zhipu_chat_completion": from .sources.zhipu_source import ProviderZhipu as ProviderZhipu case "groq_chat_completion": diff --git a/astrbot/core/provider/sources/deepseek_source.py b/astrbot/core/provider/sources/deepseek_source.py new file mode 100644 index 000000000..ad02ee076 --- /dev/null +++ b/astrbot/core/provider/sources/deepseek_source.py @@ -0,0 +1,112 @@ +from astrbot.core.agent.tool import ToolSet +from astrbot.core.provider.entities import LLMResponse + +from ..entities import ProviderType +from ..register import register_provider_adapter +from .openai_source import ProviderOpenAIOfficial + + +@register_provider_adapter( + "deepseek_chat_completion", + "DeepSeek API Chat Completion 提供商适配器", + ProviderType.CHAT_COMPLETION, + default_config_tmpl={ + "id": "deepseek", + "provider": "deepseek", + "type": "deepseek_chat_completion", + "provider_type": "chat_completion", + "enable": True, + "key": [], + "api_base": "https://api.deepseek.com/v1", + "timeout": 120, + "custom_headers": {}, + "hint": "DeepSeek 官方 API,支持思考模式。", + }, + provider_display_name="DeepSeek", +) +class ProviderDeepSeek(ProviderOpenAIOfficial): + def _should_enable_thinking(self, payloads: dict) -> bool: + """判断是否应该启用思考模式 + + 规则: + 1. deepseek-chat 模型无条件关闭思维链 + 2. deepseek-reasoner 模型无条件开启思维链 + 3. 其他模型根据 ds_thinking_tool_call 配置决定 + """ + model = payloads.get("model", "").lower() + + # deepseek-chat 强制关闭 + if "deepseek-chat" in model: + return False + + # deepseek-reasoner 强制开启 + if "deepseek-reasoner" in model or "reasoner" in model: + return True + + # 其他模型根据配置决定 + return self.provider_config.get("ds_thinking_tool_call", False) + + async def _query_stream( + self, + payloads: dict, + tools: ToolSet | None, + ): + # 判断是否启用思考模式 + ds_thinking_enabled = self._should_enable_thinking(payloads) + + if ds_thinking_enabled: + messages = payloads.get("messages", []) + + # DeepSeek API 要求:思考模式下每条助手消息必须有 reasoning_content + # 先确保每条助手消息都有这个字段 + for msg in messages: + if msg.get("role") == "assistant": + if "reasoning_content" not in msg: + # 工具调用消息等特殊消息初始化为空字符串 + msg["reasoning_content"] = "" + + # 清理历史消息中的 reasoning_content + # 只有最后一条是 user 时才清空(说明是新问题) + if messages and messages[-1].get("role") == "user": + for msg in messages: + if msg.get("role") == "assistant" and "reasoning_content" in msg: + msg["reasoning_content"] = "" + + # 添加 thinking 参数(父类会自动把它放到 extra_body) + payloads["thinking"] = {"type": "enabled"} + + # 调用父类方法 + async for response in super()._query_stream(payloads, tools): + yield response + + async def _query( + self, + payloads: dict, + tools: ToolSet | None, + ) -> LLMResponse: + # 判断是否启用思考模式 + ds_thinking_enabled = self._should_enable_thinking(payloads) + + if ds_thinking_enabled: + messages = payloads.get("messages", []) + + # DeepSeek API 要求:思考模式下每条助手消息必须有 reasoning_content + # 先确保每条助手消息都有这个字段 + for msg in messages: + if msg.get("role") == "assistant": + if "reasoning_content" not in msg: + # 工具调用消息等特殊消息初始化为空字符串 + msg["reasoning_content"] = "" + + # 清理历史消息中的 reasoning_content + # 只有最后一条是 user 时才清空(说明是新问题) + if messages and messages[-1].get("role") == "user": + for msg in messages: + if msg.get("role") == "assistant" and "reasoning_content" in msg: + msg["reasoning_content"] = "" + + # 添加 thinking 参数 + payloads["thinking"] = {"type": "enabled"} + + # 调用父类方法 + return await super()._query(payloads, tools) From bfc118961f60693ab99787f3f58d1b77acc99b6d Mon Sep 17 00:00:00 2001 From: kawayiYokami <289104862@qq.com> Date: Fri, 26 Dec 2025 21:06:49 +0800 Subject: [PATCH 2/2] =?UTF-8?q?perf:=E5=87=8F=E5=B0=91=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E9=94=99=E8=AF=AF=E7=9A=84=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 6010320af..c607ec583 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -887,7 +887,6 @@ class ChatProviderTemplate(TypedDict): "gm_resp_image_modal": False, "gm_native_search": False, "gm_native_coderunner": False, - "ds_thinking_tool_call": False, "gm_url_context": False, "gm_safety_settings": { "harassment": "BLOCK_MEDIUM_AND_ABOVE", @@ -2518,12 +2517,6 @@ class ChatProviderTemplate(TypedDict): "provider_settings.agent_runner_type": "local", }, }, - "provider_settings.ds_thinking_tool_call": { - "description": "思考中调用工具", - "type": "bool", - "condition": {"provider": "deepseek"}, - "hint": "启用后,DeepSeek 模型可以在思考过程中调用工具,实现多轮思考-工具调用循环。仅对 DeepSeek 模型生效。", - }, "provider_settings.identifier": { "description": "用户识别", "type": "bool",