ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

A2A 1.0协议拆解:跨组织Agent协作的边界与实战

A2A 1.0协议拆解:跨组织Agent协作的边界与实战
开头

Agent正在互相调用——这句话放在三年前,大概还只是科幻片里的桥段;放在今天,已经是各大Agent框架的基本操作。LangChain里的AgentExecutor、CrewAI的Crew、AutoGen的GroupChat,哪一个不是让几个Agent在你设定的剧本里你一言我一语地配合完成任务?但如果你把视野从"一个Python进程里跑三个Agent"挪到"两个不同公司的Agent隔着公网协作",你会立刻撞上一堵墙:它们之间既不知道对方能干什么,也不知道怎么把一个活儿交出去,更不知道活儿干到一半出了岔子该找谁。

A2A 1.0(Agent2Agent Protocol 1.0)就是冲着这堵墙来的。这个协议由Google在2025年提出并开源,联合了Salesforce、SAP、Cohere等一批厂商,目标是把Agent之间的协作从"框架内的私有方言"升级成"跨系统、跨组织、跨信任边界的通用协议"。

我看不少团队拿到A2A之后的第一反应是:这不就是让Agent调Agent的API吗?老套。乍一听确实没什么新鲜的,但真正把规范读一遍、再动手搭一个跨Agent协作场景之后,你会发现它真正解决的问题不是"怎么调用",而是"协作边界"——能力怎么声明、任务怎么交接、状态怎么跟踪、安全怎么兜底。这些才是两个Agent在并不互相了解的前提下,敢把活儿交给对方的底气。这篇就聊聊我对A2A 1.0的理解、拆解它的四个核心概念,再附上我实际落地时踩过的坑和排查思路。

1. 从"能互相调用"到"敢互相调用":协作边界是怎么浮出水面的

1.1 框架内的Agent协作,其实早就实现了

Agent互相调用这件事,放在今天真不算新鲜。你用LangGraph或LangChain定义几个Agent节点,让一个Agent的输出作为另一个Agent的输入;用CrewAI把不同角色、不同目标的Agent组织成一支小队,让它们按流程协作;或者在AutoGen里建一个GroupChat,让几个Agent像群聊一样讨论决策。这些都是非常成熟的多Agent协作形态,市面上教程一抓一大把。

但注意,这类协作有一个共同前提:所有Agent都在同一个框架、同一个进程、同一套代码里活着。它们的通信方式说白了就是函数调用加内存共享,它们的记忆和上下文全都挤在同一个上下文窗口里,它们的行为剧本由你一个人写在编排配置里。本质上,这些Agent是一个公司里的同事,共享同一个老板、同一套人事制度。

这种模式解决的是任务编排问题:把一个大任务切成几块,分给几个专职Agent,像生产线一样运转。它很高效,但它是单系统内部的协作,边界问题并不突出。真正麻烦的,是当边界往外扩一层之后发生的事,这也是我在实际项目里反复撞墙的地方。

1.2 跨系统、跨厂商协作时,三个边界问题躲不开

一旦你想让别的系统、别的团队甚至别的公司的Agent参与协作,事情立刻变味。我在项目里遇到的三个最典型的边界问题,这里逐个说清楚。

第一是能力边界。我这边有一个能理解用户意图的Agent,想调用对方系统的能力,可是对方的系统不是一个Agent,而是一堆API。谁能准确告诉我的Agent"你能做什么、输入是什么、返回什么"?如果对方也是一个Agent,它的能力可能还是动态变化的,又该怎么同步?没有能力边界上的明确声明,协作就只能靠人肉写对接文档,这种文档维护成本高,还总是过期。

第二是状态与所有权边界。普通API调用的逻辑是请求发出、响应返回,然后就没了。但Agent之间的协作往往是一个长任务,比如"分析本周销售数据并生成一份PPT",这个任务可能跑几十秒,也可能跑几分钟,中途还可能要回来跟发起方要补充资料。那么问题来了:这个任务现在处于什么状态,由谁负责推进?谁有权取消?算"完成"的标准是什么?这些都需要在一开始就说清楚,否则出了岔子就只能互相扯皮。

