1. 为什么需要A2A协议:智能体互连的起点
1.1 从工具调用到智能体协作
过去的两年里,AI大模型领域最明显的一个变化,就是单体模型的“智力竞赛”逐渐收敛,大家的注意力开始转移到另一件事上:让AI智能体真正去干活,而不是只会聊天。干活意味着要调用工具、读取数据、操作软件、访问服务,于是有了MCP(Model Context Protocol)这类工具调用协议,把模型和外部工具之间的连接规范起来。
但工具调用只是第一步。当一个智能体要完成的任务涉及多个专业环节时,比如“帮我在平台上找供应商、比价、然后走审批流程”,单个智能体往往搞不定,需要多个智能体各司其职、相互配合。这时候一个新的问题浮出水面:智能体之间怎么通信?用什么方式告诉对方“我需要什么”和“我现在做到哪一步了”?各家训练出来的智能体能力参差、技术栈不同、运行环境各异,总不能要求所有智能体都用同一套内部实现,于是A2A(Agent-to-Agent)协议就走到了台前。
A2A协议不是某个模型的能力,而是一套标准的“对话规则”。它定义了智能体之间如何互相发现、如何派发任务、如何汇报进度、如何传递最终结果。可以把它想象成智能体界的世界语,大家各自的母语可以不同,但用这套通用语法交流,谁都能听懂谁。对于开发者和企业架构师来说,理解A2A协议的工作原理,是设计一套可扩展、跨团队、跨系统的多智能体架构的前提。这篇文章就用实际场景和代码示例,把A2A协议从发现到执行、从任务状态机到流式事件推送,一整套原理掰开揉碎讲清楚。
1.2 A2A协议要解决的三个核心问题
在深入技术细节之前,先明确A2A协议要解决的三个核心问题,后面理解它的设计思路会顺畅很多。
第一,能力发现。A2A引入了智能体卡片(Agent Card)机制,每个智能体通过一个公开的JSON描述文件对外声明自己的名称、能力、支持的认证方式以及通信端点。调用方拿到卡片就知道“对方能干什么、该怎么喊话”。这有点像两个人初次见面先递名片,名片上写清楚了头衔和联系方式,后面合作就顺畅了。
第二,任务派发。任务在A2A中被建模为一次独立的工作单元,由调用方向被调方发送一个任务请求,被调方执行并返回任务状态。整个过程是异步的:请求发出后,任务可能立即完成,也可能运行几秒甚至几分钟,双方通过状态查询和事件通知保持同步。
第三,结果传递。任务执行完成后,结果以结构化消息和工件(Artifact)的形式返回。工件可以是文本、文件路径、图片、表格等任意类型的文件,被调方通过消息附件把任务产出物完整交给调用方。
可以说,A2A协议就是围绕“发现—任务—消息—工件”这四类核心对象设计出来的。理解这四类对象,基本就抓住了协议的骨架;再往下看传输层和生命周期的时候,就只是往骨架上添血肉了。
2. A2A协议的核心架构与设计理念
2.1 智能体卡片:让能力变得可发现、可路由
A2A协议的第一个关键设计是智能体卡片。它本质上是一个符合标准格式的JSON文档,通常通过一个固定的URL对外暴露,比如https://example.com/agent-card.json。文件内容包括智能体的名字、描述、能力清单、接入URL、安全认证方式等。
看一下一个典型智能体卡片的简化结构:
{ "name": "SupplyChainAgent", "description": "负责供应商比价和采购审批流程的智能体", "url": "https://api.example.com/agents/supply-chain", "version": "1.0.0", "capabilities": { "supplierSearch": { "description": "根据商品关键词搜索可用供应商" }, "priceComparison": { "description": "对多个供应商报价进行汇总对比" } }, "authentication": { "type": "bearer_token" } }这个卡片的作用,一是让其他智能体知道“这里有一个能报价的助手”,二是让路由层可以根据卡片信息做能力分发。比如企业内部的智能体网关收到一个“帮我比价”的请求,扫描所有已注册的Agent Card,发现只有SupplyChainAgent声明了priceComparison能力,就把请求路由过去。卡片信息写得越规范,整个系统的智能体调度就越精准。
值得注意的是,卡片里描述能力用的是自然语言加简单结构,而不是严格的函数签名。这和MCP不同——MCP的工具定义使用完整的JSON Schema描述参数类型、必填项等,精度更高;A2A卡片更倾向于“语义化声明”,因为智能体之间的交互不像函数调用那样非黑即白,很多时候需要双方在对话中逐步澄清需求。所以A2A在能力发现上保持了适度的松散,把精确的信息对齐交给后续的任务消息来完成。
2.2 基于JSON-RPC的消息框架
A2A协议在传输层选用了大家都很熟悉的JSON-RPC架构,而不是重新发明一套通信协议。JSON-RPC是一种轻量级的远程调用协议,请求和响应都以JSON格式编码,调用方式简单直接,现有的HTTP基础设置就可以直接承载。选择JSON-RPC而非REST风格,核心原因在于A2A需要支持双向的事件推送,而JSON-RPC天然支持请求/响应和通知两种模式,和智能体之间的“命令—反馈—事件流”交互模型非常契合。
A2A在JSON-RPC的基础上定义了三种方法类型:
message/send:用于发送消息给目标智能体,并返回对方智能体的响应。这是最基础的方法,承担了大部分“一问一答”类交互。message/stream:在message/send基础上增加流式返回能力,适用于大模型生成、长文档处理这类需要实时反馈的场景。task/*系列方法:包括task/send、task/get、task/cancel、task/stream等,围绕任务生命周期提供创建、查询、取消、订阅等操作。
这些方法都遵循标准JSON-RPC的请求格式,包含jsonrpc、method、params、id字段。响应同样符合JSON-RPC规范,要么返回成功结果,要么返回包含错误码和错误信息的对象。因为底层通信协议是大家熟悉的那一套,开发者在实现A2A客户端或者智能体服务端时,用现有的HTTP客户端库和JSON处理库就可以完成大部分工作,不需要引入重量级的新依赖。
2.3 任务导向的交互模型:让协作异步化
如果说智能体卡片解决了“找得到”,JSON-RPC解决了“说得通”,那么任务(Task)模型解决的则是“干得稳”。一个智能体被喊去执行任务,往往不是一句话能搞定的。比如A智能体请求B智能体“生成一份完整的市场分析报告”,B收到任务后需要调用多个数据源、反复迭代内容,整个过程可能持续几分钟,中途还可能遇到数据缺失需要人工介入。这种长时运行的协作模式,决定了A2A不能采用同步等待的请求-响应模型,而必须引入异步任务机制。
任务模型的核心是一个任务对象,由被调方创建并管理,包含唯一的任务ID、状态、输入参数、中间输出和最终结果。调用方拿到任务ID后,可以反复查询任务状态、订阅状态变更,甚至主动取消正在运行的任务。这种设计把“请求”和“执行”彻底解耦,调用方发出任务请求后,不需要保持长连接,也不需要阻塞等待,客户端掉线重连都不会影响任务继续运行。
从这个角度看,A2A协议更像是一个项目管理协议,而不只是一个通信协议。它在设计之初就假设了智能体之间的交互往往是耗时、复杂、需要状态跟踪的,所以才有了完整的状态机和任务管理方法。这个理念对于企业级多智能体系统的架构设计尤其重要,因为真实业务中的跨部门协作、审批流程、数据加工,几乎没有一个是同步完成的。
3. A2A协议的完整工作流程
3.1 能力发现:双方建立连接的第一步
两个智能体A和B第一次打交道,第一步永远是从发现开始。A需要先找到B的Agent Card,解析卡片内容,确认B是否具备完成自己任务的能力,并找到B的通信端点。这一步做扎实了,后面的任务派发才不会被“404”或“能力不支持”之类的错误卡住。
实际操作中,A2A的能力发现有两种常见方式。一种是直接配置:调用方在自己的配置里写清楚B的Agent Card地址,启动时直接拉取并缓存。这是最小可用路径,适合智能体数量少、拓扑固定的内部系统。另一种是通过目录服务:系统部署一个中心化的Agent Registry,所有智能体启动时自动注册自己的卡片,调用方按能力关键词去目录里搜索。第二种方式适合团队规模大、智能体数目多、能力经常变动的场景。
我自己的实践经验是,即使系统初期只有两三个智能体,也建议在架构上预留目录服务的接口。因为A2A协议的一个核心价值就是“动态发现”,如果每次新增智能体都要改调用方的代码配置,那这个协议的优势就打了对折。做一个简单的Registry,用数据库存卡片,加一个按能力查询的接口,整体工作量并不大,后期扩展却非常值得。
能力发现阶段还有一个容易忽略的安全问题:Agent Card里声明的url字段如果被恶意修改,调用方就可能在不知情的情况下把敏感任务发给伪造的智能体。所以生产环境中至少要做两件事:一是拉取卡片时验证来源(比如检查HTTPS证书和签名);二是在Agent Registry中维护可信智能体清单,不信任的卡片不允许注册。
3.2 任务创建:从请求到具体的任务对象
能力确认无误后,调用方开始创建任务。A2A的task/send方法允许调用方在一条消息中直接携带任务描述和输入数据,被调方收到后生成一个任务对象,并返回任务ID和初始状态。
一个典型的任务创建请求长这样:
{ "jsonrpc": "2.0", "id": "req-001", "method": "task/send", "params": { "taskId": "task-12345", "message": { "messageId": "msg-001", "role": "user", "taskId": "task-12345", "content": { "type": "text", "text": "请帮我对比以下三家供应商的报价,并给出推荐排序" } } } }对应的响应会返回任务已经创建并进入排队状态的信息,例如:
{ "jsonrpc": "2.0", "id": "req-001", "result": { "task": { "taskId": "task-12345", "status": "queued", "statusMessage": "任务已进入执行队列" } } }这里有一个设计细节值得注意:taskId由调用方自己生成,而不是被调方生成。这在分布式系统里是常见做法,保证调用方可以按自己的约定关联上下游任务,也方便调用方追踪日志。如果由被调方生成ID,调用方还需要额外保存一个映射表,增加不必要的复杂度。
3.3 任务状态查询与事件订阅:掌握执行进度
任务创建成功后,调用方必须能及时掌握任务的执行进度。A2A提供了两条路径:
- 主动查询:调用方通过
task/get方法,携带任务ID定期查看状态。适合低频查询场景。 - 被动订阅:调用方通过
task/stream方法建立流式连接,被调方在有状态变更时主动推送事件。适合需要实时反映进度的交互场景,比如前端界面要显示“正在分析中…已完成50%”这样的进度条。
这两条路径可以并存。实际项目中我推荐混合使用:核心关键节点用订阅实时感知,然后定期用task/get做一次全量状态校准,防止因为网络抖动漏掉某个事件。事件推送的结构通常包含任务当前状态、可选的进度信息,以及一条面向调用方的人性化状态描述。
4. 核心机制拆解:任务生命周期与状态管理
4.1 任务状态机:设计严谨的执行骨架
A2A协议最精妙的部分,是它对任务状态的严谨定义。任务在执行过程中会经历几个明确的状态,协议规定的状态机大致是这样的:
submitted:任务已收到,等待调度处理,这是所有任务的初始状态。working:任务正在执行中,智能体已经开始干活。input-required:任务执行到某一步,发现缺少必要信息,需要调用方补充输入。这个状态是多智能体交互特有的——比如采购任务进行到一半,发现某家供应商报价单格式不完整,需要调用方决定是否跳过。completed:任务成功完成,调用方可以获取最终结果。cancelled:任务被调用方或系统主动取消。failed:任务执行失败,错误信息会附在任务对象中。unknown:兜底状态,用于描述无法确认的异常情况。
这个状态机的设计非常务实地考虑了真实协作场景。拿input-required这个状态来说,如果协议只支持working和completed,那么智能体在需要补充信息时,只能用轮询等待的方式干耗着,或者直接把任务失败,这两种体验都难以接受。有了显式的input-required状态,调用方就能感知到“对方卡住了,需要我来帮忙”,进而通过发送新消息补充上下文,让任务继续推进。
4.2 消息结构与内容类型:不只是传文本
A2A协议中,消息是承载交互信息的基本单元。每条消息都有全局唯一的messageId,有明确的消息角色(用户、智能体),并且可以包含多种类型的内容。
文本内容是最基础的,但协议的完整能力远不止于此。消息内容支持text、file、data等类型,也就是说,智能体之间可以直接传递文件路径、表格数据、结构化JSON,甚至图片和音频的引用。比如数据分析智能体在生成图表后,可以通过文件类型的内容把图表文件地址传给汇报智能体,汇报智能体再把它整合进最终的PPT。这种多类型内容的设计,让A2A协议天然适配富媒体场景。
一个特别值得关注的设计是消息与任务的关联方式。每条消息都带有taskId,也就是说,所有消息都归档在具体任务下,形成完整的交互上下文。调试多智能体系统时,按任务ID查消息日志,就能看到一次协作的完整对话脉络,排查问题非常方便。
4.3 流式交互:让结果实时可见
A2A协议对流式交互的支持,很大程度上是为了匹配大模型应用的实时输出需求。模型生成回复不是一瞬间完成的,用户需要看到逐字输出的效果。A2A的流式接口在任务状态从working切换到completed的过程中,会持续推送增量消息片段。
流式交互的底层逻辑是通过task/stream建立一条持久化连接,被调方在任务执行过程中不断向调用方推送事件,事件类型包括文本增量、任务状态变化、阶段进度等信息。调用方可以实时渲染这些增量内容,也可以只关心状态跃迁,把增量内容缓存下来,等任务完成后再输出。
这种设计在真实业务中的价值,我在一个文档翻译场景里深有体会:翻译智能体处理长篇文档时,如果走同步接口,调用方要等全部翻译完成才能看到结果,体验上慢得让人抓狂。换成流式接口后,调用方“看到”翻译智能体一段一段地输出译文,用户感知到的响应速度大幅提升,整体交互体验也更接近真人协作。
5. A2A与MCP的关系:不是二选一,而是互补
5.1 两者的边界在哪里
A2A协议火起来之后,经常有人问:“它和MCP是什么关系?是不是MCP要过时了?”这类问题反映了对两个协议定位的不了解。MCP解决的是智能体与工具、数据源之间的连接问题,A2A解决的是智能体与智能体之间的协作问题,两者解决的根本不是同一层的问题。
MCP是一套“工具调用协议”,它的核心对象是工具、资源和指令。模型通过MCP客户端发现工具,按JSON Schema定义的参数调用工具,工具执行后将结果返回给模型。一个MCP交互的典型场景是:模型需要查天气,通过MCP调用一个天气查询工具,传一个城市名,返回一个温度字符串。整个过程是同步的、一次性的、短时的。
A2A则更接近“人际协作协议”,它的核心对象是智能化主体,而不是工具。智能体之间不只是简单调用一个函数,而是委派一项任务、跟踪执行进度、处理中间环节的异常和补充输入、最终验收产出。这个交互的粒度和复杂性都远高于工具调用。
用生活中的例子类比:MCP像是你去工具间借一把螺丝刀,拿到就用,用完就还;A2A像是你请了一个装修师傅帮你完成整个房间的布线,这中间师傅可能开槽、穿线、遇到问题回来问你,最后告诉你完工了。两者的区别不在于谁更高级,而在于职责范围完全不同。
5.2 两者如何在实际系统中协同
真实的多智能体系统中,A2A和MCP往往是打在同一个架子里的。主智能体作为协调者,通过A2A协议把任务分发给子智能体;而每个子智能体在完成自己的专业任务时,又会通过MCP协议调用各种底层工具。
举个例子,假设自动化营销主智能体要策划一场新品发布活动。它通过A2A协议调用内容创作子智能体写营销文案,内容创作子智能体接到任务后,通过MCP连接搜索工具查资料、连接表格工具拉取竞品数据,最后生成文案返回给主智能体。在这个协作链条中,A2A负责的是智能体与智能体之间的分工和衔接,MCP负责的是智能体与信息源、工具之间的具体干活路径。
所以架构上最简单有效的方式,是在企业内部部署一套统一的智能体网关:网关向北对接主智能体或业务入口,向南分别管理A2A的子智能体注册表和MCP的工具注册表。这样既能享受A2A带来的多智能体协作能力,又能利用MCP丰富的工具生态。在实际实施中,把两条链路理清楚,避免在路由层面把两者混为一谈,系统的可维护性会大幅提升。
6. 实操落地:实现一个最简单的A2A智能体
6.1 准备环境与依赖
理论讲再多,不落地都是空中楼阁。这里用一个最简化的示例,演示如何从零实现一个完整的A2A智能体交互。选用的技术栈非常普通:Python + FastAPI做HTTP服务,官方参考实现只需要两个依赖:a2a-sdk和fastapi。
pip install a2a-sdk fastapi uvicorna2a-sdk提供了Agent Card的生成与管理、JSON-RPC消息编解码、任务状态管理等功能,可以省去大量底层代码。当然,如果不想引入框架,完全可以用纯JSON序列化和HTTP端点手动实现,只不过需要处理更多边界情况,开发效率会低不少。
6.2 编写智能体卡片与核心端点
首先定义一个最小可用的Agent Card,声明这个智能体能做什么:
from a2a.sdk.agent_card import AgentCard, AgentCapability, AgentAuthentication agent_card = AgentCard( name="DemoTranslator", description="将中文技术文档翻译为英文", url="http://localhost:8000/", version="1.0.0", capabilities=[ AgentCapability( id="translate_zh_en", description="翻译中文技术文档为英文" ) ], authentication=AgentAuthentication( schemes=["none"], credentials=None ) )然后按照A2A协议规范,创建Messages和Task结构:
from a2a.types import Message, TextPart, Task, TaskState from a2a.sdk.tasks import TaskManager task_manager = TaskManager() async def handle_translate_task(task: Task) -> Task: # 简单的伪翻译:从消息中取出文本,加上翻译标记返回 parts = [] for message in task.messages: for part in message.parts: if part.type == "text": parts.append(TextPart( type="text", text=f"[EN] {part.text}" )) task.parts = parts task.status = TaskState.COMPLETED return task最后创建FastAPI应用,挂载task/send、task/get等核心路由:
from fastapi import FastAPI from a2a.sdk.server import TaskRequest, TaskResponse app = FastAPI() @app.post("/task/send") async def send_task(request: TaskRequest): task = request.params.task updated_task = await handle_translate_task(task) return TaskResponse(result=updated_task) @app.get("/") async def get_card(): return agent_card到这里,一个能够响应task/send请求、并返回任务执行的A2A智能体就已经跑起来了。启动服务后,用任意HTTP客户端POST一份JSON-RPC格式的任务请求过去,就能看到智能体返回一条“翻译后”的消息作为任务结果。
6.3 端到端测试:验证通信闭环
拿curl做一次端到端验证。先拉取Agent Card确认能力,再发送翻译任务:
curl http://localhost:8000/ curl -X POST http://localhost:8000/task/send \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "test-1", "method": "task/send", "params": { "task": { "taskId": "task-demo-001", "messages": [ { "messageId": "msg-001", "role": "user", "parts": [ {"type": "text", "text": "你好,请翻译这段内容"} ] } ] } } }'响应里能看到任务状态变成了completed,返回的parts里携带了翻译结果文本。虽然这个示例没有真正的翻译能力,但它完整跑通了“发现—任务创建—任务执行—结果返回”的协议链路。生产环境里,只需要在handle_translate_task函数中接入真正的大模型API或内部翻译服务,这个雏形就是一个可用的A2A翻译智能体。
7. 实施中的常见问题与排查经验
7.1 智能体卡片拉取失败或信息不匹配
这是落地A2A时最容易踩的坑。调用方拉取不到Agent Card,或者卡片里的URL、能力信息配错,直接导致后续所有的交互都进行不下去。排查时先检查网络连通性,确认卡片URL能被外部访问,不能是localhost或内网地址;再看卡片内容是不是合法JSON,字段是否符合A2A规范。
更隐蔽的一个问题,是卡片信息的缓存过期。智能体能力升级后卡片更新了,但调用方依然使用旧缓存路由,导致新请求被路由到一个已经不存在的端点。解决方案是给Agent Card加版本号或更新时间戳,调用方定期增量更新缓存,同时配合失败重试机制:请求失败后重新拉取卡片再试一次。
7.2 任务状态迟迟不更新
任务执行过程中,调用方发现task/get返回的状态一直是working,甚至任务实际已经完成,状态却没有跳变。这种情况多半是被调方没有在任务对象变更时主动同步状态,或者调用方的事件订阅连接断开了没有自动重连。
排查时先确认被调方是否实现了状态持久化。很多智能体服务在处理任务时只把任务对象放在内存里,服务重启后任务状态全部丢失。生产环境必须把任务状态写入数据库或缓存(如Redis),才能保证断点续查。
还有一个容易被忽略的点:task/stream订阅连接的超时设置。长连接如果超过一定时间没有心跳,中间网关会断开连接。客户端需要实现自动重连和重订阅机制,在断线后重新订阅,并主动拉取一次task/get做状态校准。
7.3 消息内容格式不兼容
A2A协议规范消息内容为多部分格式,每个部分有明确的type字段,但不同的SDK实现版本之间,对消息结构和字段命名可能有不一致的地方。比如有的实现用parts数组,有的早期版本用content对象,直接混合使用就会导致解析失败。
这种问题没有捷径,只能严格对照协议版本。开发时锁死双方依赖的A2A SDK版本,生产环境尽量用同一套协议版本库。如果涉及跨语言实现,比如Python调用方调用Java智能体,建议在集成测试阶段就进行针对性的格式联调,并准备一份双方认可的样例消息集合作为回归测试基准。
7.4 安全认证策略不一致
A2A协议定义了多种认证方式,包括不认证、Bearer Token、OAuth等。在企业内部落地时,最常见的坑是两个智能体对安全级别要求不一致:调用方要求被调方必须校验Token,但被调方的Agent Card里写的认证方式却是none,导致鉴权失败或者安全审计不过关。
建议在规划阶段就统一认证策略,至少在同一网络域内保持一致。全链路启用mTLS或JWT认证,在Agent Card中如实声明认证方式,并在调用方侧配置对应的凭证管理。这不仅是为了安全,也是为了智能体之间协作的顺畅——认证信息不透明造成的间歇性失败,排查成本远比想象中的高。
8. 演进方向与个人实践体会
A2A协议还处在快速演化阶段,目前版本对很多场景的支持仍在增强中,比如更细粒度的权限控制、跨组织智能体协作的信任模型、以及更丰富的富媒体内容类型。但它的核心设计理念已经被验证是扎实的:用智能体卡片做能力发现,用JSON-RPC做消息传输,用任务对象做生命周期管理,用事件流做实时同步,这套模型在复杂的多智能体协作场景中表现出了很好的通用性和扩展性。
我个人在实际开发中的体会是,A2A协议最大的价值不在于“又多了一个协议”,而在于它迫使开发者用一套标准化的语言去思考智能体之间的协作边界。过去搭建多智能体系统,很容易变成两两之间写定制接口的蜘蛛网,每个接口的处理逻辑都不一样,到后期维护成本极高。引入A2A之后,所有智能体对外都暴露统一的交互范式:一张卡片、几个端点、一套任务状态机。系统内部再怎么复杂,对外交流的“界面”是标准化的,这在大规模团队协作中带来的维护收益是非常显著的。
最后分享一个小技巧:在调试A2A智能体网络时,给所有对外请求带上标准化的追踪ID,并把它透传到每一个任务、消息和事件里。多智能体系统的问题排查最难的就是跨节点追踪,有了一条贯穿全链路的追踪ID,日志检索的效率能提升好几倍。这个做法和具体的协议版本无关,但在一线折腾多智能体系统时,它的价值怎么强调都不过分。