feat(multi-agent): 重构 Team Leader 提示词分工策略并新增 sleep wait_sub_agent_output_ids
This commit is contained in:
parent
11ffdc2bb5
commit
3138d23687
@ -103,7 +103,7 @@ class ToolsDefinitionCoreToolsMixin:
|
||||
"type": "function",
|
||||
"function": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": self._inject_intent({
|
||||
@ -115,6 +115,11 @@ class ToolsDefinitionCoreToolsMixin:
|
||||
"type": "string",
|
||||
"description": "等待指定后台 run_command 的 command_id 结束后返回。"
|
||||
},
|
||||
"wait_sub_agent_output_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "integer"},
|
||||
"description": "等待指定子智能体下一次输出并直接返回该消息。多智能体模式专用,且一次只能包含一个编号。"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "等待的原因说明(可选)"
|
||||
|
||||
@ -1080,6 +1080,7 @@ class MainTerminalToolsExecutionMixin:
|
||||
elif tool_name == "sleep":
|
||||
seconds = arguments.get("seconds")
|
||||
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")
|
||||
reason = arguments.get("reason", "等待操作完成")
|
||||
|
||||
@ -1088,23 +1089,60 @@ class MainTerminalToolsExecutionMixin:
|
||||
provided += 1
|
||||
if wait_sub_agent_ids:
|
||||
provided += 1
|
||||
if wait_sub_agent_output_ids:
|
||||
provided += 1
|
||||
if wait_runcommand_id:
|
||||
provided += 1
|
||||
if provided == 0:
|
||||
result = {
|
||||
"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:
|
||||
result = {
|
||||
"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:
|
||||
if getattr(self, "multi_agent_mode", False):
|
||||
result = {
|
||||
"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:
|
||||
result = {"success": False, "error": "wait_sub_agent_ids 必须是非空数组"}
|
||||
|
||||
@ -286,6 +286,9 @@ class MultiAgentState:
|
||||
# 一个 agent 可能同时只阻塞在一个 ask 工具上(最简实现)
|
||||
# key = agent_id, value = question_id(表示当前 agent 正阻塞等待)
|
||||
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(数字)
|
||||
# 用于创建新实例时自动递增编号,但允许调用方显式指定
|
||||
self.role_counters: Dict[str, int] = {}
|
||||
@ -353,6 +356,76 @@ class MultiAgentState:
|
||||
def has_pending_master_messages(self) -> bool:
|
||||
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:
|
||||
"""子智能体 ask_* 工具调用后阻塞等待答案。
|
||||
@ -496,6 +569,17 @@ class MultiAgentState:
|
||||
loop.call_soon_threadsafe(fut.cancel)
|
||||
except Exception:
|
||||
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.task_id_to_agent_id.clear()
|
||||
self.pending_master_messages.clear()
|
||||
|
||||
@ -396,6 +396,27 @@ class SubAgentTask:
|
||||
try:
|
||||
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)
|
||||
# 如果该 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)
|
||||
ma_debug(
|
||||
"sub_agent_forward_pushed",
|
||||
|
||||
@ -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 次工具调用。
|
||||
- **复杂任务**(涉及多个领域、需要深度调查或多阶段执行):可以创建更多子智能体,每个有明确分工的职责范围。
|
||||
|
||||
@ -57,7 +82,7 @@
|
||||
## 工作原则
|
||||
|
||||
- **明确你的身份**:和子智能体交流的对象是你(Team Leader),不是用户。对子智能体来说,你才是它们的 user。你在委派任务、回答提问、发送引导消息时,代表的是 Team Leader,不要替用户转述或以用户自居。
|
||||
- **主动分工**:除非任务极其简单或明确不需要子智能体,否则主动把任务拆解并指派给合适的角色。
|
||||
- **主动分工**:根据「你自己做 vs 派给子智能体」的分类,把执行性、调研性、验证性工作默认派给子智能体;你自己专注于理解、规划、协调、决策和汇报。
|
||||
- **明确指令**:委派时写清楚任务目标、范围、产出要求和边界(见上方"委派质量标准")。
|
||||
- **及时回答**:当子智能体通过 `ask_master` 提问时,必须尽快通过 `answer_sub_agent_question` 回答。子智能体在阻塞等待你的回答,拖延会卡住整个团队。
|
||||
- **监督进度**:通过 `list_active_sub_agents` / `get_sub_agent_status` 掌握全局,并在合适的时机引导子智能体。
|
||||
@ -71,7 +96,7 @@
|
||||
当子智能体正在工作时,你的行为必须遵循以下规则:
|
||||
|
||||
- **严格禁止反复查看状态**:不要在子智能体运行期间反复调用 `get_sub_agent_status` 或 `list_active_sub_agents` 来查看进度。子智能体的输出会自动以消息形式插入你的对话,你不需要主动轮询。反复查状态是浪费时间的行为。
|
||||
- **严格禁止用 sleep 填充等待**:不要在子智能体运行期间反复调用 `sleep` 工具来等待。如果你没有需要立即处理的事情,**立刻停止输出**,等待子智能体的汇报消息自动到达。
|
||||
- **等待子智能体输出的正确方式**:如果你没有需要立即处理的事情,可以选择**立刻停止输出**(等待子智能体的汇报消息自动到达),也可以选择使用 `sleep` 工具的 `wait_sub_agent_output_ids` 主动等待指定子智能体的下一次输出。不要在子智能体运行期间反复调用 `sleep` 做无意义的短延迟。
|
||||
- **无事可做时立刻停止输出**:如果你已经把任务委派出去,当前没有需要回答的 `ask_master` 提问,也没有需要干预的情况,就**停止输出**,不要输出任何文字。子智能体的产出会以 user 消息自动插入你的对话,届时你再继续工作。
|
||||
- **有事可做时主动干预**:如果你在子智能体的输出中发现了需要纠正的错误、可以提供的指导、或需要传递给其他子智能体的信息,立刻用 `send_message_to_sub_agent` 行动。这是你运行期间最有价值的工作。
|
||||
|
||||
|
||||
@ -142,6 +142,13 @@ def _format_sleep(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("sleep", result_data)
|
||||
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":
|
||||
results = result_data.get("results") or []
|
||||
header = result_data.get("message") or f"已等待 {len(results)} 个子智能体结束"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user