第三是安全与信任边界。跨组织的Agent协作,本质上是在公网上交换数据。对方会不会乱动我的数据?对方返回的结果我能不能信?我发给对方的数据万一泄露了谁负责?这些话题一点都不浪漫,但它们是决定"敢不敢让Agent互call"的底层问题。

我们常说的协作边界,其实就是这三件事:能力边界、状态与所有权边界、信任与安全边界。A2A 1.0的价值,恰恰就是把这三个边界在协议层面定义清楚。理解了这三个边界,你再看MCP和A2A的关系也会清晰很多:MCP解决的是Agent怎么去够到外部工具和数据源,A2A解决的是Agent与Agent之间怎么建立外交关系、怎么交办业务,两者是不同层级的事,很多人一上来就把它们搞混了。

2. A2A 1.0拆解:四个核心概念值得吃透

在动手写代码之前,建议先把A2A 1.0的四个核心概念过一遍。理解了它们,你再看协议文档就不会晕:Agent Card、Task、Message、Artifact。

2.1 Agent Card:把"我能做什么"变成机器可读的简历

A2A 1.0里,每个Agent都要提供一个Agent Card,说白了就是这个Agent的公开简历,同时也是机器可读的能力契约。Agent Card通常放在/.well-known/agent-card这个标准路径下,方便任何对端过来"查档案"。

我贴一个精简示例,方便你直观感受:

{ "name": "inventory-agent", "description": "查询商品库存数量与状态的智能体", "url": "https://inventory.example.com/a2a", "version": "1.0.0", "capabilities": { "streaming": true, "pushNotifications": false }, "skills": [ { "id": "check-stock", "name": "check_stock", "description": "输入SKU编号,返回该商品的实时库存数量", "inputModes": ["text"], "outputModes": ["text", "json"] } ], "security": { "authentication": "bearer" } }

这里有几个字段值得展开。name和description是给协作方看的,描述要具体到"输入什么、输出什么",千万别写"能处理各种复杂任务"这种空话,因为卡片不是给人看的海报,主要是给别的Agent的模型看的,描述越具体,对方选型越准确,误调用的概率越低。url是A2A请求的入口地址,类似API网关。skills是核心,它声明了Agent能干什么、输入输出是什么格式。

capabilities声明的是协议层能力,比如支不支持流式输出、支不支持服务端推送,这样客户端可以决定用轮询还是SSE。security则声明了安全策略。你可以把Agent Card理解成Agent界的OpenAPI文档,但它比OpenAPI多了一层策略意味:它不仅告诉你接口长什么样,还告诉你这个Agent在外交场合怎么打交道。这里面的每一项,都是在定义能力边界。

2.2 Task:从"调用函数"升级为"交办任务"

A2A里最核心的抽象是Task。传统API里你调用一个函数,做完了就完了;Agent协作里,你是在把一个任务交出去,然后像盯项目进度一样跟进它。

Task有完整的生命周期状态,规范里定义得很明确:

  • submitted:任务已提交,等待处理
  • working:Agent正在执行中
  • input-required:Agent在执行中发现信息不足,等待发起方补充
  • completed:任务完成,交付Artifact
  • failed:任务执行失败,带具体错误信息
  • canceled:任务被取消

任务的流转路径大致是这样:从submitted进入working,正常情况一路走到completed;中间如果信息不足,会退回input-required挂起等待;发生错误则进入failed;发起方或承接方都可以在某些条件下将任务置为canceled。

