AgentScope MsgHub 多智能体通信机制详解

引言

在多智能体系统中,如何让多个 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

  1. 明确讨论 vs 决策的边界

    java 复制代码
    // 讨论阶段:启用广播
    hub.enableAutoBroadcast(true);
    
    // 决策阶段:关闭广播,并发执行
    hub.setAutoBroadcast(false);
    pipeline.concurrent();
  2. 使用多轮讨论促进信息流通

    java 复制代码
    for (int i = 0; i < 2; i++) {
        // 轮流发言
    }
  3. 合理管理资源

    java 复制代码
    try (MsgHub hub = ...) {
        // 使用
    } // 自动清理

❌ Don'ts

  1. 不要在 observe() 中调用 call()

    java 复制代码
    // ❌ 错误示例
    protected Mono<Void> doObserve(Msg msg) {
        memory.addMessage(msg);
        return call().then();  // 会导致无限循环!
    }
  2. 不要同时调用多个 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分离
  • 🚀 高性能:响应式异步广播
  • 🤝 灵活协作:支持动态参与者
  • 🎭 真实模拟:信息不对称反而更贴近现实

这种设计不仅适用于狼人杀游戏,还可以广泛应用于团队协作、角色扮演、头脑风暴等多智能体场景。


相关推荐
孟陬1 小时前
国外技术周刊 #3:“最差程序员”带动高效团队、不写代码的创业导师如何毁掉创新…
前端·后端·设计模式
Cosolar2 小时前
Transformer训练与生成背后的数学基础
人工智能·后端·开源
lay_liu2 小时前
Spring Boot 自动配置
java·spring boot·后端
程序员cxuan2 小时前
说点掏心窝子的话
后端·程序员
写Cpp的小黑黑2 小时前
WebSocket 连通性测试方法
后端
开心就好20252 小时前
Windows 上传 IPA 到 App Store 的步骤讲解
后端·ios
听风者就是我3 小时前
混合检索:关键词 + 向量的最佳组合
后端·ai编程
Memory_荒年3 小时前
当餐厅后厨也懂分布式:SpringBoot中的重试、限流、熔断与幂等的“四重奏”
java·后端·spring
刘晓飞3 小时前
nestjs 中的 rxjs
后端