技术文档 | 当 Agent 遇上 Pulsar:如何重构 A2A 协议,玩转事件驱动架构

本文来自 Pulsar 社区贡献者 Sinan Liu

如果你也有关于 Pulsar 的技术解析、实践应用、花式玩法等内容想与社区分享,欢迎向我们投稿。

大家好,我是Sinan Liu。Github ID: Denovo1998。

最近 AI Agent 的浪潮是一波接一波,从单个能用工具的智能体,到多个 Agent 协同工作的复杂系统,整个行业都在飞速进化。我们正从 Level 2: AI Agent 迈向 Level 3: Multi Agent

在多 Agent 的世界里,"对话"是关键。Agent 之间怎么沟通?A2A (Agent-to-Agent)(a2aproject.github.io/A2A/) 协议就是为此而生的,它像普通话一样,让来自不同框架、不同厂商的 Agent 能够互相理解、协同工作。

A2A 协议最初的设计是基于 HTTP 的,简单直接,大家都会用。但随着我们的系统越来越复杂,我们发现,只靠 HTTP 的"打电话"模式,在某些场景下有点力不从心了。

我们不禁开始思考:Agent 间的通信,能不能不只是打电话,而是像一个高效、可靠的"中央邮局"?于是,我们把目光投向了事件驱动架构(EDA),并选择了一个强大的"邮差"------ Apache Pulsar

今天,我就带大家深入幕后,聊聊我们是如何对 A2A 的通信架构进行一次初步的重构,并把 Pulsar 无缝集成进来的。

一、最初的模样:HTTP 直连的 A2A 通信

在最开始,我们的 A2AClient 是直接依赖一个具体的 HTTP 客户端的。代码里大概是这个样子:

ini 复制代码
// 重构前的 A2AClient
publicclassA2AClient {
    // 直接依赖一个具体的 HTTP Client 实现
    privatefinal A2AHttpClient httpClient;
    privatefinal String agentUrl;

    publicA2AClient(AgentCard agentCard) {
        this.agentCard = agentCard;
        this.agentUrl = agentCard.url();
        // 硬编码创建了一个 JdkA2AHttpClient
        this.httpClient = newJdkA2AHttpClient();
    }

    public SendMessageResponse sendMessage(...) {
        // ...直接使用 httpClient 发送 HTTP 请求...
        A2AHttpResponseresponse= httpClient.createPost()...post();
        // ...处理 HTTP 响应...
    }
}

这种方式简单明了,对于两个 Agent 之间的点对点通信,完全够用。

但问题来了,当成百上千的 Agent 组成一个复杂的协作网络时:

    1. 强耦合A2AClient 和 HTTP 通信方式焊死在了一起。如果有一天我们想换成 gRPC,或者别的什么协议,怎么办?得大改 A2AClient
    1. 可靠性问题:如果目标 Agent 宕机了,HTTP 请求直接就失败了。没有重试、没有缓冲,消息就这么丢了。在一个需要高可靠的生产环境里,这可不太妙。
    1. "伪"异步:像 SSE (Server-Sent Events) 这种流式通信,本质上还是一个长连接的 HTTP 请求。如果客户端断线了,再想接上之前的进度就很难了。它更像是"在线等",而不是真正的"有消息了再叫我"。

我们需要一个更健壮、更灵活的架构。

二、破局之道:引入"交通工具"抽象层

为了解决上面的问题,我们进行了一次关键的重构。核心思想很简单,就是"依赖倒置"。

说白了,就是 A2AClient 不应该关心消息是"怎么"被送出去的。它不该知道是用 HTTP 卡车,还是 Pulsar 高铁。它只需要把"信"交给一个叫 Transport(交通工具)的接口,由这个接口的具体实现去负责运输。

于是,我们定义了一个干净的 Transport 接口:

arduino 复制代码
// 新定义的 Transport 接口
public interface Transport {
    // 用于请求-响应模式
    CompletableFuture<String> request(String destination, String body);

    // 用于流式/事件模式
    void stream(String destination, String body, Consumer<StreamingEventKind> onEvent, ...);
}

有了这个接口,A2AClient 的代码就变得清爽多了:

typescript 复制代码
// 重构后的 A2AClient
publicclassA2AClient {
    // 不再关心具体实现,只认 Transport 接口
    privatefinal Transport transport;
    privatefinal String agentUrl; // 或者其他地址信息

    publicA2AClient(AgentCard agentCard, Transport transport) {
        this.agentCard = agentCard;
        this.agentUrl = agentCard.url(); // 这里的 url 可能是 HTTP 地址,也可能是 Pulsar 的 topic
        this.transport = transport; // 交通工具是传进来的!
    }

    public SendMessageResponse sendMessage(...) {
        // ...
        // 我只管发货,怎么送是 transport 的事
        StringresponseBody= transport.request(agentUrl, requestBody).get();
        // ...
    }
}

看,A2AClient 现在彻底解放了,它和具体的通信方式完全解耦。原来的 JdkA2AHttpClient 摇身一变,成了 JdkHttpTransport,作为 Transport 接口的一个实现。

这个小小的改动,却为我们打开了通往新世界的大门。

三、Pulsar 登场:事件驱动的"神经网络"

现在我们有了一个可以插入任何"交通工具"的插座,是时候把 Pulsar 这辆"跑车"开进来了。

我们创建了一个新的模块 transport-eda-pulsar,在里面实现了一个 PulsarTransport

