资讯详情

资讯详情

A2A协议实战:多Agent协作的架构设计与落地经验

1. 从一个真实困境说起为什么单Agent再强也扛不住复杂任务去年下半年我接手了一个企业内部的智能助手项目需求听起来很朴素帮运维团队自动处理日常告警。一开始我的思路很直接堆一个能力足够强的Agent把日志查询、指标拉取、变更记录、知识库检索这些工具全挂上去让它自己规划、自己调用。Demo阶段效果确实惊艳单次对话里它能连续调七八个工具把一条告警从发生了什么一路追到根因是什么。但上线两周后问题集中爆发。第一工具数量超过二十个之后模型选择工具的准确率肉眼可见地下滑经常在查指标和查日志之间反复横跳。第二所有逻辑挤在一个Agent里提示词膨胀到四千多token稍微改一处就牵一发而动全身。第三也是最要命的不同团队想接入自己的专业Agent——比如网络组有自己的拓扑分析Agent数据库组有自己的慢查询诊断Agent——但它们的工具集、知识库、甚至底层模型都不一样硬塞进一个Agent里根本没法维护。这个困境其实不是个例。当你把视角从一个Agent能做什么抬升到多个Agent如何协作时就会发现真正缺的不是更强的模型而是一套让Agent之间能互相发现、互相调用、互相理解的标准语言。这就是A2A协议要解决的问题。A2A全称Agent-to-Agent直译就是智能体到智能体它定义的是Agent之间怎么打招呼、怎么描述自己的能力、怎么把任务派出去、怎么把结果收回来。你可以把它理解成Agent世界的HTTP——不是让某个Agent变聪明而是让一群各有所长的Agent能像微服务一样组队干活。这篇文章适合三类人看正在做多Agent系统、被Agent间通信折磨过的开发者想搞清楚A2A和MCP、和传统RPC到底差在哪的技术负责人以及准备把单Agent架构升级成多Agent协作的团队。我会从协议的核心抽象讲起一路讲到能力发现、任务生命周期、消息格式再落到实际踩过的坑和选型建议。全程不堆概念尽量用我实际项目里的场景来对照。2. A2A到底抽象了什么Agent Card、Task与Message三件套要理解A2A先得抛开协议这个词带来的距离感。通信协议本质上就是一套约定我说什么格式你回什么格式中间出了岔子怎么处理。A2A的特别之处在于它的通信双方都是具有自主决策能力的Agent而不是被动响应请求的服务。这个前提决定了它的抽象层次比普通RPC高得多。2.1 Agent CardAgent的名片与能力声明A2A里最基础的概念是Agent Card。每个符合A2A规范的Agent都要对外暴露一张卡片用JSON描述自己是谁、能干什么、怎么调用。这张卡片通常挂在/.well-known/agent.json这个约定路径下任何想跟它协作的Agent第一步就是去拉这张卡片。一张典型的Agent Card长这样{ name: network-topology-agent, description: 负责网络拓扑分析与链路故障定位, url: https://internal.example.com/agents/network, version: 1.2.0, capabilities: { streaming: true, pushNotifications: false }, skills: [ { id: trace-path, name: 链路追踪, description: 给定源和目的IP返回完整转发路径, inputModes: [text], outputModes: [text, data] }, { id: detect-loop, name: 环路检测, description: 检测指定网段是否存在二层环路 } ], defaultInputModes: [text], defaultOutputModes: [text] }这张卡片的价值在于它把这个Agent能做什么从自然语言的模糊描述变成了机器可解析的结构化声明。调用方不需要读文档、不需要猜直接看skills数组就知道有哪些能力可用每个能力的输入输出是什么模态。我在实际项目里踩过一个坑一开始Agent Card里的description写得太随意比如处理网络问题结果上游的编排Agent根本判断不出该不该把任务派给它。后来我们把每个skill的description都改写成动词对象约束的格式比如给定源和目的IP返回完整转发路径仅支持IPv4路由准确率立刻上了一个台阶。Agent Card不是给人看的文档是给其他Agent看的接口契约措辞必须精确到可判定。2.2 Task一次协作的完整生命周期A2A把Agent之间的一次协作抽象成Task。注意这里不是一次请求而是一个任务。区别在哪请求是瞬时的一问一答就结束任务是有状态的可能持续几分钟甚至几小时中间要经历多个状态流转。A2A定义的Task状态大致有这么几种状态含义典型场景submitted任务已提交等待受理刚发出请求working正在处理中Agent正在调工具、推理input-required需要补充输入信息不全要反问completed已完成正常结束failed失败出错终止canceled已取消调用方主动撤销这个状态机是A2A区别于普通RPC的核心。普通RPC你发一个请求要么成功要么失败中间没有我需要你补充信息这种交互。但Agent协作经常需要多轮澄清——比如你让网络Agent追踪一条链路它发现你给的源IP不在它管辖的网段内这时候它不该直接失败而应该进入input-required状态反问这个IP属于哪个区域。我印象很深的一次调试编排Agent把一个诊断任务派给数据库Agent数据库Agent返回input-required要求提供具体的库名。但编排Agent当时没处理这个状态直接把整个任务标记成失败了。排查了半天才发现是编排逻辑里漏了对input-required的分支处理。这就是A2A的Task模型带来的新复杂度——你必须把Agent当成会反问的协作方而不是只会应答的服务。2.3 Message与Part多模态内容的承载方式Task之下是MessageMessage之下是Part。这个层级关系有点像HTTP里的Request-Response-Body但更灵活。一个Message可以包含多个Part每个Part可以是文本、文件、或者结构化数据。{ role: user, parts: [ { type: text, text: 帮我追踪 10.20.1.5 到 10.30.2.8 的路径 }, { type: data, data: { sourceZone: core, targetZone: dmz } } ] }为什么要设计Part这一层因为Agent之间的通信往往不是纯文本。网络Agent可能需要接收拓扑图数据分析Agent可能需要接收CSV视觉Agent需要接收图片。如果协议只支持文本那所有非文本内容都得先编码成字符串再解析既低效又容易出错。Part机制让多模态内容有了原生的承载方式。这里有个实操细节值得说Part的type字段决定了接收方怎么解析内容所以发送方必须确保type和实际内容一致。我们曾经遇到过一个诡异bug某个Agent把结构化数据塞进了text类型的Part里接收方按纯文本处理结果JSON里的引号被转义了解析直接崩。后来统一规范只要是键值对或数组一律用data类型绝不偷懒塞进text。3. 能力发现与任务派发A2A的通信流程拆解理解了三个核心抽象接下来看它们怎么串成一条完整的通信链路。A2A的交互流程可以拆成四个阶段发现、协商、执行、回收。每个阶段都有它的设计意图也都有容易翻车的地方。3.1 发现阶段从我知道你到我确认你能发现阶段的核心动作是拉取Agent Card。调用方Agent在派发任务前先访问目标Agent的well-known路径拿到能力声明然后判断这个任务该不该给它。这里有个设计上的取舍值得聊。A2A没有规定一个中心化的注册中心而是采用去中心化的well-known发现。这意味着每个Agent自己就是能力的权威来源不需要向某个注册中心登记。好处是解耦彻底坏处是调用方得自己维护一份我知道哪些Agent的列表。实际项目里我们做了个折中维护一个轻量的Agent目录服务只存各个Agent的URL不存能力详情。调用方先从目录拿到候选URL列表再逐个拉Agent Card做能力匹配。这样既保留了去中心化的灵活性又避免了完全不知道有哪些Agent存在的尴尬。能力匹配这一步我强烈建议不要只靠语义相似度。我们最初用向量检索匹配任务描述和skill描述准确率大概七成经常把查慢查询匹配到查连接池。后来改成两段式先用关键词做粗筛再用小模型做精排准确率提到九成以上。Agent Card里的skill id是稳定标识比description更适合做精确匹配的锚点。3.2 协商阶段任务该以什么形式派出去发现之后是协商。协商要确定几件事用同步还是流式、要不要推送通知、输入输出用什么模态。A2A支持两种主要的交互模式同步请求/响应调用方发一个message/send目标Agent处理完返回结果。适合快任务。流式调用方发message/stream目标Agent通过SSE持续推送中间状态和结果。适合长任务。选哪种模式取决于任务时长和调用方对中间状态的依赖程度。我们项目里链路追踪这种秒级任务走同步全量日志分析这种分钟级任务走流式。流式模式的一个隐藏价值是它让调用方能实时看到任务进展而不是干等。这在多Agent编排里特别重要因为编排Agent需要根据中间结果决定下一步派什么任务。协商阶段还有个容易忽略的点模态协商。如果调用方发的是文本但目标Agent的skill只接受结构化数据这时候应该在协商阶段就报错而不是等执行到一半才发现。我们在Agent Card里明确声明了每个skill的inputModes调用方在派发前先校验不匹配直接换Agent省得浪费一轮往返。3.3 执行阶段任务状态如何流转执行阶段是A2A最复杂的部分因为Task状态会动态变化。调用方需要根据目标Agent返回的状态决定下一步动作。一个典型的执行链路是这样的调用方发送message/send携带任务描述目标Agent创建Task返回submitted状态和task id。目标Agent开始处理状态转为working。如果信息不足状态转为input-required返回需要补充的内容。调用方补充信息再次发送message携带同一个task id。目标Agent继续处理最终返回completed和结果。这里的关键是task id的贯穿。同一个任务的所有消息都携带同一个task id目标Agent靠这个id关联上下文。如果调用方忘了带task id目标Agent会当成新任务处理之前的上下文全丢。我踩过的最大的坑就在这编排Agent在收到input-required后重新发起了一个全新的message/send没带原来的task id。结果目标Agent开了个新任务之前积累的中间状态全没了任务从头开始跑。记住A2A里的Task是有状态的续接任务必须带task id这是硬性要求。3.4 回收阶段结果怎么拿、异常怎么处理任务完成后结果通过Task的artifacts字段返回。Artifacts和Message的Part类似也是多模态的可以包含文本、文件、结构化数据。{ id: task-abc123, status: { state: completed }, artifacts: [ { name: trace-result, parts: [ { type: data, data: { path: [10.20.1.5, 10.20.1.1, 10.30.2.1, 10.30.2.8], latencyMs: 12 } } ] } ] }异常处理这块A2A定义了标准的错误结构但实际用起来最大的挑战不是协议层面的错误而是语义层面的失败。比如目标Agent返回completed但结果明显不对——它说链路正常可你明明知道那条链路断了。这种协议成功但业务失败的情况协议本身管不了得靠调用方的结果校验逻辑。我们的做法是在编排层加一道结果合理性检查对关键任务用规则或小模型校验返回结果是否符合预期不符合就重试或换Agent。这道检查救过我们好几次有一次数据库Agent因为连不上库返回了一个空的慢查询列表协议层面是completed但业务上完全错误幸好被校验拦下了。4. A2A与MCP、传统RPC的边界别把三件事混为一谈聊到A2A几乎所有人都会问同一个问题它和MCPModel Context Protocol是什么关系和gRPC、REST这些传统RPC又差在哪这个问题不搞清楚架构设计很容易走偏。4.1 A2A与MCP一个管横向一个管纵向先说结论MCP解决的是Agent和工具/数据源之间的连接A2A解决的是Agent和Agent之间的协作。两者是正交的不是替代关系。打个比方。MCP像是给Agent配了一套标准化的手——通过MCPAgent能统一地调用数据库、文件系统、API这些外部资源不用为每个工具写一套适配代码。而A2A像是给Agent配了一套标准化的嘴和耳朵——通过A2AAgent能和其他Agent对话、派活、收结果。在我们的架构里这两者是叠加使用的。每个专业Agent内部用MCP连接自己的工具集对外用A2A暴露自己的能力。编排Agent通过A2A把任务派给专业Agent专业Agent通过MCP调用底层工具完成任务。MCP让单个Agent变强A2A让多个Agent组队。有个常见的误解是有了A2A就不需要MCP了。恰恰相反A2A把任务派给专业Agent后专业Agent还是得靠MCP去调工具。两者解决的是不同层次的问题缺一不可。4.2 A2A与传统RPC状态与自主性的本质差异再说A2A和gRPC、REST的区别。表面上看A2A也是发请求、收响应和RPC很像。但本质差异在两点状态和自主性。传统RPC是无状态的一个请求对应一个响应服务端不会反问你。A2A的Task是有状态的目标Agent可以进入input-required状态反问你可以流式推送中间进展可以被取消。这种状态机模型是为了适配Agent协作的不确定性——Agent不是确定性函数它可能中途发现信息不足可能走弯路需要多轮交互。自主性差异更微妙。传统RPC的服务端是被动的你调什么它做什么。A2A的目标Agent是有自主决策能力的它可能拒绝任务比如这个不在我能力范围内可能建议换个Agent可能在执行中发现更好的方案。协议必须为这种自主性留出表达空间这就是为什么A2A的Task状态里有input-required和canceled而不只是成功/失败。维度传统RPCA2A状态无状态有状态Task交互一问一答多轮可反问服务端角色被动执行自主决策能力描述接口文档Agent Card内容模态通常单一多模态Part长任务需自行实现原生流式支持这张表不是要证明A2A更高级而是说明它们适用的场景不同。如果你的Agent其实是个确定性服务用RPC就够了套A2A反而增加复杂度。只有当协作方真的需要自主决策、多轮交互时A2A的价值才体现出来。4.3 什么时候该上A2A什么时候别凑热闹基于实际经验我给个判断标准适合上A2A的场景协作方是真正独立的Agent有各自的能力边界和知识库任务需要多轮澄清任务时长跨度大需要流式反馈团队边界清晰不同Agent由不同团队维护。不适合上A2A的场景只是想把一个单体拆成几个函数协作逻辑完全确定不需要反问任务都是毫秒级团队就一个人维护所有Agent。我们项目里有个反面教材一开始把格式化输出这种纯确定性操作也包装成了A2A Agent结果每次调用都要走一遍发现、协商、执行的完整流程开销比直接函数调用大十倍。后来把这些确定性操作收回到编排层内部只把真正需要自主决策的能力拓扑分析、慢查询诊断保留为A2A Agent整体延迟降了一半。A2A是给Agent用的不是给函数用的别为了架构好看而过度拆分。5. 落地A2A时的真实坑从鉴权到幂等的六个教训协议规范读起来清爽真落地全是细节。这一节我把项目里踩过的坑按类别整理出来都是文档里不会写、但实际一定会遇到的东西。5.1 鉴权Agent Card暴露了但接口不能裸奔Agent Card挂在well-known路径下意味着任何人都能拉到。这本身没问题能力声明本来就是公开的。但接口调用必须鉴权否则你的Agent就成了公开的免费算力。我们的做法是在A2A的HTTP层加标准鉴权头用内部的服务间令牌。Agent Card里不写鉴权细节只声明securitySchemes具体令牌通过带外渠道分发。这样能力发现是开放的能力调用是受控的。有个细节流式接口的鉴权要在建立连接时就完成不能等SSE流开始推了再校验。我们一开始没注意结果未鉴权的连接也能收到部分中间状态虽然拿不到最终结果但中间状态里可能包含敏感信息。后来改成连接建立阶段就校验令牌不通过直接拒绝。5.2 幂等任务重试不能变成任务重复A2A的Task是有状态的但网络是不可靠的。调用方发了message/send没收到响应它不知道是任务没创建还是响应丢了。这时候如果直接重试可能创建两个重复任务。解决办法是幂等键。调用方在发送任务时带一个唯一的messageId目标Agent收到后先查这个id是否处理过处理过就直接返回之前的Task不重复创建。这个机制在A2A规范里是通过message id实现的但很多实现会忽略它觉得重试概率低无所谓。我们项目里因为网络抖动导致的重复任务出过好几次后来强制所有Agent实现幂等检查才消停。5.3 超时长任务的超时策略要分层A2A支持长任务但HTTP连接本身有超时。如果任务跑十分钟同步接口早就超时了。这时候必须用流式或者用推送通知。我们的策略是分层的短任务30秒走同步超时设30秒中任务30秒到5分钟走流式连接超时设5分钟中间靠心跳保活长任务5分钟走流式加推送连接断了也能通过推送拿到最终结果。流式接口的心跳很关键。有些Agent处理任务时中间状态很少SSE流可能几分钟不推数据中间的网络设备会把空闲连接掐掉。我们在流式实现里加了定时心跳即使没进展也每15秒推一个keep-alive事件保证连接不被误杀。5.4 版本兼容Agent Card的演进不能破坏调用方Agent Card是会变的skill会增删字段会调整。如果调用方硬编码了某个skill id目标Agent一升级就崩。我们的做法是Agent Card版本化同时在skill层面做兼容。新增skill不影响老调用方删除skill要先标记deprecated观察一段时间确认没人用了再删。调用方这边能力匹配逻辑要能容忍未知字段不能因为Agent Card多了个字段就解析失败。这里有个血泪教训有次目标Agent把某个skill的inputModes从[text]改成了[text, data]本意是扩展能力结果调用方的校验逻辑写死了必须完全等于text直接判定不兼容把任务路由到了备用Agent。兼容性检查要用包含而不是相等扩展能力不该被当成破坏性变更。5.5 可观测性Agent之间的调用链怎么追多Agent协作最头疼的是排查问题。一个任务从编排Agent派到A AgentA又派给BB调了三个工具最后失败了。你从哪查起A2A本身没有规定可观测性方案但task id是天然的追踪锚点。我们要求所有Agent在日志里打印task id和message id再用一个统一的trace id把整条链路串起来。编排Agent在派发任务时生成trace id通过A2A的metadata字段透传下游Agent继续透传。这样在日志系统里按trace id一搜整条链路一目了然。metadata字段是A2A留给实现方的扩展点别浪费。我们把trace id、调用方标识、优先级都塞进metadata既不影响协议兼容性又满足了可观测性需求。5.6 降级目标Agent挂了怎么办A2A没有规定目标Agent不可用时的降级策略但这在实际系统里是必须考虑的。我们的做法是三层降级重试目标Agent返回5xx或超时重试一次带同样的message id保证幂等。换Agent重试失败从能力匹配的候选列表里换一个同类Agent。降级执行没有可换的Agent编排Agent自己用简化逻辑兜底或者返回明确的失败信息让上游决策。降级策略要提前设计不能等出事再想。我们有一次线上故障某个专业Agent因为依赖的数据库挂了而不可用编排Agent没有降级逻辑所有相关任务全部卡死。后来补上降级同类故障的影响面缩小了八成。6. 多Agent协作的编排模式从流水线到黑板协议是基础但真正决定系统好不好用的是编排模式。A2A只规定了Agent之间怎么通信没规定谁先谁后、怎么协调。这部分得自己设计。我总结了几种实际用过的模式各有适用场景。6.1 流水线模式任务按固定顺序流转最简单的模式任务像流水线一样从一个Agent流到下一个。比如日志采集Agent → 日志解析Agent → 异常检测Agent → 告警生成Agent。这种模式的优点是逻辑清晰、易于调试。缺点是僵化任何一个环节卡住整条线就停。适合步骤固定、依赖明确的场景。我们用这个模式处理标准化的日志分析流程效果稳定。实现上流水线模式可以用一个编排Agent串起来也可以让Agent之间直接A2A调用。我建议用编排Agent统一调度而不是Agent之间互相调用。因为直接互调会让依赖关系变成网状排查问题极其痛苦。编排Agent作为单一入口所有调用关系都是星型清晰得多。6.2 路由模式按任务类型分派路由模式是有一个调度Agent根据任务类型把任务派给对应的专业Agent。这其实就是我们前面讲的发现加匹配流程。路由模式的关键是匹配准确率。匹配错了任务就派给了不合适的Agent要么失败要么结果不对。我们前面提到的两段式匹配关键词粗筛加小模型精排就是为这个模式服务的。路由模式适合能力边界清晰、任务类型可枚举的场景。如果任务类型模糊、经常需要跨领域协作路由模式就不够用了得上更复杂的模式。6.3 黑板模式多Agent围绕共享状态协作黑板模式是我个人觉得最有意思的一种。它的核心是有一个共享的黑板可以是一个共享存储多个Agent围绕黑板上的状态协作。每个Agent观察黑板发现有自己的活就干干完把结果写回黑板触发其他Agent。这种模式特别适合问题诊断这类场景。比如一条告警进来网络Agent、数据库Agent、应用Agent同时观察各自检查自己领域有没有异常把发现写到黑板上。某个Agent的发现可能触发另一个Agent的进一步检查形成链式反应。黑板模式的优点是灵活Agent之间解耦彻底新增Agent不影响现有逻辑。缺点是难以预测执行路径调试困难而且容易出现惊群——一个状态变化触发所有Agent同时行动。我们用黑板模式做根因分析配合优先级和限流效果不错但前期调试花了不少时间。模式适用场景优点缺点流水线步骤固定清晰、易调试僵化路由任务类型可枚举分派高效依赖匹配准确率黑板复杂诊断灵活、解耦难预测、难调试选哪种模式取决于你的任务特征。别一上来就上黑板模式那是复杂度最高的。大多数场景流水线加路由就够了黑板模式留给真正需要多Agent协同推理的场景。6.4 编排Agent自身的职责边界最后聊聊编排Agent。它是多Agent系统的大脑但大脑不该什么都管。我见过一些实现编排Agent既做路由又做结果校验还做重试降级最后膨胀成一个巨型单体和当初想拆掉的那个单体没什么区别。我的建议是编排Agent只做三件事任务分解、Agent选择、结果聚合。重试、降级、幂等这些横切关注点应该下沉到A2A的通信层用统一的客户端封装。结果校验可以做成独立的校验Agent通过A2A调用。这样编排Agent保持轻量职责清晰。我们重构过一次把编排Agent里的重试逻辑抽到A2A客户端封装里代码量减了四成而且所有Agent调用自动获得重试能力不用每个调用点单独写。横切逻辑下沉是保持编排Agent可维护的关键。7. 我踩过的那些坑与最终沉淀下来的实践写到这里协议、流程、模式都聊完了。最后分享几个零散但实用的经验都是真金白银换来的。关于Agent Card的维护我们后来把Agent Card的生成做成了自动化——每个Agent在启动时根据自己注册的skill动态生成卡片而不是手写JSON。这样skill变了卡片自动更新不会出现代码改了卡片没改的不一致。手写卡片在Agent少的时候还行超过五个必然出岔子。关于流式的调试SSE流调试起来很痛苦因为中间状态一闪而过。我们的做法是在开发环境把所有流式事件同时写一份到日志文件用tail -f实时看。生产环境则把关键事件采样上报用于事后分析。别指望在浏览器Network面板里看SSE事件多了根本看不过来。关于任务取消A2A支持取消任务但取消不是免费的。目标Agent收到取消请求后要清理已分配的资源、回滚中间状态。如果Agent正在调一个不可中断的工具取消可能要等工具返回才能生效。我们的做法是在Agent Card里声明cancellationSupported调用方根据这个决定要不要提供取消按钮。不支持取消的AgentUI上就不给取消选项避免用户点了没反应。关于测试多Agent系统的测试比单Agent难得多因为涉及多个Agent的交互。我们搭了一套模拟Agent可以按脚本返回各种状态包括input-required、超时、错误用来测试编排逻辑的健壮性。这套模拟Agent后来成了我们回归测试的核心资产每次改编排逻辑都跑一遍能拦住大部分回归问题。关于成本多Agent协作的token消耗是单Agent的好几倍因为每次A2A调用都要传上下文。我们的优化是上下文裁剪——只传任务必需的信息不传完整历史。同时给Agent Card加了个contextWindow声明调用方根据这个决定传多少上下文。这个优化让整体成本降了大概三成。关于团队协作A2A最大的价值其实不在技术层面而在组织层面。当每个团队维护自己的Agent、通过A2A对外暴露能力时团队边界和技术边界就对齐了。网络组改自己的Agent不影响数据库组只要Agent Card的契约不变。这种契约驱动的协作比大家挤在一个代码库里改来改去高效得多。我们项目从单体拆成多Agent后跨团队的需求交付周期从两周缩到了一周主要收益就来自这个。如果你正准备上A2A我的建议是从小场景开始。先挑一个边界清晰、协作方明确的任务把发现、协商、执行、回收这条链路跑通把幂等、超时、降级这些基础设施搭好再逐步扩展。别一上来就设计一个覆盖全公司的Agent网络那大概率会烂尾。协议是工具解决实际问题才是目的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →