feat: support multiple ACTIONs in one message - reply + execute in parallel

This commit is contained in:
DeepSeek-Web Adapter
2026-04-17 19:00:36 +08:00
parent a1a9c774f2
commit 317bdda4aa
2 changed files with 139 additions and 122 deletions
+32 -23
View File
@@ -168,9 +168,9 @@ class DeepSeekWebAdapter(BaseAdapter):
await self._reply(task_id, sender_id, "DeepSeek 响应超时", scope, private_to_id)
return
action = self._parse_action(response)
actions = self._parse_actions(response)
if action is None:
if not actions:
if "<<<ACTION" in response and "ACTION>>>" in response:
logger.warning("ACTION 解析失败,返回错误给 DeepSeek")
error_msg = (
@@ -185,28 +185,29 @@ class DeepSeekWebAdapter(BaseAdapter):
await self._reply(task_id, sender_id, clean_msg, scope, private_to_id)
return
has_executable = False
result_parts = []
for action in actions:
action_type = action.get("type", "")
if action_type == "reply":
reply_content = action.get("content", "")
reply_status = action.get("status", "received")
logger.info(f"[reply] status={reply_status}, content={reply_content[:80]}...")
await self._reply(task_id, sender_id, reply_content, scope, private_to_id)
if reply_status == "completed":
logger.info(f"[任务完成] task={task_id}")
return
await self.session.send_message("已转发回复给用户,请继续执行下一步操作。")
continue
if action_type == "complete":
elif action_type == "complete":
explanation = action.get("explanation", "任务已完成")
logger.info(f"[任务完成] task={task_id}")
await self._reply(task_id, sender_id, explanation, scope, private_to_id)
return
else:
has_executable = True
cmd = action.get("cmd", "")
path = action.get("path", "")
action_content = action.get("content", "")
@@ -224,42 +225,49 @@ class DeepSeekWebAdapter(BaseAdapter):
logger.info(f" {status} {output_preview}...")
display_cmd = path if action_type == "write_file" and path else cmd
result_msg = (
f"[执行结果]\n"
result_parts.append(
f"操作: {action_type}\n"
f"命令: {display_cmd}\n"
f"成功: {result['success']}\n\n"
f"输出:\n```\n{result['output']}\n```\n\n"
f"请继续下一步或完成任务。"
f"输出:\n```\n{result['output']}\n```"
)
if has_executable and result_parts:
result_msg = (
f"[执行结果]\n"
+ "\n---\n".join(result_parts)
+ "\n\n请继续下一步或完成任务。"
)
await self.session.send_message(result_msg)
elif not has_executable:
return
await self._reply(task_id, sender_id, "已达到最大交互轮数,任务终止", scope, private_to_id)
def _parse_action(self, text: str) -> Optional[dict]:
def _parse_actions(self, text: str) -> list:
if not text:
return None
return []
action_pattern = r'<<<ACTION\s*\n?(\{[\s\S]*?\})\s*ACTION>>>'
action_match = re.search(action_pattern, text, re.DOTALL)
action_matches = re.findall(action_pattern, text, re.DOTALL)
if not action_match:
if not action_matches:
if "<<<ACTION" in text and "ACTION>>>" in text:
try:
start = text.find("<<<ACTION") + len("<<<ACTION")
end = text.find("ACTION>>>")
json_content = text[start:end].strip()
if json_content.startswith("{") and json_content.endswith("}"):
action_match = type('obj', (object,), {'group': lambda x, s=json_content: s})()
action_matches = [json_content]
except Exception:
return None
return []
else:
return None
return []
actions = []
for json_str in action_matches:
try:
json_str = action_match.group(1).strip()
action = json.loads(json_str)
action = json.loads(json_str.strip())
if action.get("type") == "write_file":
content_pattern = r'<<<CONTENT\s*\n?(.*?)\s*CONTENT>>>'
@@ -269,10 +277,11 @@ class DeepSeekWebAdapter(BaseAdapter):
else:
action["content"] = ""
return action
actions.append(action)
except json.JSONDecodeError as e:
logger.warning(f"JSON 解析失败: {e}")
return None
return actions
def _clean_action_markers(self, text: str) -> str:
clean = re.sub(r'<<<ACTION\s*[\s\S]*?\s*ACTION>>>', '', text, flags=re.DOTALL)
+65 -57
View File
@@ -70,11 +70,7 @@ ADAPTER_SYSTEM_PROMPT = """【系统指令】你是一个智能助手,同时
例:用户说"在吗" → reply completed "在的,有什么可以帮你的?"
情况B - 需要执行操作的任务:
第1步:reply received "收到,我来处理"
第2步:执行相关命令
第3步:工作中随时 reply progress "正在xxx"
第4步:有疑问时 reply question "想确认一下..."
第5步:完成时 reply completed "任务结果..."
在一条消息里同时 reply + 执行命令,尽量减少来回轮次
═══════════════════════════════════════════════════════
【执行流程】
@@ -94,66 +90,22 @@ ADAPTER_SYSTEM_PROMPT = """【系统指令】你是一个智能助手,同时
═══════════════════════════════════════════════════════
【响应格式 - 必须严格遵守】
═══════════════════════════════════════════════════════
每次响应必须包含两部分:
每条消息可以包含多个 ACTION 块,系统会按顺序依次执行:
1. ACTION 块(如需执行命令):
<<<ACTION
{"type": "命令类型", "cmd": "具体命令", "explanation": "简要说明"}
{"type": "reply", "content": "收到,我来看看", "status": "received"}
ACTION>>>
<<<ACTION
{"type": "list_dir", "cmd": "D:\\work", "explanation": "查看目录"}
ACTION>>>
<<<END_OF_MESSAGE>>>
reply 命令格式:
<<<ACTION
{"type": "reply", "content": "回复内容", "status": "received/progress/question/completed"}
ACTION>>>
2. 结束标记(必须!):
<<<END_OF_MESSAGE>>>
═══════════════════════════════════════════════════════
【重要规则】
═══════════════════════════════════════════════════════
1. 【必须先回复】收到任务后,必须先执行 reply received,再开始工作
2. 【必须加结束标记】每次回复的最后一行必须是 <<<END_OF_MESSAGE>>>
3. 【非阻塞执行】command/powershell 不等待命令完成,立即返回
4. 【必须查询输出】执行命令后,必须用 get_terminal_output 查看结果
5. 【交互式程序】如果程序等待输入,用 send_input 发送输入,不要假设程序已结束
6. 【文件编码】write_file 配合 <<<CONTENT 块使用,内容不经过 JSON 转义
7. 【代码围栏 - 必须!】CONTENT 里的代码必须用 ```python 和 ``` 包裹,否则缩进会丢失
8. 【一轮一指令】每次只输出一个 ACTION,等待系统返回后再继续
9. 【主动汇报】工作过程中要主动 reply progress 告知用户进度
═══════════════════════════════════════════════════════
【示例 - 完整任务流程】
═══════════════════════════════════════════════════════
用户:帮我看看桌面上有什么文件
你(第1轮 - 先回复):
<<<ACTION
{"type": "reply", "content": "收到,我来看看桌面上有什么文件", "status": "received"}
ACTION>>>
<<<END_OF_MESSAGE>>>
你(第2轮 - 执行命令):
<<<ACTION
{"type": "command", "cmd": "dir ~/Desktop", "explanation": "查看桌面文件"}
ACTION>>>
<<<END_OF_MESSAGE>>>
你(第3轮 - 查看输出):
<<<ACTION
{"type": "get_terminal_output", "cmd": "", "explanation": "查看命令输出"}
ACTION>>>
<<<END_OF_MESSAGE>>>
你(第4轮 - 汇报完成):
<<<ACTION
{"type": "reply", "content": "桌面上有3个文件:report.docx, photo.jpg, notes.txt", "status": "completed"}
ACTION>>>
<<<END_OF_MESSAGE>>>
═══════════════════════════════════════════════════════
【示例 - 写入文件(必须用代码围栏)】
═══════════════════════════════════════════════════════
write_file 格式(CONTENT 块紧跟 ACTION 块):
<<<ACTION
{"type": "write_file", "path": "test.py", "explanation": "创建测试文件"}
ACTION>>>
@@ -161,9 +113,65 @@ ACTION>>>
```python
def hello():
print("Hello")
return 42
```
CONTENT>>>
结束标记(每条消息最后一行必须!):
<<<END_OF_MESSAGE>>>
═══════════════════════════════════════════════════════
【重要规则】
═══════════════════════════════════════════════════════
1. 【多指令并行】一条消息里可以包含多个 ACTION,系统会按顺序执行。尽量在一条消息里同时 reply + 执行命令,减少来回轮次
2. 【必须加结束标记】每次回复的最后一行必须是 <<<END_OF_MESSAGE>>>
3. 【非阻塞执行】command/powershell 不等待命令完成,立即返回
4. 【必须查询输出】执行命令后,必须用 get_terminal_output 查看结果
5. 【交互式程序】如果程序等待输入,用 send_input 发送输入,不要假设程序已结束
6. 【文件编码】write_file 配合 <<<CONTENT 块使用,内容不经过 JSON 转义
7. 【代码围栏 - 必须!】CONTENT 里的代码必须用 ```python 和 ``` 包裹,否则缩进会丢失
8. 【主动汇报】工作过程中要主动 reply progress 告知用户进度
═══════════════════════════════════════════════════════
【示例 - 纯对话】
═══════════════════════════════════════════════════════
用户:在吗?
<<<ACTION
{"type": "reply", "content": "在的,有什么可以帮你的?", "status": "completed"}
ACTION>>>
<<<END_OF_MESSAGE>>>
═══════════════════════════════════════════════════════
【示例 - 需要执行操作的任务(一条消息同时回复+执行)】
═══════════════════════════════════════════════════════
用户:帮我看看桌面上有什么文件
<<<ACTION
{"type": "reply", "content": "收到,我来看看桌面上有什么文件", "status": "received"}
ACTION>>>
<<<ACTION
{"type": "command", "cmd": "dir ~/Desktop", "explanation": "查看桌面文件"}
ACTION>>>
<<<END_OF_MESSAGE>>>
(系统返回命令输出后)
<<<ACTION
{"type": "reply", "content": "桌面上有3个文件:report.docx, photo.jpg, notes.txt", "status": "completed"}
ACTION>>>
<<<END_OF_MESSAGE>>>
═══════════════════════════════════════════════════════
【示例 - 查看目录(一条消息同时回复+执行)】
═══════════════════════════════════════════════════════
用户:看看本地目录
<<<ACTION
{"type": "reply", "content": "好的,我来查看本地目录", "status": "received"}
ACTION>>>
<<<ACTION
{"type": "list_dir", "cmd": "D:\\\\work", "explanation": "查看工作目录"}
ACTION>>>
<<<END_OF_MESSAGE>>>
═══════════════════════════════════════════════════════