从官方示例理解A2A任务交互
作者运行A2A官方Hello World示例,通过Agent Card和实际任务输出解释智能体间的通信流程。文章同时区分MCP与A2A:前者面向结构化工具调用,后者面向需要推理、状态和往返交互的智能体。
AI 深度解读
A2A 要解决的,是你的 Agent 如何把工作交给另一个 Agent,并跟踪它完成,而不只是调用一个接口。作者跑通官方 Hello World 示例后的结论很具体:先读 Agent Card,再围绕 Task 的状态、结果和后续消息组织客户端逻辑。
这篇最有用的地方,是把协议落到了真实请求上:结果去哪里取,任务为什么会看起来卡住,已经完成的任务为什么不能继续追加。对准备接入外部 Agent 的程序员,这些比记住「A2A 连接 Agent,MCP 连接工具」更有用。
什么时候需要 A2A,而不是 MCP
A2A,即 Agent2Agent Protocol,是不同框架、不同厂商的 AI Agent 之间通信的开放标准。它最初由 Google 开发,后来捐赠给 Linux Foundation,由技术指导委员会维护。原文介绍的规范发布版本是 1.0.0;作者实测使用 a2a-sdk Python 包 1.3.0,对接协议版本 1.0。
它不负责教你怎么构建 Agent,也不替代 LangGraph、CrewAI、ADK,更不是 Agent 调用内部工具或子 Agent 的统一方案。
选择协议,先看对面承担什么责任。
查询天气、执行数据库查询,通常是结构明确的一次调用,适合 MCP。寻找供应商并协商交付,则可能需要多轮沟通、保存上下文,甚至中途反问你,属于 A2A 要处理的问题。
作者给出的判断方法有四个维度:是不是一次调用,是否保留状态,是否需要补充信息,以及你是否控制对方内部实现。A2A 面向的往往是一个不透明的服务:你知道它承诺提供什么,却看不到它的提示词、工具和记忆。
官方文档里的修车案例很好理解:维修 Agent 通过 MCP 调用诊断扫描仪,再通过 A2A 向配件供应商 Agent 询价找货。两者可以出现在同一条业务流程里。
不过,这四个问题是作者的经验判断,不是协议的硬性分类。原文也明确提到,A2A Agent 可以把定义清晰、无状态的技能暴露成 MCP 风格的工具。不能只凭接口背后用了模型,就决定接 A2A。
先读 Agent Card,再决定怎么调用
Agent Card 是 Agent 对外提供的 JSON 说明书。公共 Agent 推荐放在 https://{agent-domain}/.well-known/agent-card.json,客户端从这里了解接口、能力和认证要求。
作者本地示例叫 Hello World Agent,自身版本是 0.0.1,服务地址为 http://127.0.0.1:9999。读卡片时,重点看四组字段。
supportedInterfaces 描述请求地址和通信方式,而且有顺序,第一项是首选接口。示例使用 HTTP 上的 JSONRPC,协议还定义了 gRPC 和 REST 绑定。
capabilities 表示启用的可选能力。示例中 streaming 和 extendedAgentCard 都是 true,后者表示支持向已认证客户端提供扩展卡片。
skills 描述能做什么。示例的技能 ID 是 echo_bot,名称为 Echo Bot,输入输出都支持 text/plain。但规范把技能定位为描述性概念,列出来不代表执行质量获得保证。
securitySchemes 则说明认证方式。示例公共卡片没有这个字段,因此没有声明认证要求,适用于本地演示。凭证需要通过其他渠道获取,卡片本身不负责发放凭证。
示例还定义了公共卡片看不到的 Echo Bot (Super Mode)。规范要求扩展卡片必须经过认证才能访问,但原文没有展示这部分认证请求的实测过程。
Agent Card 能帮助客户端发现和理解服务,不能替代对服务的信任判断。
一次请求之后,要跟踪的是任务生命周期
作者用下面的请求向本地 Agent 发送 hi,其中保留了协议版本请求头:
curl -s -X POST http://127.0.0.1:9999/ \
-H 'Content-Type: application/json' \
-H 'A2A-Version: 1.0' \
-d '{"jsonrpc":"2.0","id":"req-1","method":"SendMessage",
"params":{"message":{"role":"ROLE_USER","messageId":"msg-1",
"parts":[{"text":"hi"}]}}}'
要看清 A2A-Version: 1.0 和 method: SendMessage:这里调用的是协议方法,消息内容放在 parts 中。
返回结果没有只给一段问候,而是创建了一个已经完成的 Task。下面是作者保留的响应片段,省略了历史记录和时间戳:
{
"result": {
"task": {
"id": "0c0faba9-…",
"contextId": "91d6e4d5-…",
"status": {
"state": "TASK_STATE_COMPLETED",
"message": { "role": "ROLE_AGENT", "parts": [{ "text": "Request is completed!" }] }
},
"artifacts": [
{
"artifactId": "5b355243-…",
"parts": [{ "text": "Hello, World! I have received your request (hi)", "mediaType": "text/plain" }]
}
]
}
}
}
关键看 artifacts:真正的业务结果在这里,status.message 只是状态说明。
Task 是有 ID 和状态的工作单元;Artifact 是产出;contextId 把相关任务和消息归到同一段会话。Agent 也可以直接返回 Message,用于无需跟踪的快速交流,只是这个示例始终创建 Task。
客户端随后要处理完整生命周期。TASK_STATE_SUBMITTED 和 TASK_STATE_WORKING 表示处理中;TASK_STATE_INPUT_REQUIRED 表示需要补充输入;TASK_STATE_AUTH_REQUIRED 表示需要授权。完成、失败、取消和拒绝,分别对应 TASK_STATE_COMPLETED、TASK_STATE_FAILED、TASK_STATE_CANCELED、TASK_STATE_REJECTED,都属于终态。
如果页面只等待成功或失败,中途需要输入和授权的任务就会一直显示加载中。客户端应展示状态消息,并让用户或上游 Agent 提供所需响应。进入 AUTH_REQUIRED 本身,也不能被服务端当成已经取得许可。
作者还实测了一个容易踩坑的操作:向已完成任务的 taskId 追加消息,收到错误码 -32004,提示任务处于终态。再次用旧 ID 调用 GetTask,状态仍然是完成;调用 CancelTask,则返回 Task cannot be canceled。
正确的后续请求应沿用 contextId,通过 referenceTaskIds 引用旧任务,并创建新任务。作者这样重试后,拿到了新的任务 ID 和相同的会话 ID。
会话可以继续,已经结束的任务不能重新启动。
协议还提供 SendStreamingMessage、ListTasks、SubscribeToTask,以及推送通知和扩展卡片相关方法。原文没有逐一展示这些能力的运行结果,不能把它们都当作本次验证过的路径。
我的判断
这篇最扎实的是任务模型和客户端边界。作者实际验证了发现卡片、发送消息、终态追加失败、新建后续任务,以及查询和取消旧任务。它能帮助开发者避免把 A2A 简化成一个聊天接口。
但 Hello World 仍然只是本地协议演示。它没有验证跨团队协作的成功率、长任务恢复能力、吞吐量,也没有证明 A2A 比普通 HTTP API 更高效。示例声明支持流式传输,不等于文章展示了流式链路的验证;两个暂停状态的处理建议,也主要来自规范。
我的取舍是:如果服务只有一次结构化输入输出,先考虑普通 API 或 MCP;当对方独立管理执行过程、会追问、会暂停,并需要被持续跟踪时,A2A 的任务生命周期才更有价值。协议统一的是通信约定,不会自动解决模型可靠性和业务验收。
生产安全也不能从这个示例直接照搬。规范要求生产环境使用加密传输;服务端必须逐请求检查权限,并让 ListTasks、GetTask 只返回调用者有权访问的数据。Webhook 通知可能重复,处理逻辑需要幂等,令牌应唯一且只用于单一用途。这些是接入要求,原文没有提供生产部署的验证证据。
能拿来做什么
- 准备接外部 Agent 前,先按多轮沟通、状态保存、补充输入和内部控制权四个维度评估,确认是否真的需要 A2A 的任务模型。
- 编写前端任务页面时,把处理中、等待输入、等待授权和终态分别展示;状态说明读取
status.message,业务结果读取artifacts。 - 编写 Node 客户端时,分别保存任务 ID 和
contextId。用户继续追问已完成的工作,应在原会话中新建任务,并通过referenceTaskIds引用前次任务。 - 想复现文章,可以找到
a2aproject/a2a-samples下的samples/python/agents/helloworld,安装a2a-sdk[http-server]和uvicorn,运行端口为 9999 的示例,再获取卡片并执行上面的请求。