这里input-required这个状态特别值得一提。它是Agent协作区别于传统API调用最明显的地方。传统接口打电话过去,话筒里传来的永远是"请一次性说完你的需求";但Agent协作中,执行方干到一半可能会回头说:"你给的参数不够,再补两个维度。"这种双向对话机制,让Agent之间的协作真正具备了边做边沟通的能力,而不是一步错、整盘崩。

2.3 Message与Artifact:过程对话和最终交付要分开

任务执行过程中,双方靠Message交换内容。Message包含role和parts,可以是一段文本,也可以是一个文件。整个交互就像聊天记录一样逐轮累积。

而Artifact是任务执行完之后的交付物,可能是一份报告、一个数据分析结果、一个文件。它和Message的区别在语义上:Message是过程性的交流,Artifact是结果性的交付。把它们分开设计,是为了避免把"聊天记录"和"交付成果"混在一起。客户端收到Message,可以流式展示给用户;收到Artifact,则可以落盘保存或直接喂给下一个环节。

这一点在工程上很实用。比如数据分析Agent跑完任务,中间过程看起来是"我正在加载第2张表",最终交付是一张CSV文件。协议约定就是:过程中的寒暄用Message,成果用Artifact。边界清晰,下游处理就简单。

2.4 传输层为什么选HTTP而不是消息队列

A2A 1.0的传输层采用JSON-RPC 2.0 over HTTP(S),长任务的实时更新用SSE(Server-Sent Events),也可以选Webhook推送。很多人第一反应是:Agent之间这么高频交互,为什么不用Kafka或者MQTT?

答案很实在。HTTP是互联网的基础设施,任何一个能开端口、能上公网的服务都能跑,不需要额外部署一个broker,也不用担心企业防火墙对私有协议的各种拦截。对A2A的目标场景——跨组织、跨技术栈的Agent协作——HTTP是最短路径。

消息队列在高吞吐上确实更强,但它引入了部署和运维的额外负担,还要求所有参与方都兼容同一套消息基础设施。A2A 1.0优先要解决的是"能不能互通",而不是"每秒能扛多少消息"。先把路打通,再谈速度。这个取舍我完全认同。还有一个现实原因:消息队列的场景主要在企业内部或同一生态内,而A2A要面对的恰恰是最分散、最异构的互联网环境,只有HTTP具备这种普适性。

3. 协作边界的本质:信任、状态与安全的博弈

说了这么多概念,回到标题那句话:A2A 1.0真正解决的是协作边界。我得再往深挖一层,讲清楚这个"边界"到底指什么。

3.1 能力边界:Agent Card是一份外包合同

你想让两个Agent协作,第一个要解决的是:它们怎么知道对方能干什么?A2A的方案就是Agent Card。这个卡片本质上是边界两侧的契约。调用方不需要读对方源码,不需要专门集成对方SDK,只需要拿到Agent Card,就知道对方的能力清单和入口,然后按协议发起请求。

这就像你请外包团队干活,合作之前一定先看他们的能力和报价单,而不是先让他们把公司内部代码开源给你。Agent Card就是这个报价单和能力说明的组合体。边界清晰了,协作才有展开的基础。如果能力声明含糊,对方Agent就会像面试者拿到一份含糊的JD,要么不敢投,要么投错了岗位,最后一定是低效的协作。

3.2 状态边界:Task的所有权决定责任的归属

Task状态机的深层含义,是定义了状态的所有权边界。A2A的模型里,发起方和承接方各自维护对任务的视图,协议约定了谁负责推进状态、谁负责处理异常。

默认情况下,承接方(执行Agent)负责更新Task进度,发起方通过轮询、SSE或Webhook去订阅。一旦任务进入input-required,承接方就明确地把一部分控制权交还给发起方:我需要更多信息,你决定给不给、给什么。这种你来我往的状态流转,就是在定义责任的边界:哪个环节归谁管、卡住了找谁、交付以什么为准。

