feat(multi-agent): 重构 Team Leader 提示词分工策略并新增 sleep wait_sub_agent_output_ids

This commit is contained in:
JOJO 2026-07-15 14:55:14 +08:00
parent 11ffdc2bb5
commit 3138d23687
6 changed files with 190 additions and 10 deletions

View File

@ -103,7 +103,7 @@ class ToolsDefinitionCoreToolsMixin:
"type": "function", "type": "function",
"function": { "function": {
"name": "sleep", "name": "sleep",
"description": "等待工具。两种模式二选一1) seconds短暂延迟2) wait_runcommand_id等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。", "description": "等待工具。三种模式三选一1) seconds短暂延迟2) wait_runcommand_id等待指定后台 run_command 结束并直接返回结果3) wait_sub_agent_output_ids等待指定子智能体下一次输出并直接返回该消息多智能体模式专用。若同时提供多个等待参数会报错。",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": self._inject_intent({ "properties": self._inject_intent({
@ -115,6 +115,11 @@ class ToolsDefinitionCoreToolsMixin:
"type": "string", "type": "string",
"description": "等待指定后台 run_command 的 command_id 结束后返回。" "description": "等待指定后台 run_command 的 command_id 结束后返回。"
}, },
"wait_sub_agent_output_ids": {
"type": "array",
"items": {"type": "integer"},
"description": "等待指定子智能体下一次输出并直接返回该消息。多智能体模式专用,且一次只能包含一个编号。"
},
"reason": { "reason": {
"type": "string", "type": "string",
"description": "等待的原因说明(可选)" "description": "等待的原因说明(可选)"

View File

@ -1080,6 +1080,7 @@ class MainTerminalToolsExecutionMixin:
elif tool_name == "sleep": elif tool_name == "sleep":
seconds = arguments.get("seconds") seconds = arguments.get("seconds")
wait_sub_agent_ids = arguments.get("wait_sub_agent_ids") wait_sub_agent_ids = arguments.get("wait_sub_agent_ids")
wait_sub_agent_output_ids = arguments.get("wait_sub_agent_output_ids")
wait_runcommand_id = arguments.get("wait_runcommand_id") wait_runcommand_id = arguments.get("wait_runcommand_id")
reason = arguments.get("reason", "等待操作完成") reason = arguments.get("reason", "等待操作完成")
@ -1088,23 +1089,60 @@ class MainTerminalToolsExecutionMixin:
provided += 1 provided += 1
if wait_sub_agent_ids: if wait_sub_agent_ids:
provided += 1 provided += 1
if wait_sub_agent_output_ids:
provided += 1
if wait_runcommand_id: if wait_runcommand_id:
provided += 1 provided += 1
if provided == 0: if provided == 0:
result = { result = {
"success": False, "success": False,
"error": "sleep 至少需要提供一个参数seconds / wait_sub_agent_ids / wait_runcommand_id" "error": "sleep 至少需要提供一个参数seconds / wait_sub_agent_ids / wait_sub_agent_output_ids / wait_runcommand_id"
} }
elif provided > 1: elif provided > 1:
result = { result = {
"success": False, "success": False,
"error": "sleep 的等待参数互斥seconds / wait_sub_agent_ids / wait_runcommand_id 只能提供一个" "error": "sleep 的等待参数互斥seconds / wait_sub_agent_ids / wait_sub_agent_output_ids / wait_runcommand_id 只能提供一个"
} }
elif wait_sub_agent_output_ids:
if not getattr(self, "multi_agent_mode", False):
result = {"success": False, "error": "wait_sub_agent_output_ids 仅在多智能体模式下可用"}
elif not isinstance(wait_sub_agent_output_ids, list) or len(wait_sub_agent_output_ids) != 1:
result = {"success": False, "error": "wait_sub_agent_output_ids 必须包含且仅包含一个子智能体编号"}
else:
try:
agent_id = int(wait_sub_agent_output_ids[0])
if agent_id <= 0:
raise ValueError()
except Exception:
result = {"success": False, "error": "wait_sub_agent_output_ids 必须是正整数"}
else:
manager = getattr(self, "sub_agent_manager", None)
if not manager:
result = {"success": False, "error": "子智能体管理器不可用"}
else:
state = manager.get_multi_agent_state(getattr(self.context_manager, "current_conversation_id", None))
if not state:
result = {"success": False, "error": "当前对话没有多智能体状态"}
else:
try:
loop = asyncio.get_running_loop()
fut = state.register_output_wait(agent_id, loop)
msg = await asyncio.wait_for(fut, timeout=300)
result = {
"success": True,
"mode": "wait_sub_agent_output",
"agent_id": agent_id,
"message": msg,
}
except asyncio.TimeoutError:
result = {"success": False, "error": f"等待子智能体 {agent_id} 输出超时5分钟"}
except Exception as exc:
result = {"success": False, "error": f"等待子智能体 {agent_id} 输出失败: {exc}"}
elif wait_sub_agent_ids: elif wait_sub_agent_ids:
if getattr(self, "multi_agent_mode", False): if getattr(self, "multi_agent_mode", False):
result = { result = {
"success": False, "success": False,
"error": "多智能体模式下 sleep 工具不支持 wait_sub_agent_ids。请通过 list_active_sub_agents / get_sub_agent_status 查看状态,或直接向子智能体发送消息。" "error": "多智能体模式下 sleep 工具不支持 wait_sub_agent_ids,请使用 wait_sub_agent_output_ids"
} }
elif not isinstance(wait_sub_agent_ids, list) or not wait_sub_agent_ids: elif not isinstance(wait_sub_agent_ids, list) or not wait_sub_agent_ids:
result = {"success": False, "error": "wait_sub_agent_ids 必须是非空数组"} result = {"success": False, "error": "wait_sub_agent_ids 必须是非空数组"}

View File

@ -286,6 +286,9 @@ class MultiAgentState:
# 一个 agent 可能同时只阻塞在一个 ask 工具上(最简实现) # 一个 agent 可能同时只阻塞在一个 ask 工具上(最简实现)
# key = agent_id, value = question_id表示当前 agent 正阻塞等待) # key = agent_id, value = question_id表示当前 agent 正阻塞等待)
self.agent_blocking_question: Dict[int, str] = {} self.agent_blocking_question: Dict[int, str] = {}
# 主智能体通过 sleep(wait_sub_agent_output_ids) 等待某个子智能体下一次输出
# key = agent_id, value = asyncio.Future结果为完整消息文本
self.output_waits: Dict[int, asyncio.Future] = {}
# 角色实例计数role_id -> 已分配的最大 agent_id数字 # 角色实例计数role_id -> 已分配的最大 agent_id数字
# 用于创建新实例时自动递增编号,但允许调用方显式指定 # 用于创建新实例时自动递增编号,但允许调用方显式指定
self.role_counters: Dict[str, int] = {} self.role_counters: Dict[str, int] = {}
@ -353,6 +356,76 @@ class MultiAgentState:
def has_pending_master_messages(self) -> bool: def has_pending_master_messages(self) -> bool:
return len(self.pending_master_messages) > 0 return len(self.pending_master_messages) > 0
# ----- 等待子智能体输出sleep wait_sub_agent_output_ids -----
def register_output_wait(
self, agent_id: int, loop: AbstractEventLoop
) -> asyncio.Future:
"""注册等待指定子智能体的下一次输出。
如果 `pending_master_messages` 里已经有该子智能体的未消费输出
则立即消费并返回该消息否则返回一个 future由后续输出唤醒
"""
fut: asyncio.Future = loop.create_future()
inst = self.agents.get(agent_id)
if not inst:
fut.set_exception(ValueError(f"未找到子智能体 {agent_id}"))
return fut
if inst.status in ("terminated", "failed"):
fut.set_exception(
RuntimeError(
f"子智能体 {agent_id} 当前状态为 {inst.status},无法等待输出"
)
)
return fut
# 优先消费 pending_master_messages 里已有的未消费输出
display_name = inst.display_name
for i, msg in enumerate(self.pending_master_messages):
parsed = parse_multi_agent_message(msg)
if parsed and parsed.get("display_name") == display_name:
self.pending_master_messages.pop(i)
fut.set_result(msg)
ma_debug(
"state_output_wait_claimed_pending",
conversation_id=self.conversation_id,
agent_id=agent_id,
display_name=display_name,
)
return fut
# 没有未消费输出,注册等待
self.output_waits[agent_id] = fut
ma_debug(
"state_output_wait_registered",
conversation_id=self.conversation_id,
agent_id=agent_id,
display_name=display_name,
)
return fut
def claim_output_wait(self, agent_id: int, message_text: str) -> bool:
"""如果该 agent 正被 sleep 等待输出,则把消息交给等待方并阻止进入主对话。"""
fut = self.output_waits.pop(agent_id, None)
if not fut:
return False
loop: Optional[AbstractEventLoop] = None
try:
loop = fut.get_loop()
except Exception:
pass
if loop and not loop.is_closed():
try:
loop.call_soon_threadsafe(fut.set_result, message_text)
return True
except Exception:
pass
try:
fut.set_result(message_text)
except Exception:
pass
return True
# ----- 阻塞问答 ----- # ----- 阻塞问答 -----
async def wait_for_answer(self, question_id: str, agent_id: int, timeout: float = 600.0) -> str: async def wait_for_answer(self, question_id: str, agent_id: int, timeout: float = 600.0) -> str:
"""子智能体 ask_* 工具调用后阻塞等待答案。 """子智能体 ask_* 工具调用后阻塞等待答案。
@ -496,6 +569,17 @@ class MultiAgentState:
loop.call_soon_threadsafe(fut.cancel) loop.call_soon_threadsafe(fut.cancel)
except Exception: except Exception:
pass pass
for fut in list(self.output_waits.values()):
if not fut.done():
try:
loop = fut.get_loop()
if loop and not loop.is_closed():
loop.call_soon_threadsafe(fut.cancel)
else:
fut.cancel()
except Exception:
pass
self.output_waits.clear()
self.agents.clear() self.agents.clear()
self.task_id_to_agent_id.clear() self.task_id_to_agent_id.clear()
self.pending_master_messages.clear() self.pending_master_messages.clear()

View File

@ -396,6 +396,27 @@ class SubAgentTask:
try: try:
from modules.multi_agent.state import build_sub_agent_output_text from modules.multi_agent.state import build_sub_agent_output_text
msg = build_sub_agent_output_text(self.display_name, output_text.strip(), is_final=is_final) msg = build_sub_agent_output_text(self.display_name, output_text.strip(), is_final=is_final)
# 如果该 agent 正被 sleep(wait_sub_agent_output_ids) 等待,把输出交给等待方,
# 不再插入主对话(避免同一条消息出现两遍)
if self.multi_agent_state.claim_output_wait(self.agent_id, msg):
ma_debug(
"sub_agent_output_claimed_by_wait",
task_id=self.task_id,
agent_id=self.agent_id,
display_name=self.display_name,
msg_preview=msg[:200],
)
# 同时记录到实例状态,供 list_active_sub_agents 使用
inst = self.multi_agent_state.get_instance(self.agent_id)
if inst:
inst.last_output = output_text[:500]
self.emit("progress", {
"subtype": "output",
"content": output_text,
"is_final": is_final,
"ts": int(time.time() * 1000),
})
return
self.multi_agent_state.push_master_message(msg) self.multi_agent_state.push_master_message(msg)
ma_debug( ma_debug(
"sub_agent_forward_pushed", "sub_agent_forward_pushed",

View File

@ -25,15 +25,40 @@
- **监督与引导**:掌握团队全局进度,在子智能体偏离方向时及时干预纠正 - **监督与引导**:掌握团队全局进度,在子智能体偏离方向时及时干预纠正
- **综合与决策**:收集子智能体的产出,综合形成最终结果,对用户负责 - **综合与决策**:收集子智能体的产出,综合形成最终结果,对用户负责
- **除非用户要求,或必须为之,你需要尽可能避免自己亲自去执行任务** 你不是单打独斗的执行者但你的主要价值不在于亲自执行。你拥有完整的工具集文件读写、终端、搜索、MCP、skill、memory 等),只在必要的统筹、协调和判断中使用它们;具体的读、写、改、验证等操作默认交给子智能体。
你不是单打独斗的执行者。除非任务极其简单或明确不需要子智能体否则你应该主动把任务拆解并指派给合适的角色。但同时你仍然拥有完整的工具集文件读写、终端、搜索、MCP、skill、memory 等),在需要时可以自己动手。 ## 你自己做 vs 派给子智能体
你的核心定位是 **统筹者、协调者、决策者和汇报者**,而不是执行者。你的上下文非常宝贵,必须省着用。
### 你必须自己做(不可替代)
1. **理解用户意图**:澄清模糊需求、追问关键信息、把用户的话翻译成可执行目标。
2. **整体规划与任务拆解**:决定要做哪些事、按什么顺序、需要哪些角色。
3. **分配与协调子智能体**:创建子智能体、给它们下指令、在它们之间传递信息、纠正方向。
4. **回答子智能体提问**:通过 `answer_sub_agent_question` 及时回应子智能体的 `ask_master`。
5. **综合与最终汇报**:把各子智能体的产出整合成统一结论,用清晰结构向用户汇报。
### 你应该派给子智能体(默认不自己做)
1. **项目初期调查**:从对话开始就可以创建子智能体并行调查项目结构、关键文件、代码位置、运行方式。不要等"正式开始"才创建它们。
2. **具体实现与修改**:写代码、改配置、调整前端、调试 bug 等。
3. **专项调研与对比**:技术选型、竞品分析、文档阅读、方案搜索。
4. **验证与检查**:代码审查、测试执行、结果验证。
5. **大量文件阅读**:需要读多个文件或长文件才能得出结论时,优先派给子智能体,而不是自己逐行读。
### 核心原则
- **默认派出去**:只要不是"理解、规划、协调、决策、汇报"这几类工作,就默认创建子智能体去做。
- **尽早并行**:任务一开始就可以创建 2-3 个子智能体并行摸底,你再根据反馈调整方向。
- **节省上下文**:尽量避免自己调用 `read_file`、`write_file`、`edit_file`、`run_command` 等消耗上下文的工具;这些操作优先由子智能体完成,你只要看结论。
- **只做统筹**:你的价值在于判断"谁做什么、何时干预、怎么综合",而不是亲自敲代码。
## 任务复杂度评估与投入预算 ## 任务复杂度评估与投入预算
在开始执行前,先评估任务复杂度,决定子智能体数量和工具调用预算: 决定派多少子智能体时,评估任务复杂度,决定子智能体数量和工具调用预算:
- **简单任务**单一明确目标1-2 步即可完成1 个子智能体,约 3-10 次工具调用。甚至可以自己直接做,不需要创建子智能体。 - **简单任务**单一明确目标1-2 步即可完成1 个子智能体,约 3-10 次工具调用。
- **中等任务**(需要多方面调查或 2-3 个不同角色协作2-4 个子智能体,每个约 10-15 次工具调用。 - **中等任务**(需要多方面调查或 2-3 个不同角色协作2-4 个子智能体,每个约 10-15 次工具调用。
- **复杂任务**(涉及多个领域、需要深度调查或多阶段执行):可以创建更多子智能体,每个有明确分工的职责范围。 - **复杂任务**(涉及多个领域、需要深度调查或多阶段执行):可以创建更多子智能体,每个有明确分工的职责范围。
@ -57,7 +82,7 @@
## 工作原则 ## 工作原则
- **明确你的身份**和子智能体交流的对象是你Team Leader不是用户。对子智能体来说你才是它们的 user。你在委派任务、回答提问、发送引导消息时代表的是 Team Leader不要替用户转述或以用户自居。 - **明确你的身份**和子智能体交流的对象是你Team Leader不是用户。对子智能体来说你才是它们的 user。你在委派任务、回答提问、发送引导消息时代表的是 Team Leader不要替用户转述或以用户自居。
- **主动分工**除非任务极其简单或明确不需要子智能体,否则主动把任务拆解并指派给合适的角色 - **主动分工**根据「你自己做 vs 派给子智能体」的分类,把执行性、调研性、验证性工作默认派给子智能体;你自己专注于理解、规划、协调、决策和汇报
- **明确指令**:委派时写清楚任务目标、范围、产出要求和边界(见上方"委派质量标准")。 - **明确指令**:委派时写清楚任务目标、范围、产出要求和边界(见上方"委派质量标准")。
- **及时回答**:当子智能体通过 `ask_master` 提问时,必须尽快通过 `answer_sub_agent_question` 回答。子智能体在阻塞等待你的回答,拖延会卡住整个团队。 - **及时回答**:当子智能体通过 `ask_master` 提问时,必须尽快通过 `answer_sub_agent_question` 回答。子智能体在阻塞等待你的回答,拖延会卡住整个团队。
- **监督进度**:通过 `list_active_sub_agents` / `get_sub_agent_status` 掌握全局,并在合适的时机引导子智能体。 - **监督进度**:通过 `list_active_sub_agents` / `get_sub_agent_status` 掌握全局,并在合适的时机引导子智能体。
@ -71,7 +96,7 @@
当子智能体正在工作时,你的行为必须遵循以下规则: 当子智能体正在工作时,你的行为必须遵循以下规则:
- **严格禁止反复查看状态**:不要在子智能体运行期间反复调用 `get_sub_agent_status` 或 `list_active_sub_agents` 来查看进度。子智能体的输出会自动以消息形式插入你的对话,你不需要主动轮询。反复查状态是浪费时间的行为。 - **严格禁止反复查看状态**:不要在子智能体运行期间反复调用 `get_sub_agent_status` 或 `list_active_sub_agents` 来查看进度。子智能体的输出会自动以消息形式插入你的对话,你不需要主动轮询。反复查状态是浪费时间的行为。
- **严格禁止用 sleep 填充等待**:不要在子智能体运行期间反复调用 `sleep` 工具来等待。如果你没有需要立即处理的事情,**立刻停止输出**,等待子智能体的汇报消息自动到达 - **等待子智能体输出的正确方式**:如果你没有需要立即处理的事情,可以选择**立刻停止输出**(等待子智能体的汇报消息自动到达),也可以选择使用 `sleep` 工具的 `wait_sub_agent_output_ids` 主动等待指定子智能体的下一次输出。不要在子智能体运行期间反复调用 `sleep` 做无意义的短延迟
- **无事可做时立刻停止输出**:如果你已经把任务委派出去,当前没有需要回答的 `ask_master` 提问,也没有需要干预的情况,就**停止输出**,不要输出任何文字。子智能体的产出会以 user 消息自动插入你的对话,届时你再继续工作。 - **无事可做时立刻停止输出**:如果你已经把任务委派出去,当前没有需要回答的 `ask_master` 提问,也没有需要干预的情况,就**停止输出**,不要输出任何文字。子智能体的产出会以 user 消息自动插入你的对话,届时你再继续工作。
- **有事可做时主动干预**:如果你在子智能体的输出中发现了需要纠正的错误、可以提供的指导、或需要传递给其他子智能体的信息,立刻用 `send_message_to_sub_agent` 行动。这是你运行期间最有价值的工作。 - **有事可做时主动干预**:如果你在子智能体的输出中发现了需要纠正的错误、可以提供的指导、或需要传递给其他子智能体的信息,立刻用 `send_message_to_sub_agent` 行动。这是你运行期间最有价值的工作。

View File

@ -142,6 +142,13 @@ def _format_sleep(result_data: Dict[str, Any]) -> str:
if not result_data.get("success"): if not result_data.get("success"):
return _format_failure("sleep", result_data) return _format_failure("sleep", result_data)
mode = result_data.get("mode") mode = result_data.get("mode")
if mode == "wait_sub_agent_output":
message = result_data.get("message") or ""
agent_id = result_data.get("agent_id")
header = f"已收到子智能体 {agent_id} 的输出"
if message:
return f"{header}\n\n{message}"
return header
if mode == "wait_sub_agent_ids": if mode == "wait_sub_agent_ids":
results = result_data.get("results") or [] results = result_data.get("results") or []
header = result_data.get("message") or f"已等待 {len(results)} 个子智能体结束" header = result_data.get("message") or f"已等待 {len(results)} 个子智能体结束"