引言
在多智能体系统中,如何让多个 AI 智能体高效、安全地进行通信和协作?本文将以 AgentScope-Java 的 MsgHub 为核心,深入解析其订阅 - 广播机制的设计哲学与实现细节。
一、核心问题:多智能体通信的挑战
1.1 传统方案的问题
假设我们有三个狼人智能体 A、B、C,需要讨论今晚要攻击的目标:
❌ 手动传递消息(繁琐且易错)
java
// 没有 MsgHub 的情况
Msg aResponse = wolfA.call().block();
wolfB.observe(aResponse).block();
wolfC.observe(aResponse).block();
Msg bResponse = wolfB.call().block();
wolfA.observe(bResponse).block();
wolfC.observe(bResponse).block();
// ... 代码重复,难以维护
❌ 并发调用(信息不同步)
java
// 同时调用所有 Agent
var responses = Flux.just(wolfA, wolfB, wolfC)
.flatMap(agent -> agent.call())
.collectList()
.block();
// 问题:每个 Agent 都基于相同初始信息,无法互相影响
❌ 同步阻塞(可能死锁)
java
// 如果每个 Agent 都在等其他人先发言
Msg aResponse = wolfA.call().block(); // A 等 B
Msg bResponse = wolfB.call().block(); // B 等 C
Msg cResponse = wolfC.call().block(); // C 等 A
// 💀 死锁!
二、MsgHub 的核心设计
2.1 订阅 - 广播模式
MsgHub 采用 发布 - 订阅模式,通过以下关键机制避免上述问题:
核心组件
java
// AgentBase.java
public class AgentBase {
// 每个 Agent 维护一个订阅者列表
private final Map<String, List<AgentBase>> hubSubscribers;
// observe() 只是被动接收消息,不触发响应
protected Mono<Void> doObserve(Msg msg) {
if (msg != null) {
memory.addMessage(msg); // 存入内存
}
return Mono.empty(); // 不调用 LLM
}
// call() 完成后自动广播给订阅者
private Mono<Msg> notifyPostCall(Msg finalMsg) {
return broadcastToSubscribers(finalMsg).thenReturn(finalMsg);
}
}
订阅关系建立
java
// MsgHub.java
private void resetSubscribers() {
if (enableAutoBroadcast) {
for (AgentBase agent : participants) {
// 找出"其他"参与者
List<AgentBase> others = participants.stream()
.filter(a -> !a.equals(agent))
.collect(Collectors.toList());
// 设置订阅关系:agent → [others]
agent.resetSubscribers(name, others);
}
}
}
订阅关系图示:
css
狼人 A → 订阅者:[狼人 B, 狼人 C]
狼人 B → 订阅者:[狼人 A, 狼人 C]
狼人 C → 订阅者:[狼人 A, 狼人 B]
三、工作流程详解
3.1 完整时序图
sequenceDiagram
participant Hub as MsgHub
participant A as 狼人 A
participant B as 狼人 B
participant C as 狼人 C
Note over Hub: hub.enter()
Hub->>A: resetSubscribers([B, C])
Hub->>B: resetSubscribers([A, C])
Hub->>C: resetSubscribers([A, B])
Note over Hub: 第 1 轮讨论
A->>A: call() - 调用 LLM
Note right of A: Memory: [系统提示]
A-->>B: broadcast("淘汰甲")
A-->>C: broadcast("淘汰甲")
B->>B: observe() - 存入 Memory
C->>C: observe() - 存入 Memory
B->>B: call() - 调用 LLM
Note right of B: Memory: [系统提示,A 的发言]
B-->>A: broadcast("同意")
B-->>C: broadcast("同意")
A->>A: observe() - 存入 Memory
C->>C: observe() - 存入 Memory
C->>C: call() - 调用 LLM
Note right of C: Memory: [系统提示,A,B 的发言]
C-->>A: broadcast("没问题")
C-->>B: broadcast("没问题")
Note over Hub: 第 2 轮讨论(信息更充分)
A->>A: call() - 调用 LLM
Note right of A: Memory: [系统提示,B,C 第 1 轮发言]
3.2 关键代码示例
java
try (MsgHub werewolfHub = MsgHub.builder()
.name("WerewolfDiscussion")
.participants(werewolves.toArray(ReActAgent[]::new))
.announcement(prompts.createWerewolfDiscussionPrompt(gameState))
.enableAutoBroadcast(true) // 启用自动广播
.build()) {
werewolfHub.enter().block(); // 建立订阅关系
// 2 轮讨论,让信息充分流通
for (int i = 0; i < 2; i++) {
for (Player werewolf : werewolves) {
// 顺序调用,每个 Agent 都能听到前面的发言
Msg response = werewolf.getAgent().call().block();
String content = utils.extractTextContent(response);
emitter.emitPlayerSpeak(werewolf.getName(), content, "werewolf_discussion");
}
}
// 投票阶段:关闭广播,确保独立决策
werewolfHub.setAutoBroadcast(false);
FanoutPipeline votingPipeline = FanoutPipeline.builder()
.addAgents(werewolves.stream()
.map(p -> (AgentBase) p.getAgent())
.toList())
.concurrent() // 并发执行
.build();
List<Msg> votes = votingPipeline.execute(votingPrompt, VoteModel.class).block();
}
四、关键技术特性
4.1 避免死锁的秘密
✅ observe() ≠ call()
| 方法 | 触发时机 | 是否调用 LLM | 作用 |
|---|---|---|---|
observe(msg) |
被动接收广播 | ❌ 否 | 仅将消息存入内存 |
call() |
主动调用 | ✅ 是 | 执行 ReAct 循环,生成回复 |
这个分离设计确保了:
- 接收消息不会触发新的响应
- 只有显式调用
call()才会产生输出 - 避免了"收到消息 → 自动回复 → 触发更多回复"的死循环
4.2 响应式异步广播
java
private Mono<Void> broadcastToSubscribers(Msg msg) {
if (hubSubscribers.isEmpty()) {
return Mono.empty();
}
return Flux.fromIterable(hubSubscribers.values())
.flatMap(Flux::fromIterable)
.flatMap(subscriber -> subscriber.observe(msg))
.then(); // 异步非阻塞
}
优势:
- 🚀 非阻塞:广播不会阻塞主流程
- ⚡ 高性能:使用 Project Reactor 的响应式流
- 🔄 背压支持:自动处理消息积压
4.3 信息不对称的设计哲学
问题:顺序发言导致信息差异
less
第 1 轮:
├─ A 发言 → Memory: [系统提示] ← 不知道 B、C 的想法
├─ B 发言 → Memory: [系统提示,A 的话] ← 知道 A,不知道 C
└─ C 发言 → Memory: [系统提示,A、B 的话] ← 知道 A 和 B
解决方案:多轮讨论
java
// 2 轮讨论弥补信息差
for (int i = 0; i < 2; i++) {
for (Player werewolf : werewolves) {
werewolf.getAgent().call().block();
}
}
第 2 轮时:
- A 已经听过 B、C 在第 1 轮的发言
- B 已经听过 A、C 在第 1 轮的发言 + A 在第 2 轮的发言
- C 已经听过所有人第 1 轮 + A、B 第 2 轮的发言
优化建议:轮换发言顺序
java
// 每轮换顺序,减少偏差
for (int i = 0; i < 2; i++) {
List<Player> roundOrder = new ArrayList<>(werewolves);
if (i % 2 == 1) {
Collections.reverse(roundOrder); // 奇数轮倒序
}
for (Player werewolf : roundOrder) {
werewolf.getAgent().call().block();
}
}
五、实际应用场景
5.1 狼人杀游戏
java
// 狼人夜间讨论(私密频道)
try (MsgHub werewolfHub = MsgHub.builder()
.participants(werewolves.map(Player::getAgent))
.announcement(discussionPrompt)
.enableAutoBroadcast(true)
.build()) {
// 自动广播确保信息共享
}
// 白天全体讨论(公共频道)
try (MsgHub dayHub = MsgHub.builder()
.participants(alivePlayers.map(Player::getAgent))
.announcement(dayDiscussionPrompt)
.enableAutoBroadcast(true)
.build()) {
// 所有存活玩家共享信息
}
5.2 团队头脑风暴
java
// 创意收集阶段:启用广播,激发灵感
MsgHub brainstormingHub = MsgHub.builder()
.participants(teamMembers)
.enableAutoBroadcast(true)
.build();
// 投票决策阶段:关闭广播,避免从众
brainstormingHub.setAutoBroadcast(false);
FanoutPipeline votingPipeline = FanoutPipeline.builder()
.addAgents(teamMembers)
.concurrent()
.build();
5.3 多人角色扮演
java
// 剧本杀、互动小说等场景
MsgHub storyHub = MsgHub.builder()
.participants(characters)
.announcement(storyContext)
.enableAutoBroadcast(true)
.build();
// 每个角色的发言都会成为其他人的上下文
六、性能与扩展性
6.1 线程安全
java
// 使用 CopyOnWriteArrayList 保证线程安全
private final List<AgentBase> participants =
new CopyOnWriteArrayList<>(builder.participants);
6.2 动态参与者
java
// 运行时添加/删除参与者
hub.add(newAgent).block();
hub.delete(existingAgent).block();
// 自动更新订阅关系
6.3 资源管理
java
// try-with-resources 自动清理
try (MsgHub hub = createHub()) {
hub.enter().block();
// 使用 hub
} // 自动调用 exit(),释放订阅关系
七、最佳实践总结
✅ Do's
-
明确讨论 vs 决策的边界
java// 讨论阶段:启用广播 hub.enableAutoBroadcast(true); // 决策阶段:关闭广播,并发执行 hub.setAutoBroadcast(false); pipeline.concurrent(); -
使用多轮讨论促进信息流通
javafor (int i = 0; i < 2; i++) { // 轮流发言 } -
合理管理资源
javatry (MsgHub hub = ...) { // 使用 } // 自动清理
❌ Don'ts
-
不要在 observe() 中调用 call()
java// ❌ 错误示例 protected Mono<Void> doObserve(Msg msg) { memory.addMessage(msg); return call().then(); // 会导致无限循环! } -
不要同时调用多个 Agent 的 call()
java// ❌ 除非你确实需要并发 Flux.just(agent1, agent2) .flatMap(agent -> agent.call()) // 并发调用 .block();
八、源码地址
- AgentScope-Java : github.com/agentscope-...
- MsgHub 源码 :
agentscope-core/src/main/java/io/agentscope/core/pipeline/MsgHub.java - AgentBase 源码 :
agentscope-core/src/main/java/io/agentscope/core/agent/AgentBase.java
结语
MsgHub 通过巧妙的订阅 - 广播机制,实现了高效、安全的多智能体通信:
- 🎯 避免死锁:observe/call分离
- 🚀 高性能:响应式异步广播
- 🤝 灵活协作:支持动态参与者
- 🎭 真实模拟:信息不对称反而更贴近现实
这种设计不仅适用于狼人杀游戏,还可以广泛应用于团队协作、角色扮演、头脑风暴等多智能体场景。