这个设计在实际系统中非常有用。以前我们做跨系统流程,最怕的就是"一个请求发出去了,后面到底跑没跑、跑到哪一步了、为什么停了"全靠猜。有了Task状态机,每一步都有明确的状态可查,出了问题也能定位到具体环节。这就是状态边界带来的可观测性。

3.3 安全边界:互信不是协议给的,但协议得给互信留位置

A2A 1.0规范里,认证和授权并没有强制绑定某种具体实现,而是要求Agent在Card里通过security字段自报安全策略。这种设计很务实:协议永远解决不了组织间的信任问题,信任要依靠业务流程和合同的兜底;但协议必须为信任的建立留出声明与协商的空间。

我在落地时的标准配置是:Card里声明Bearer Token认证,入口网关统一校验,传输链路必须走TLS,敏感数据尽量用引用的方式传递而不是把整个上下文复制给对方。这些安全边界的处理,才是两个Agent敢在公网上互相喊话的大前提。你甚至可以要求对端Agent在每次请求里携带一个可吊销的短期令牌,而不是一把万能钥匙式的长期密钥。安全边界画得越清楚,后续出事的半径就越小。

3.4 记忆边界:上下文该不该跨过边界

还有一个很多人会忽略的边界,是记忆边界。一个Agent在运行过程中积累了大量上下文、用户意图、历史对话,这些是不是都要原封不动地传给协作方?

我的经验是:不要。记忆是Agent最私密也最有价值的资产,跨边界传递记忆首先要考虑隐私和合规风险;其次,对方Agent并不需要你的完整记忆,它只需要与当前任务直接相关的输入。正确的做法是按需抽取:把任务描述、必要参数、约束条件打包成Message传过去,至于这家伙以前跟用户聊过什么,跟这个任务没有关系,就不要带过去。这不仅是安全考量,也是性能和上下文窗口的管理考量。A2A协议本身没有强制约束这一点,但协作边界的设计者必须在业务层面把它定死。

4. 实战落地:搭一个跨Agent协作的最小示例

概念说完了,直接上手。我以一个最简单的客服场景举例,完整跑一遍A2A协作的流程。

4.1 场景设计:一个客服Agent和一个库存Agent

假设我们有两个Agent:

  • 客服Agent:负责跟用户对话,理解用户意图,但不具备库存查询功能
  • 库存Agent:专门负责查询商品库存,对外暴露A2A接口

用户问客服Agent:"A1001的库存还有多少?"客服Agent发现自己不会查询库存,就根据库存Agent的Agent Card,把查询任务通过A2A协议委托给它。库存Agent执行完,把库存数量作为Artifact返回。客服Agent拿到结果,再组织语言回复用户。

这个例子简单,但完整覆盖了协作边界的全部要素:客服Agent不关心库存Agent的内部实现,库存Agent不关心客服Agent的用户上下文。两边只通过协议边界交互。现实中你把这个模式放大,就是人力资源系统和财务系统、销售系统和服务系统之间的Agent协作,本质上都是同一套逻辑。

4.2 服务端实现:Agent Card与A2A入口

库存Agent的入口,我用Python写一个最小实现,基于JSON-RPC 2.0处理请求。首先暴露Agent Card,然后处理tasks/send和tasks/get两个核心方法:

from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/.well-known/agent-card") def agent_card(): card = { "name": "inventory-agent", "description": "查询商品库存数量与状态的智能体", "url": "https://inventory.example.com/a2a", "version": "1.0.0", "skills": [ { "id": "check-stock", "name": "check_stock", "description": "输入SKU编号,返回商品库存数量和状态", "inputModes": ["text"], "outputModes": ["json"] } ], "security": {"authentication": "bearer"} } return jsonify(card) @app.route("/a2a", methods=["POST"]) def a2a_endpoint(): payload = request.get_json() method = payload.get("method") if method == "tasks/send": return handle_tasks_send(payload) if method == "tasks/get": return handle_tasks_get(payload) return jsonify({ "jsonrpc": "2.0", "id": payload.get("id"), "error": {"code": -32601, "message": "Method not found"} }) if __name__ == "__main__": app.run(host="0.0.0.0", port=80)

