第八章 多智能体协同:orchestrator + workers 模式,主持Agent调度子Agent群聊
我们接着探讨。在 1.x 版本中,若想实现多个Agent相互对话,通常依赖一个名为 MsgHub 的组件。它把“群组讨论”抽象成一条共享消息总线——任何Agent发出消息后,该消息会自动推送给总线上所有其他成员。这种设计直观易用,但在 2.0 版本中,你会发现这个类已被移除。

那该如何应对?实际上,与之等价的功能完全可以通过一个“主导Agent”来掌控全程。就像主持一场会议,我们不需要一个自动广播的扩音器,只需要一位主持人,他清楚谁该发言、谁该倾听。下面我们通过表格来具体对比1.x与2.0的变化:
1.x MsgHub 含义 | 2.0 替代方案 |
|---|---|
| 多Agent共享消息总线 | 主Agent维护一个 Map 状态 |
| 任一Agent发言自动广播 | 主Agent依次向每个子Agent传递“上一位的发言内容” |
hub.add(a, b, c) | HarnessAgent.builder().subagent(a, b, c) |
hub.broadcast(msg) | 主Agent自行编写prompt,在循环中调用spawn |
| 异步 / 多轮 | 主Agent每轮执行一次 agent.call(...) |
8.2 首个群聊案例:四方圆桌对话
理论讲完,不如直接动手实践一个示例。我们来模拟一个典型场景:产品、技术、运营、法务四个角色针对同一需求展开讨论,这正是团队协作中最容易产生分歧也最能体现多Agent价值的地方。
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.OpenAIChatModel;
import io.agentscope.harness.HarnessAgent;
import io.agentscope.harness.agent.subagent.SubagentDeclaration;
import ja va.nio.file.Path;
import ja va.util.List;
public class Chapter08_Roundtable {
public static void main(String[] args) {
OpenAIChatModel model = OpenAIChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.baseUrl("https://api.openai.com")
.stream(true)
.formatter(new OpenAIChatFormatter())
.build();
SubagentDeclaration product = speaker("product", "产品经理", "关注用户价值与优先级");
SubagentDeclaration tech= speaker("tech","技术负责人", "关注实现成本与稳定性");
SubagentDeclaration ops = speaker("ops", "运营负责人", "关注上线节奏与风险");
SubagentDeclaration legal = speaker("legal", "法务顾问", "关注合规与用户协议");
HarnessAgent host = HarnessAgent.builder()
.name("roundtable_host")
.sysPrompt("""
你是一个会议主持人。
1. 你管辖 4 位参与者:product / tech / ops / legal。
2. 拿到议题后,调用 agent_spawn 让 product 先发言,
然后把 product 的发言喂给 tech,再把 tech 的发言喂给 ops,
最后喂给 legal,legal 收尾。
3. 最后用一句话总结四方共识。
""")
.model(model)
.workspace(Path.of("./workspace"))
.subagent(product)
.subagent(tech)
.subagent(ops)
.subagent(legal)
.build();
host.call(List.of(new UserMessage("user", "议题:我们打算把免费用户的 API 限速从 60/min 降到 30/min,请四方评估。")),
RuntimeContext.empty()).block();
}
static SubagentDeclaration speaker(String id, String role, String concern) {
return SubagentDeclaration.builder()
.name(id)
.description(role + ",参与四方会议;每次发言聚焦自己的关切:" + concern)
.inlineAgentsBody("你是" + role + "。发言简洁(不超过 60 字),聚焦:" + concern)
.build();
}
}
运行这段代码,你会看到四个角色的输出被自然地串联成一段颇具真实感的“圆桌会议纪要”。实现成本极低,而呈现效果却相当生动。
8.3 多轮群组对话:将“上一轮”内容传递给“下一轮”
1.x 中 MsgHub 的核心优势在于多轮对话——上一轮A的发言会自动成为下一轮B的上下文。在 2.0 里,这一能力并未丢失,只是换了一种实现方式:通过主Agent自身的 AgentState 来承载。
具体而言,每执行完一个子Agent,主Agent在自己的 call() 上下文里就自动拥有了该子Agent的回复。当下一轮子Agent被 spawn 时,主Agent可以在 system prompt 中显式地拼接“上一位发言者的内容”。
// 在主 agent 的 system prompt 里加一条规则:
"""
第 N 轮发言前,先在 user 输入里写:
'以下是上一位 speaker 的发言:<上一段完整文本>'
然后再让下一个 subagent 接着说。
"""
有意思的是,主Agent会在每一轮自行拼接这个前缀——完全不需要业务代码中编写循环。这正是 HarnessAgent + agent_spawn 相较于硬编码 Pipeline 更灵活的地方:LLM 自己就能组织好“上下文传递”这一任务。
8.4 防止“消息风暴”
不过话说回来,1.x 的 MsgHub 也有一个长期被诟病的问题:4个Agent在Hub中互相发消息,运行5轮之后token用量就会暴增。2.0 通过两种机制来解决此问题。
8.4.1 限制发言轮次
最直接且有效的办法,就是在主Agent的 system prompt 中加入一条规则:"每位参与者最多发言 1 轮"。LLM 看到这一约束,自然会避免重复生成对话。
8.4.2 使用 CompactionMiddleware 自动摘要
另一种更优雅的方式是挂载一个 CompactionMiddleware:
HarnessAgent host = HarnessAgent.builder()
...
.middleware(new CompactionMiddleware(CompactionConfig.builder()
.triggerTokens(8000) // 上下文超过 8k token 时压缩
.keepRecentMessages(6) // 保留最近 6 条
.summaryTarget("MEMORY.md")
.build()))
.build();
当上下文超过 8k token 时,它会自动将“早期的发言”压缩成摘要,写入 workspace/MEMORY.md 文件。下一章我们将详细拆解这一机制,此处先了解其存在即可。
8.5 最小迁移清单(1.x MsgHub → 2.0 orchestrator)
如果你正在从 1.x 迁移,下面这张速查表能帮你快速定位关键变化。请记住一个核心原则:1.x 要求你手动编排Agent的顺序,而 2.0 则把这个顺序放到了 system prompt 中。
| 1.x 用法 | 2.0 等价实现 |
|---|---|
MsgHub hub = new MsgHub() | 隐式:主 agent 持有 subagent 列表 |
hub.add(a, b, c) | HarnessAgent.builder().subagent(a, b, c) |
hub.broadcast(Msg) | 主 agent 自己组织 prompt,循环 spawn |
hub.enter().broadcastAll(...) | 主 agent 一次性发多 spawn(async) |
Pipelines.conversation([a,b,c]) | 主 agent 写“轮次规则” + subagent 列表 |
迁移技巧其实很简单:先想清楚你的“四方会议”发言顺序是什么样的,然后将这个顺序用自然语言描述到 system prompt 中即可。1.x 时代写 Pipeline 时你本来就要设计这个顺序,现在只是把它挪了个位置。
8.6 完整可运行示例:3 人辩论赛
最后,我们再来看一个更生动的场景——辩论赛。正方、反方陈述观点,最后由裁判判定胜负。这个例子可以作为理解整个 orchestrator + workers 模式的补充素材。
public class Chapter08_Debate {
public static void main(String[] args) {
DashScopeChatModel model = DashScopeChatModel.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build();
SubagentDeclaration pro= debater("pro","正方","AI 会大幅提升生产力");
SubagentDeclaration con= debater("con","反方","AI 不会减少劳动时间");
SubagentDeclaration judge = SubagentDeclaration.builder()
.name("judge")
.description("辩论赛裁判;正反方发言结束后给出一句胜负判定")
.inlineAgentsBody("你是裁判,只返回'正方胜'或'反方胜',不要解释。")
.build();
HarnessAgent host = HarnessAgent.builder()
.name("debate_host")
.sysPrompt("""
你是辩论赛主持人。流程:
1. 拿议题,先 agent_spawn pro,让正方开场
2. 把 pro 发言喂给 con,反方反驳
3. 把 con 反驳喂给 pro,正方再反驳(最多 1 轮)
4. 最后让 judge 收尾
5. 总结一句:哪个论据最有说服力
""")
.model(model)
.workspace(Path.of("./workspace"))
.subagent(pro)
.subagent(con)
.subagent(judge)
.build();
host.call(List.of(new UserMessage("user", "辩题:未来 5 年,AI 是否会替代大部分白领工作?")),
RuntimeContext.empty()).block();
}
static SubagentDeclaration debater(String id, String side, String stance) {
return SubagentDeclaration.builder()
.name(id)
.description(side + "辩手;坚持立场:" + stance)
.inlineAgentsBody("你是" + side + "辩手,立场:" + stance + "。发言不超过 80 字。")
.build();
}
}
8.7 本章小结
- 1.x 的
MsgHub和Pipelines在 2.0 中已被整体移除,取而代之的是SubagentDeclaration+agent_spawn的组合。 - 所谓的“群组讨论”,在 2.0 里本质上就是 orchestrator + workers 模式:一个主Agent充当主持人,用 system prompt 描述清晰发言顺序。
- 多轮群组对话的上下文保留,依赖主Agent自身的
AgentState,不再需要共享消息总线。 - 防止 token 爆炸的两道防线:一是限制发言轮次,二是挂载
CompactionMiddleware实现自动摘要。
