第七章 子 Agent 编排:SubagentDeclaration + agent_spawn,主 Agent 委派子任务
"以前我们用
SequentialPipeline串三个 agent、用FanoutPipeline并行三个 agent、用MsgHub在多 agent 间广播消息......2.0 把这三种用法统一成了 『子 agent + 同步/异步 spawn』 一种范式:你的主 agent 就像一个『包工头』,对 LLM 喊一声'叫个翻译来',它会自己 spawn 一个翻译 subagent 并把结果带回来。"本章你将学到:如何用
SubagentDeclaration描述子 agent、如何让主 agent 通过agent_spawn工具调用它们、同步与异步的差别、以及文件驱动的 subagent 描述如何让 1.x 的 Pipeline 写法彻底消失。
7.1 Pipeline 已经是历史
1.x 提供了一组"组织多个 agent"的抽象:
SequentialPipeline------ 把 agent 串成流水线FanoutPipeline------ 把同一输入广播到多个 agentMsgHub------ 多个 agent 共享同一个消息总线Pipelines.conversation()------ 让多个 agent 在同一个 hub 里自由聊天
这些 API 在 2.0.0-RC2 中被整体移除。原因也很简单:LLM 自己做编排("现在应该叫翻译")远比写死 Pipeline 更鲁棒、更灵活。2.0 用一个"工具调用"代替了所有这些------
| 1.x 概念 | 2.0 替代 |
|---|---|
SequentialPipeline(a, b, c) |
主 agent 决定先 调 a、agent_spawn a 拿结果、再调 b、最后调 c |
FanoutPipeline([a, b, c]) |
主 agent 用 agent_spawn timeout_seconds=0 并行 调 a、b、c |
MsgHub 多 agent 对话 |
主 agent 在循环里 agent_spawn 多个 subagent 并把上轮的回复喂回去 |
Pipelines.conversation() |
同上,但用 HarnessAgent 的 subagent 文件做"角色表" |
⚠️ 2.0 重大变更
2.0 移除了整个
io.agentscope.core.pipeline包。如果你正在把 1.x 旧工程升到 2.0,遇到import io.agentscope.core.pipeline.*编译失败,请按本章 7.6 的迁移清单做改造。
7.2 第一个 subagent 例子:翻译
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.subagent.SubagentDeclaration;
import io.agentscope.harness.HarnessAgent;
import java.nio.file.Path;
import java.util.List;
public class Chapter07_FirstSubagent {
public static void main(String[] args) {
// 1. 描述一个「翻译 subagent」:叫什么、prompt 是什么、用什么模型
SubagentDeclaration translator =
SubagentDeclaration.builder()
.id("translator")
.description("中英互译;输入文本,输出翻译结果")
.sysPrompt("你是一个中英互译助手,只返回翻译后的文本,不要解释。")
.model(DashScopeChatModel.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build())
.build();
// 2. 构造主 agent,把 subagent 注册进去
HarnessAgent main = HarnessAgent.builder()
.name("main")
.sysPrompt("""
你是一个调度员。
需要把用户输入翻译成英文时调用 translator subagent。
""")
.model(DashScopeChatModel.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build())
.workspace(Path.of("./workspace"))
.subagent(translator)
.build();
// 3. 主 agent 会自己判断要不要 spawn translator
main.call(
List.of(new UserMessage("user", "把'今天杭州有雷阵雨'翻译成英文。")),
io.agentscope.core.RuntimeContext.empty())
.block();
}
}
跑一下你会发现:主 agent 在 1 轮里就调用了 agent_spawn 工具,把任务交给 translator,再把 translator 的输出整合成最终回复------整个过程对业务代码透明。
7.3 SubagentDeclaration 字段速查
SubagentDeclaration(io.agentscope.core.subagent)描述一个 subagent:
| 字段 | 必填 | 说明 |
|---|---|---|
id |
是 | 唯一 ID,主 agent 通过这个 ID 来 agent_spawn |
description |
是 | 主 agent 看这段描述来决定是否调用------写好描述等于写好路由表 |
sysPrompt |
是 | subagent 的系统提示 |
model |
否 | 缺省时继承主 agent 的 model |
tools |
否 | 缺省时继承主 agent 的 tool 组 |
middleware |
否 | subagent 自己的中间件(不继承主 agent) |
workspace |
否 | 缺省时与主 agent 共享 workspace |
写好
description非常关键------主 agent 完全靠它判断"我该不该调这个 subagent"。描述里务必写清楚:subagent 是干嘛的、什么时候该调、输入和输出是什么。
7.4 同步 vs 异步:靠 timeout_seconds
agent_spawn 工具的入参:
| 字段 | 含义 |
|---|---|
agent_id |
要调用的 subagent ID |
task |
给 subagent 的自然语言任务 |
timeout_seconds |
同步超时阈值 :> 0 同步等待 N 秒;= 0 立即返回(异步) |
7.4.1 同步 spawn(timeout_seconds > 0)
主 agent 等到结果回来再继续推理。最常用------"主 agent 把 subagent 当成工具函数"。
主 agent:
1. LLM 决定调用 translator,timeout_seconds=10
2. Harness 启动 translator subagent,等它跑完(最多 10 秒)
3. 把 subagent 的最终输出回填给主 agent
4. 主 agent 继续推理
7.4.2 异步 spawn(timeout_seconds = 0)
主 agent 立刻拿到一个"任务 ID + 状态"对象,主 agent 可以继续做别的事 ,稍后再用 agent_list / agent_send 拉结果。
主 agent:
1. LLM 决定"同时调研 3 个问题",对 3 个 subagent 各发一次 async spawn
2. 3 个 subagent 并行启动(fanout)
3. 主 agent 立即拿到 3 个 task_id
4. 主 agent 写 `todo_write` 记下要跟踪
5. 后续轮次里主 agent 用 `agent_send` 问"搞完没?"
异步 spawn 的最佳搭档是
todo_write:主 agent 起任务时把 task_id 写到 todo 列表,每轮agent_send看哪些完成、回填哪些结果。详见第 12 章 Plan Mode。
7.5 文件驱动的 subagent:让 prompt 离开 Java 代码
SubagentDeclaration 写死在 Java 里没问题,但生产中我们常希望"产品经理改一段描述就能调整路由"------2.0 推荐用文件描述:
workspace/
└── subagents/
├── translator.md
├── weather_lookup.md
└── invoice_parser.md
每个文件就是一份 YAML/Markdown 描述:
# translator.md
id: translator
description: 中英互译;输入文本,输出翻译结果
sysPrompt: |
你是一个中英互译助手。
只返回翻译后的文本,不要解释。
然后:
HarnessAgent main = HarnessAgent.builder()
...
.workspace(Path.of("./workspace")) // 自动扫描 subagents/*.md
.build();
HarnessAgent.builder().workspace(...) 会自动加载 workspace/subagents/ 下所有 *.md / *.yaml 文件,省去 Java 端重复注册。
7.6 最小迁移清单(1.x Pipeline → 2.0 subagent)
| 1.x 用法 | 2.0 等价 |
|---|---|
SequentialPipeline(a, b, c).run(x) |
主 agent 依次 agent_spawn a / b / c(同步) |
FanoutPipeline([a, b, c]).run(x) |
主 agent 一次发 3 个 timeout_seconds=0 的 async spawn |
MsgHub.broadcast(msg) |
主 agent 写一段自然语言 prompt,让所有 subagent 各自跑 |
Pipelines.conversation([a, b, c]) |
主 agent 持有 subagent 列表,每轮 agent_spawn + 拼接 |
pipeline.run(Msg) |
main.call(List.of(new UserMessage("user", task)), ctx) |
迁移技巧:把 Pipeline 的每一步都想象成"主 agent 的一个工具调用"。1.x 写死的执行顺序,2.0 让 LLM 在 system prompt + subagent description 的引导下自主决定------一开始可能觉得不踏实,跑两个 case 就发现它比硬编码灵活得多。
7.7 完整可运行示例
import io.agentscope.core.RuntimeContext;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.subagent.SubagentDeclaration;
import io.agentscope.harness.HarnessAgent;
import java.nio.file.Path;
import java.util.List;
public class Chapter07_Fanout {
public static void main(String[] args) {
DashScopeChatModel model = DashScopeChatModel.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build();
// 3 个并行调研 subagent
SubagentDeclaration legal = research("legal", "做法律合规研究");
SubagentDeclaration finance = research("finance", "做财务分析");
SubagentDeclaration tech = research("tech", "做技术可行性评估");
HarnessAgent main = HarnessAgent.builder()
.name("project_due_diligence")
.sysPrompt("""
你是一个项目尽调主管。
同时调用 legal / finance / tech 三个 subagent 调研同一议题,
最后综合三方结果给出一句话结论。
""")
.model(model)
.workspace(Path.of("./workspace"))
.subagent(legal)
.subagent(finance)
.subagent(tech)
.build();
main.call(
List.of(new UserMessage("user", "我们想在杭州开一家 50 平米咖啡店,请三路调研。")),
RuntimeContext.empty())
.block();
}
static SubagentDeclaration research(String id, String role) {
return SubagentDeclaration.builder()
.id(id)
.description(role + ";输入是项目描述,输出是结构化要点(最多 5 条)")
.sysPrompt("你是一个尽调分析师,专注 " + role + "。")
.build();
}
}
跑一下你会看到三个 subagent 的输出被自动拼成一份"尽调备忘录"。
7.8 本章小结
- 2.0 移除了 1.x 的 Pipeline / MsgHub;改用
SubagentDeclaration+ 主 agent 自带的agent_spawn工具。 - 同步 spawn(
timeout_seconds > 0)= 拿结果再走;异步 spawn(timeout_seconds = 0)= 立即返回 task_id,主 agent 后续用agent_send拉。 - 写好
description字段 = 写好路由表------主 agent 全靠它判断"该不该调"。 - 文件驱动:
workspace/subagents/*.md让产品经理也能调路由。
下一章我们把同样的思路推到"多 agent 协作"------用 orchestrator + workers 的模式做狼人杀 / 群聊 / 多 agent 决策。