这里我把Agent Card固定在标准路径,客户端框架拿到根地址后就能直接发现。实际部署时,前面通常会加一层网关做认证校验和限流,而不是让Flask直接暴露公网。

4.3 客户端实现:提交任务与轮询状态

客服Agent这边,核心逻辑是:先拿到库存Agent的Card,确认有check-stock这个技能,然后构造任务请求并提交,最后轮询任务状态直到终态。代码大概长这样:

import requests import time AGENT_CARD_URL = "https://inventory.example.com/.well-known/agent-card" def delegate_stock_query(sku: str, user_text: str): # 第一步:获取并解析Agent Card card = requests.get(AGENT_CARD_URL).json() if not any(s["name"] == "check-stock" for s in card["skills"]): raise RuntimeError("inventory-agent 不具备 check-stock 技能") # 第二步:构造并提交任务 payload = { "jsonrpc": "2.0", "id": "req-cs-001", "method": "tasks/send", "params": { "id": f"task-{int(time.time())}", "message": { "role": "user", "parts": [{"text": f"请查询SKU {sku} 的库存"}] } } } resp = requests.post(card["url"], json=payload).json() task = resp.get("result", {}) # 第三步:轮询状态,直到到达终态 while task["status"] in ("submitted", "working", "input-required"): time.sleep(1) get_payload = { "jsonrpc": "2.0", "id": "req-cs-002", "method": "tasks/get", "params": {"id": task["id"]} } task = requests.post(card["url"], json=get_payload).json()["result"] if task["status"] == "completed": parts = task["artifacts"][0]["parts"] return "".join(p.get("text", "") for p in parts) raise RuntimeError(f"任务失败: {task.get('error')}")

这段代码里有几个细节值得注意。第一,提交任务前先校验技能,这一步看起来多余,但正好体现了Agent Card的价值:能力发现发生在调用之前,而不是调用失败之后。第二,任务ID我用时间戳生成,这在生产环境不够严谨,建议用UUID或者带业务前缀的幂等ID。

4.4 把input-required纳入流程的实际效果

如果库存Agent发现传进来的SKU格式不对,它可以不回failed,而是进入input-required,向客服Agent追问:"请提供8位标准SKU编码。"客服Agent可以就此回复用户,拿到正确的SKU后,再通过tasks/send把补充信息作为新Message推进任务。

这个过程在传统API里几乎不可想象,但在真实业务里价值巨大。因为用户最初的请求经常是模糊的,"我要查库存"和"请查A1001的库存"之间隔着一次澄清。A2A把这种澄清显式地纳入了任务生命周期,双方不用为了一个不完整请求做得罪用户的失败返回,也不会因为一次请求就放弃整个任务。这也是我认为A2A最具"与人协作"神韵的地方。

5. 常见问题与排查清单

我把这段时间实际踩过的坑整理成了一张速查表,希望帮你少走弯路。

5.1 典型故障速查

症状可能原因排查建议
对方Agent长时间没有响应任务卡在working,或服务端崩溃先用tasks/get查看状态;再查服务端日志;确认有没有配置超时机制
结果一直轮询不到SSE连接被网关切断,或Webhook没注册成功检查网络链路;把SSE降级成轮询;检查Webhook回调URL是否可达
任务状态在completed与failed之间反复跳幂等处理缺失,同一任务被重放检查Task ID是否稳定;加去重表;在日志里按task_id做全链路追踪
Agent Card获取失败路径不标准,或卡片字段不全确认是否按规范暴露.well-known路径;用curl直接验证返回内容
上下文越来越大,最终超限Message没做裁剪或摘要设计上下文压缩策略:长对话先摘要再作为下一轮输入;只保留与任务相关的消息