在这个新架构里,Pulsar 扮演了几个关键角色:

    1. 可靠的信使 (Request-Reply) :对于 tasks/get 这样的请求-响应调用,我们利用 Pulsar 的 RPC 模式。客户端把请求发到一个 request-topic,然后在一个专属的 reply-topic 上等待回复。消息在 Pulsar 里是持久化的,就算目标 Agent 当时离线,等它上线后依然能收到任务并处理,保证了消息不丢失。**这里我使用了我之前贡献的使用pulsar-client实现的request-reply模型 pulsar-rpc-contrib
    1. 事件总线 (Streaming) :对于 message/stream 这样的流式通信,我们彻底抛弃了 SSE。客户端发起一个流式请求,这个请求消息里会带上一个临时的、唯一的"回信地址"(一个临时的 Pulsar topic)。服务端的 Agent 收到后,就会持续地把状态更新、结果片段等事件,像发快递一样,一个个地发到这个"回信地址"上。这种方式比 SSE 更健壮,即使客户端断线重连,只要订阅关系还在,就不会错过重要更新。这里暂时没办法使用 pulsar-rpc-contrib,或许后续我可以优化一下。

四、点睛之笔:用 AgentCard 实现动态切换

你可能会问,客户端怎么知道一个 Agent 是用 HTTP 还是用 Pulsar 通信呢?

答案就在 AgentCard(Agent 的名片)里。我们对 AgentCard 做了扩展,增加了一个 transport 字段。

以前的 AgentCard:

json 复制代码
{
  "name": "天气查询 Agent",
  "url": "http://weather-agent.com/a2a"
  // ...
}

现在的 AgentCard:

json 复制代码
{
  "name":"天气查询 Agent",
// "url" 字段可能还在,用于 HTTP 兼容
"url":"http://weather-agent.com/a2a",
// 新增的 transport 字段!
"transport":{
    "type":"pulsar",// 告诉大家,我支持 Pulsar!
    "serviceUrl":"pulsar://pulsar.cluster.local:6650",
    "requestTopic":"persistent://public/default/weather-agent-requests",
    "streamingTopic":"persistent://public/default/weather-agent-streaming"
}
// ...
}

客户端在和 Agent 通信前,先看它的"名片"。如果发现 transport 字段里写着 pulsar,就会实例化一个 PulsarTransport 来和它通信。如果没这个字段,就默认使用老的 HTTP 方式。

这个小小的设计,让整个系统变得极其灵活和可扩展。

总结

通过这次重构,我们成功地将 A2A 的通信底层从一个强绑定的 HTTP 实现,升级为了一个可插拔、事件驱动的架构。

Apache Pulsar 在这个新架构中,不仅仅是一个消息队列,它更像是多 Agent 系统的"中央神经网络" 。它让 Agent 之间的通信变得:

  • 更解耦:Agent 们不需要知道彼此的网络地址,只需跟 Pulsar 打交道。
  • 更可靠:消息持久化,任务不丢失。
  • 更具弹性:可以轻松地增加或减少处理任务的 Agent 实例,而无需改动客户端。
  • 真正异步:完美支持长时间运行、客户端可能离线的任务场景。

AI Agent 的世界正在从"个体英雄"走向"团队协作"。而一个强大、灵活的通信基础设施,正是打造顶级 Agent 团队的基石。我们相信,EDA 和 Pulsar 的结合,将为构建下一代复杂、智能的多 Agent 系统提供无限可能。

由于我仅用了一天的时间,这个PR目前还很粗糙,如果你感兴趣请关注:

a2a-java-eda-pulsar (github.com/a2aproject/...)

pulsar-rpc-contrib (github.com/apache/puls...)

希望这次的分享能给你带来一些启发。欢迎在评论区留下你的想法,我们一起交流!

速报

截至发稿时该 PR 已被关闭。

烫知识:按照项目当前规定,A2A 通信必须通过 HTTP(S) 进行

a2aproject.github.io/A2A/v0.2.5/...

考虑到 EDA 的灵活性、可扩展性、松耦合和可靠性,这种架构简直就是为AI Agent量身定制的!那它之所以被排除在外是因为技术适配挑战?还是生态成熟度问题?或者有更好的替代方案?

欢迎大家在评论区分享看法!

相关推荐
逻极15 分钟前
Dify 从入门到精通(第 20/100 篇):Dify 的自动化测试与 CI/CD
人工智能·ai·agent·ai编程·工作流·dify·ai助手
字节跳动数据平台14 小时前
火山引擎Data Agent:突破传统BI局限,用智能对话打造"数据决策大脑"
agent
阿里云云原生15 小时前
垂直和领域 Agent 的护城河:上下文工程
agent
KevinZhang1357916 小时前
ShadowAI 支持二维表格数据生成了
人工智能·agent·ai编程
安思派Anspire17 小时前
通过上下文工程优化LangChain AI Agents(一)
aigc·openai·agent
AI大模型18 小时前
人工智能大模型(LLMs)高效学习指南:30天系统性掌握
程序员·llm·agent
AI大模型18 小时前
RAG(检索增强生成)的深度解析——如何让人工智能告别“胡说八道”?
程序员·llm·agent
小厂永远得不到的男人20 小时前
我在AI公司的作死实录:实习生把Chatbot训练成祖安大师
agent
Q同学1 天前
阿里WebDancer:自主信息搜索Agent
人工智能·llm·agent