你有没有遇到过这种情况:公司里同时有客服AI、数据分析AI、工单处理AI,每个单拎出来都能干活,但想让它们互相配合,比如客服AI把用户诉求转给工单AI去处理,却只能靠你在中间写胶水代码。我在做多智能体项目时,这个问题被放大得特别明显。直到我认真研究了Google开源的A2A协议,才意识到agent之间协作这件事,本不该这么痛苦。
A2A协议(Agent-to-Agent)是Google于2025年4月发布的开放协议,专门解决AI代理之间的发现、通信和协作问题。和其他把agent接入工具的协议不同,A2A把每个agent都当成可对话的实体,让它能向另一个agent发送任务、接收结果、追问细节。下面我从原理层面拆解A2A的工作方式,适合准备做多agent协作但不想被厂商绑定的开发者,也适合想搞懂agent间通信底层逻辑的技术决策者。
1. 聊天机器人之间为什么还需要一套协议
现在做单个功能Agent已经不难。难的是让多个Agent像团队一样一起工作。举个真实的例子:我用AI做了个日程管理助手,它能读取邮件、识别用户“周五下午开评审会”的意图,然后创建日历事件。这没问题。但我想让另一个Agent去联系参会人、预订会议室,问题就来了——两个Agent都是我自己开发的,我可以让它们直接调用同一个数据库、共享同一个内存表,等于我在它们之间拉了根私线。
1.1 多Agent协作的真实困境
私线一多,就变成了蜘蛛网。假设有三个Agent(A、B、C)需要两两协作,至少得写三条私线;再加一个D,连线变成六条;五个Agent就是十条。每条私线的接口风格、参数语义、错误处理都不同,维护成本指数级上升。更隐蔽的问题是语义不一致:A发一个“query_inventory”,B理解为“库存查询”,C却理解成“库存的单价查询”。没有公共规范,就只能靠人肉对齐。
我当时还试过用企业内部的消息队列(比如Kafka)来做Agent通信。能解决一部分问题,但消息队列本质上是“发布-订阅”模型,Agent之间的对话更像“请求-响应”:我需要向Agent X发起一个任务,等待它的反馈,甚至中途打断它。消息队列的异步拓扑天然不适合这种交互。所以A2A协议出来的时候,我第一反应是:终于有人把“Agent之间的协作语言”标准化了。
1.2 A2A协议想解决的三件事
A2A的官方定位,说起来特别朴素:让不同厂商、不同技术栈的Agent能在一起工作,且不需要提前知道对方的内部实现。拆开看,它主要解决三个问题。
第一是发现。一个Agent怎么知道另一个Agent存在?知道后怎么知道它“会做什么”?A2A用一张标准化的Agent Card对外描述自己的身份和能力,并通过HTTP端点暴露出来,相当于每个Agent在网上挂了一张“电子名片”。
第二是交互。Agent之间不是“调接口”的关系,而是“派活+协作”的关系。A2A定义了Task、Message、Artifact这些通用概念,让Agent能发送任务、接收中间状态、追问细节、取消任务。这套交互模型比单纯的API调用更贴近人类的沟通习惯。
第三是安全与集成。A2A支持OAuth 2.0等主流认证方式,并且定义了在企业环境里如何控权、怎么做审计。这一点对商业化落地很重要——不然你连一个公司内部的Agent都不敢往外放。
顺便补充一个时间线:2025年4月Google发布A2A后,6月就把它捐给了Linux基金会,成立A2A工作组来继续演进。也就是说它现在不是Google自家的私有协议,而是一个社区驱动的开放标准。对做技术选型的人来说,这一点比协议本身还重要——你不会想为一家公司的私有方案投入两年架构。
2. Agent Card:A2A世界的“自我介绍”与角色模型
要理解A2A,我建议先看Agent Card,因为它最直观。它相当于Agent在互联网上的公开资料页,别的Agent拿到这张卡,就能判断“我要不要跟它合作、怎么跟它合作”。
2.1 一张标准化的Agent名片
Agent Card本身是一个JSON文档,放在Agent服务的根路径或者已知路径下,比如/.well-known/agent.json。它最少要包含这些信息:
{ "name": "CalendarAgent", "description": "负责日程管理、会议安排的智能助手", "url": "https://agent.example.com/", "version": "1.0.0", "capabilities": { "streaming": true, "pushNotifications": false }, "security": { "schemes": [ { "type": "oauth2", "description": "使用OAuth 2.0授权码模式" } ] }, "skills": [ { "id": "create_event", "name": "创建日程", "description": "根据用户自然语言创建日历事件" }, { "id": "query_meeting_room", "name": "查询会议室", "description": "检查指定时间点哪些会议室可用" } ] }注意capabilities字段不是“我能做什么”,而是“我在通信方式上有什么能力”。比如开启了streaming,说明这个Agent支持流式响应,客户端可以先收到零碎结果再拼装;开启了pushNotifications,说明它能主动往你注册的回调地址推任务状态。这两个开关直接决定了客户端用哪种交互方式。
skills列表才是“我会做什么”。里面每一项有一个id和description。客户端拿到这张卡,会先看description,再看具体skill的description,判断任务能不能交给它。这有点像你打开某家公司的官网,先看主营介绍,再看业务条线。
我在自己的项目里测试过,Agent Card对不确定性的贡献很大:以前多个Agent协作时,对方到底能力边界在哪全凭猜,现在有了标准卡,至少可以自动做“能力筛选”。当然,它不能解决“Agent吹牛”的问题——卡上说会修图,实际接过来可能只是滤镜,这是另一个层面的事。
2.2 既分Client/Server,又彼此对等
A2A协议里其实引入了client和server的说法:主动发起协作的Agent是client,接收任务的Agent是server。但这里要特别强调一点:这不是传统意义上的“调用方-被调用方”的不平等关系。因为每个Agent都可以既当client又当server。
比如一个复杂的跨国协作场景:上海公司的金融分析Agent需要新加坡公司的数据Agent给一份财报摘要。前者发起任务,是client;后者接收任务,是server。但如果数据Agent中途发现需要更进一步的信息,比如需要分析Agent确认某个指标的口径,它也可以反过来发起一个任务,角色就互换了。
所以A2A的模式在协议层面是对等的,不像REST API那样严格区分“服务提供者”和“服务消费者”。这种对等性,让A2A更像两个同事之间来回商量,而不是主从指令。我见过有人把A2A理解成“Agent的远程过程调用”,其实不准确——RPC关心的是“执行一个函数”,A2A关心的是“完成一项任务”,后者天然包含了上下文、中间反馈、多轮澄清这些过程。
2.3 传输协议的选择:JSON-RPC 2.0与HTTPS
A2A没有发明新的传输协议,而是直接复用HTTP和JSON-RPC 2.0。这一点我特别欣赏,因为这意味着任何语言、任何框架都能接入,只要你能处理HTTP请求和JSON。
具体来说,Agent必须暴露一个HTTP端点,接受JSON-RPC 2.0格式的方法调用。核心方法大概有这些:
| 方法 | 作用 |
|---|---|
initialize | 建立连接前,客户端用它确认对方的基本信息和协议版本 |
message | 向Agent发送一条Message,用于无状态或者对话式的交互 |
tasks/send | 下发一个任务,返回任务结果或任务ID |
tasks/get | 根据任务ID查询当前状态 |
tasks/cancel | 取消一个正在执行或等待中任务 |
tasks/resubscribe | 重新订阅某个任务的状态变更 |
用JSON-RPC而不是自定义协议,还有个附带好处:生态成熟,很多语言都有现成SDK,不需要自己造轮子。传输层用HTTPS,既保证了基本加密,又为身份认证留下了扩展空间。安全性上A2A没有自己做一套,而是复用OAuth 2.0这些企业里已经跑通的方案——老老实实站在巨人的肩膀上。
3. 任务生命周期:A2A协作的核心状态机
如果你只记一点A2A的知识,我建议记住Task(任务)这个概念。它是A2A里最核心的抽象,理解了任务生命周期,就理解了整个协议的运转方式。
3.1 Task是协作的“经办单”
你可以把Task想象成公司里的“经办单”:一个任务进来,有一个编号,有负责人,有当前状态。无论任务被分给谁,这个编号全程不变,双方靠它对齐进度。
A2A定义了Task的几个状态:
| 状态 | 含义 |
|---|---|
submitted | 任务已提交,对方已收到,尚未开始处理 |
working | 任务正在执行中 |
input-required | 需要更多信息,等待发起方补充 |
completed | 任务成功完成 |
failed | 任务执行失败 |
canceled | 任务被发起方取消 |
awaiting-execution | 任务已排队,但还没轮到自己执行(如果目标Agent有并发控制) |
状态流转的直观路径是submitted -> working -> completed,失败则走working -> failed,需要补充信息时走input-required,这时候client可以再发一条带补充信息的请求,让它回到working。
这里有个设计上的细节:input-required很重要,但很多Agent框架都忽略它。我在自己项目里发现,大部分Agent协作失败,不是因为技术问题,而是因为对方需要澄清用户意图,但发起方直接当成死任务挂了。A2A把“追问”显式建模成状态,等于是在协议层面承认了“Agent之间可以对话,而不只是递交”。
3.2 Message、Part与Artifact的关系
Task之外,A2A还有几个容易混淆的概念:Message、Part和Artifact。
Message是Agent之间往来的具体内容,可以类比微信里的一条消息。每条Message由若干个Part组成,Part可以是一段文本、一张图片、一份PDF文件,甚至是一段结构化JSON。这个设计的好处很直接:一条任务请求里,既要写“请分析这份财报”,又要附上财报PDF,文本在Part1,文件在Part2,一起放进Message。
Artifact则完全不同,它是Task执行过程中产生的结果“物”。比如分析Agent完成财报解读,产出一份“财报解读报告”,这就是Artifact。区别在于:Message是沟通过程中的对话内容,Artifact是协作成果的产出物。所以Task最终会关联一个或多个Artifact,而Message只是过程中的数据交换载体。
我把这几个概念用一个例子串起来:我是主Agent,向某个报表Agent发了一个Task,内容为“生成第一季度销售汇总”。这条请求是一个Message,其中Part1是指令文本,Part2是原始销售数据文件。报表Agent开始工作,期间发回一条Message说“原始文件中包含缺失值,是否用均值补全”,这是Message,不是Artifact。我回复“可以,并标注缺项”,它继续运行,最终返回一个Excel文件,这个Excel才是Artifact。
3.3 同步、轮询、推送与SSE:四种交互姿势
A2A在设计上很聪明的一点,是它不强迫所有Agent用同一种交互方式,而是提供了四种模式,通信双方根据能力协商选择。
先说同步。所谓同步,是指调用方发出tasks/send后,对方在同一个HTTP响应里直接返回最终结果。这种方式适合短任务,比如“把英文翻译成中文”,几秒内能出结果,没必要搞异步。
但现实里很多Agent任务耗时长,比如“爬取100个网页并生成摘要”。这时同步等待会让客户端长时间挂起,于是一般用异步:tasks/send先返回任务ID和当前状态(比如submitted),客户端隔一段时间再调用tasks/get查询进度。这就是轮询。
轮询虽然实现简单,但效率不高,频繁查空转还浪费HTTP请求。所以A2A支持推送:如果server的Agent Card里声明了pushNotifications: true,client可以提供一个回调URL,server在任务状态变化时主动POST一个状态更新过去。屋里有人说这像webhook,确实就是同一个思路。
第四种是流式输出,对应的是streaming能力。服务端通过SSE(Server-Sent Events)向客户端持续推送中间结果。比如一个AI创作Agent开始写文章,你可以看到它一个字一个字往外蹦,而不是等整篇生成完才一次性返回。对用户体感来说,流式输出明显比“干等5秒然后一次性弹出一大篇”友好得多。
我在实际项目里的建议是:短任务直接同步,长任务优先让server开SSE流式,不具备条件就退化为轮询。推送通知适合内部系统间协作,因为需要双方都有公网回调能力,本地开发环境下配置起来会比较麻烦。
4. A2A与MCP的分工:一个管队友,一个管工具
聊A2A的人,几乎一定会聊到MCP。因为这两个协议听起来都跟Agent生态有关,而且名字都带“C/P”,容易混。我在社区的问答里也见过不少“是不是有了MCP就不需要A2A了”的问题。我的回答是:它们解决的不是同一个问题,非但不冲突,反而应该配合使用。
4.1 MCP解决的是Agent到工具的最后一公里
MCP(Model Context Protocol)最初由Anthropic提出,后来Google、OpenAI等也陆续加入生态。它要解决的问题是:怎么让LLM统一地调用外部工具和数据源。
没有MCP之前,你要让AI的行为依赖一个数据库查询,得专门给它写一个工具函数,让模型自己决定何时调用。每个工具一套写法,每接一个新数据源就要写新代码。MCP把这些“AI与外部世界交互”的动作标准化了:模型通过MCP client,去连接一个个MCP server,每个server封装了一组工具或数据资源。
可以粗暴地理解:MCP解决的是“Agent如何拿到信息和执行动作”,它让Agent有了用工具的双手。A2A解决的是“Agent如何和别的Agent合作”,它让Agent有了能对话的队友。一个是纵向的“Agent-工具”通道,一个是横向的“Agent-Agent”通道。
4.2 A2A解决的是Agent到Agent的横向协作
A2A的设计场景,是两个独立Agent之间的协作。它们可能运行在不同公司、不同云厂商、不同技术栈上,不可能共享内部数据模型。A2A为这种场景定义了一套大家都认的“商务沟通规则”:你投递任务,我反馈进度,产出Artifact,中间还可以追问。
MCP继承不了这种职责。你可以想象,如果A2A只提供工具调用,那就意味着每个Agent都要知道“另一个Agent有哪些内部方法”。这正好违反了解耦原则,而且一旦另一个Agent内部改了方法名,所有协作方一起挂。有了A2A的任务级抽象,协作双方只需要知道“对方能完成什么任务”以及“输入输出格式是什么”,不需要理解对方内部架构。
所以,一个Agent内部可以用MCP去访问数据库、文档、API;对外则用A2A跟另一个Agent互通。两层协议管的事情不同,不存在谁取代谁。
4.3 实际项目中的组合架构
我在自己搭的一个多Agent系统里,就是这样组合的。以“用户让客服Agent预约会议室”为例,完整链路是:
- 用户对客服Agent说:“帮我预约周五下午的会议室。”
- 客服Agent先用MCP调用会议室查询工具,拿到可用会议室列表。
- 客服Agent发现涉及跨团队的日程同步,于是用A2A向日历Agent发一个Task,内容为“创建会议室预约事件,参会人包括市场部四人”。
- 日历Agent内部通过MCP调用企业的日历API,创建事件。
- 日历Agent通过A2A返回任务状态和最终的日历event作为Artifact。
- 客服Agent拿到结果后,用MCP调用邮件工具通知参会人。
整个过程里,A2A管的是客服Agent和日历Agent之间的横向协作,MCP管的是各自内部的纵向工具调用。两层各司其职,逻辑很清晰。对这种组合架构,我的体会是:不要试图让一个Agent直接调用另一个Agent的MCP工具,否则耦合会非常深,一旦对方内部工具升级,你的调用就废了。通过A2A的任务边界,把耦合切断在“任务”这一层,是最稳的。
5. 动手把一个Agent暴露成A2A服务:流程与踩坑
原理讲完,说点实操。如果你想把自己手头的Agent接入A2A生态,最直接的做法是写一个A2AServer适配层。下面是我验证过的最小路径和几个值得注意的坑。
5.1 最小实现需要准备哪些东西
实现一个能被其他Agent调用的A2A服务,从最小规模看,你只需要做三件事:一份Agent Card、一个接收JSON-RPC请求的HTTP端点、一套任务状态管理逻辑。
Agent Card可以直接放在Agent服务的根目录下,路径固定为/.well-known/agent.json。HTTP端点负责路由各个JSON-RPC方法,最核心的是tasks/send、tasks/get、message。任务状态管理则是在服务端维护一张任务表,记录每个任务ID对应的状态、输入Message、输出Artifact。
用官方社区提供的A2ASDK来搭的话,代码骨架大致是这样(不同版本API可能会有微调,核心流程是一样的):
from a2a.sdk import A2AServer, A2AServerConfig from a2a.types import Task, Message, TaskState def handle_send_task(task): # 保存任务,返回 task_id 和状态 ... server = A2AServer( config=A2AServerConfig( host="0.0.0.0", port=8080, agent_card_path="./agent.json" ), task_handler=handle_send_task ) server.run()写这段代码不是为了让你照抄,而是想说明:接入A2A时,大部分协议细节都被SDK封装掉了,你需要关心的只是两件事——把Agent的能力如实写进Agent Card,以及提供一个将Task映射到你实际业务逻辑的handler。
如果你的Agent已经有了HTTP服务(比如一个FastAPI应用),也不一定要引入新SDK,最原始的方式是自己实现那几个JSON-RPC方法,本质上就是解析POST请求体里的method和params,然后返回对应结果。SDK只是帮你把反序列化、协议细节、错误码统一处理了。
5.2 最简单的验证方法:先给Agent发一条任务
接入完成后,验证客户端是否能发现和调用,可以用两条命令。
先用curl看Agent Card是否正常返回:
curl https://your-agent.example.com/.well-known/agent.json如果能看到完整的JSON文档,说明发现机制没问题。然后用A2A client模拟另一个Agent发送任务:
curl -X POST https://your-agent.example.com/ \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tasks/send", "params": { "task": { "message": [ { "role": "user", "parts": [ {"text": "帮我生成一份季度销售简报"} ] } ] } } }'如果配置得当,你会收到一个包含taskId的响应,后续可以轮询tasks/get来查看状态,直到出现completed并且携带Artifact。这一步跑通,意味着一个最小A2A协作闭环已经建立。
我强烈建议,在做多Agent编排之前,先单独把每个Agent用这种方式验证一遍。因为多Agent链路一旦出现问题,排查成本是叠加的:你先要知道A到底有没有把任务发给B,B有没有正确处理,C有没有收到结果。单点验证能先消灭一半的变量。
5.3 我在集成A2A时踩过的几个坑
坑一:Agent Card里的capabilities声明和实际行为不一致。最常见的是我声明了streaming: true,但后端的SSE实现只发了最终结果,没发中间事件。客户端在复杂场景下会认为连接异常,直接报错。反过来,如果声明支持pushNotifications却没有实现回调推送,对方等待推送更新就会一直卡着。所以,Agent Card上的能力声明一定要和实际行为对齐,宁可保守,不要夸大。
坑二:任务状态机的分支处理不完整。很多初版实现只处理了working -> completed这条happy path,遇到需要澄清的场景就直接返回failed了。这其实是把责任推给了发起方。对方收到failed根本不知道是哪里失败,也没法继续沟通。要真正发挥A2A的价值,至少在业务可能出现歧义的地方,实现input-required状态,并返回一个“需要补充哪些信息”的说明Message。
坑三:异步长任务的超时和幂等。如果你用轮询方式,client侧一定要实现合理的退避策略,不要每秒猛打tasks/get。server侧要为同一任务的重复请求做幂等处理,尤其当task ID由client生成时,避免同一个任务被重复执行两次。我遇到过隔壁团队任务并发控制没做好,同一份报告被Agent自动生成三遍,时间和费用都浪费了。
坑四:本地开发环境的webhook回环。pushNotifications依赖server主动回调client,本地开发时如果两端都在自己的笔记本电脑上,回调URL会变成类似http://localhost:9000/callback的地址,对方根本访问不到。遇到这种场景,要么用内网穿透工具把本地回调地址暴露出去,要么直接退回轮询模式。对于只想在本地验证协议逻辑的团队,轮询是最省事的选择。
整体来说,A2A从协议设计上并不复杂,复杂的是真实业务里的状态混乱和安全边界。但有了标准状态机和Agent Card,至少不同团队不用再为“你们家Agent的创建任务接口传什么参数”开半天对齐会议了。
最后分享一个我自己的体会:A2A目前还在快速演进期,官方工作组每个季度都在更新规范,所以不要急着把所有Agent都重构成A2A。先挑出一两个跨团队的协作场景试点,跑通全链路和异常处理,积累经验后再铺开,比一口气全量接入要稳得多。如果你正在做多Agent系统,我建议你现在就把Agent Card的能力清单整理出来——这一步无论将来用不用A2A,都会让你的系统边界清晰很多。