5.2 我踩过的三个坑

第一个坑是上下文无意识膨胀。最开始我把多轮Message原封不动地攒着,等到第20轮交互时,上下文窗口直接撑爆。后来改成"摘要加最近两轮原文"的混合策略,问题才缓解。这不是A2A本身的问题,但只要做Agent协作,就一定会遇到,越早设计越省心。

第二个坑是Agent Card版本不兼容。我更新了库存Agent的能力描述,把check-stock的入参从单个SKU改成支持批量SKU,却忘了同步更新Card的最低兼容版本。结果下游Agent拿着旧Card发来单个SKU参数,我在服务端又要兼容两套逻辑,维护成本直线上升。现在我会在Card里加一个compatibility字段,并确保任何能力变更必走版本评审。

第三个坑是重试风暴。某个下游服务延迟一高,上游Agent的重试机制就疯狂触发,两边的日志一会就被刷爆。后来我加了指数退避和最大重试次数,并且在全链路日志里贯穿同一个任务ID,排查问题一下子轻松很多。这个问题的本质还是边界不清:每个Agent都从自己的视角去"救火",但没人定义重试的边界和上限。

6. 冷静一点:A2A 1.0没解决什么

最后聊点泼冷水的内容。A2A 1.0确实是个好东西,但工程上最忌讳的就是拿一把好锤子到处敲钉子。搞清楚它不做什么,比搞清楚它做什么更重要。

6.1 它不负责编排,也不负责让Agent变聪明

A2A不告诉你任务该怎么拆、该选哪个Agent、流程怎么编排、冲突怎么仲裁。这些属于LangGraph、CrewAI这类编排框架的范畴。A2A也不负责提升单个Agent的推理能力或生成质量,那是模型层面的事情。换句话说,它只定义了"两个Agent之间对话的标准方言",至于这场对话是由谁发起、怎么组织的,协议不关心。

它甚至不保证对端的Agent真的靠谱。就像签了合同也不代表对方真的能按时交付,A2A只是让"委托-交付-追责"这件事有据可依,不代表交付质量自动过关。质量评估、可信度判断、结果验证,这些都还要靠业务侧自己去设计。如果你的Agent本身逻辑一团糟,A2A不会救你,它只会让你的混乱变得更容易传播。

6.2 什么时候值得上A2A,什么时候别用

根据我这段时间的实践,大致可以这么判断。

值得上A2A的场景,首先是跨组织或跨厂商协作,双方各自维护自己的Agent和系统边界;其次是异构技术栈之间互通,一方用Python框架,另一方用Java框架,对双方来说统一协议比统一框架更现实;最后是业务链条上存在明确的"上游委托、下游执行、回传结果"模式,需要把接口标准化。只要命中其中两条,A2A就能体现价值。

不推荐用A2A的场景也不少。单系统内部的多Agent协同,你自己用LangGraph或CrewAI编排,性能和可控性都更好,没必要再套一层协议;对延迟极其敏感的场景,A2A基于HTTP和JSON-RPC,设计目标是通用性而非低延迟,真到毫秒级还是要走专用链路;还有就是连能力边界和信任边界都没想清楚的系统,协议只是把边界明确化,不能凭空生成边界,你内部一团乱麻,指望A2A救你,大概率救不了。

我个人这段实践下来最大的体会是:A2A 1.0并没有让"Agent互相调用"这件事从0变成1,框架们早就做到了。它做的是把"随便怎么调"变成了"按规矩调"。Agent Card让能力有据可查,Task状态机让责任有主可依,Message和Artifact让交流与交付泾渭分明,这些朴素的约定合在一起,就是一套可以被机器理解和执行的边界契约。写一张Agent Card的过程,其实就是逼自己想清楚三个问题:我的Agent到底能做什么、需要对方提供什么、出了问题谁来兜底。这三个问题一旦想明白,你用不用A2A,都已经赢了大半。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进