A2A(Agent2Agent)协议 — 架构与原理
30 秒导读: A2A 是一套开放协议,让不同公司、用不同框架、跑在不同服务器上的 AI agent 作为平等的“代理”互相通信——而不是把对方当成一个工具来调。它解决的核心问题是:A 公司的 agent 怎么发现 B 公司的 agent、看懂它会什么、把一个可能跑很久的任务安全地托付给它、并拿回结果——全程不需要 B 暴露自己的内部记忆、提示词或工具实现。
1. 这是什么(零基础也能懂)
一句话定义。 A2A 是“agent 之间打电话的通用语言”:一个 agent(客户端)能找到另一个 agent(服务端),读懂它的“名片”,然后给它发消息、让它干活、跟踪进度、取回成果。
它解决谁的什么问题。 设想一个场景:你做了一个“旅行规划 agent”,它自己不会订机票,但市面上有别家做的“订票 agent”。你希望你的 agent 能:
- 找到那个订票 agent 在哪、它支不支持你要的能力;
- 托付一句“帮我订一张去赫尔辛基的机票”,而不必关心 对方内部用的是哪个大模型、调了哪些工具;
- 跟踪这个可能要跑几秒到几小时的任务,中途它若问“从哪个城市出发?”你还能回答;
- 安全地完成这一切——对方不需要把它的私有逻辑给你看,你也用标准的 Web 鉴权(OAuth、API key、mTLS)保护通道。
这正是 A2A 想标准化的东西。在它之前,每两个 agent 想协作都得现谈一套私有接口。
关键直觉:agent 是“黑盒”,不是“工具”。 这是 A2A 最重要的心智模型。被调用的远程 agent 对调用方是 opaque(不透明) 的——你看不到它的内存、提示词、工具。你只能通过协议规定的几个动作和它打交道。这跟“函数调用”式的工具协议(如 MCP)是两种取向:
| MCP(Model Context Protocol) | A2A(Agent2Agent) | |
|---|---|---|
| 把对方当成 | 一个工具 / 资源(函数、API、数据源) | 一个对等的 agent(黑盒) |
| 你需要知道 | 它的输入 schema、输出 schema | 它会哪些“技能”、怎么连它 |
| 交互形态 | 一次调用,拿结构化结果 | 发消息 → 起任务 → 跟踪长跑 → 多轮协商 |
| 类比 | 给机器人装一只手(用某个能力) | 让两个机器人搭伙干活 |
两者互补:一个 A2A 服务端 agent 在内部很可能用 MCP 去调它自己的工具来完成你托付的任务(见 spec Appendix B,docs/specification.md:3598)。
用起来什么样。 最小的一次交互(REST 绑定),客户端 POST 一条消息,服务端直接返回一个已完成的任务,结果放在 artifact 里:
POST /message:send HTTP/1.1
Content-Type: application/a2a+json
{ "message": { "role": "ROLE_USER",
"parts": [{"text": "What is the weather today?"}],
"messageId": "msg-uuid" } }
{ "task": {
"id": "task-uuid", "contextId": "context-uuid",
"status": { "state": "TASK_STATE_COMPLETED" },
"artifacts": [{ "artifactId": "artifact-uuid", "name": "Weather Report",
"parts": [{"text": "Today will be sunny with a high of 75°F"}] }] } }
(两段都来自 spec 的 §6.1 示例,docs/specification.md:1308-1347。)
注意:这个仓库是“协议规范”,不是某个 SDK。 真相之源是一份 Protocol Buffers 定义 specification/a2a.proto,加上一份人读的规范 docs/specification.md。各语言 SDK(Python/Go/JS/Java/.NET/Rust)是另外的仓库(README.md:94-100)。本组文档讲的是协议本身:数据模型、操作语义、传输绑定。
2. 顶层全景(它大概怎么转)
A2A 里只有三个“角色”,围着一份“名片”和一个“任务”运转:
┌──────────┐ 1. 取 Agent Card(名片) ┌─────────────────────┐
│ User │ ────────────────────────────▶ │ A2A Server │
│ (人/服务)│ │ = Remote Agent │
└────┬─────┘ 2. SendMessage(发消息/起任务) │ (黑盒,不暴露内部) │
│ 代为 ────────────────────────────▶ │ │
▼ 行动 │ ┌───────────────┐ │
┌──────────┐ 3. 跟踪 Task: │ │ 内部:LLM/工具 │ │
│ A2A │ · 轮询 GetTask │ │ (可能用 MCP) │ │
│ Client │ ◀── · 流 SendStreamingMessage │ └───────────────┘ │
│(Client │ · push 通知到 webhook └─────────────────────┘
│ Agent) │ 4. 取回 Artifacts(成果)
└──────────┘
怎么读这张图: 左边 Client 代表 User 发起,右边 Server 是被托付的远程 agent;箭头 1→4 是一次协作的时间顺序。Server 内部对 Client 完全黑盒。
几个核心部件,一句话职责:
| 部件 | 干什么 | 在哪定义(proto) |
|---|---|---|
| Agent Card | agent 的“名片 / 自描述清单”:身份、能力、技能、端点、鉴权要求 | AgentCard,specification/a2a.proto:361 |
| Task | 一个有状态、有生命周期的“工作单元”,核心动作单位 | Task,specification/a2a.proto:167 |
| Message | 一来一回的一“轮”通信,带 role(user/agent) | Message,specification/a2a.proto:260 |
| Part | 消息/产物里的内容容器:文本 / 文件 / 结构化 JSON | Part,specification/a2a.proto:224 |
| Artifact | 任务产出的“成果物”(文档、图片、数据) | Artifact,specification/a2a.proto:280 |
| A2AService | 11 个 RPC:发消息、查/列/取消任务、push 配置、扩展名片 | service A2AService,specification/a2a.proto:19 |
主线走一遍(高层):
- 发现。 Client 先拿到 Server 的 Agent Card(常见做法:GET
https://域名/.well-known/agent-card.json)。名片里写明端点 URL、支持哪些技能、要哪种鉴权。 - 发消息。 Client 调
SendMessage,把一条Message(含若干Part)发过去。 - 服务端二选一回应。 要么立刻回一条
Message(简单问答),要么起一个Task并返回它(需要长跑/有状态)。 - 跟踪 + 取回。 若是 Task,Client 用轮询 / SSE 流 / push 通知三种方式之一跟踪它的状态机,直到终态,然后从
Task.artifacts取回成果。
3. 阅读地图(建议顺序)
本组文档按“由浅入深”拆成四章。建议顺序如下:
- 01 · 发现与 Agent Card —— 一切的起点。名片里有什么、客户端怎么找到它(well-known URI / 注册表 / 直配)、以及怎么用 JWS 签名让名片可验真。
- 02 · Task 生命周期 —— 协议的“心脏”。Task 的 8 态状态机、什么时候回 Message 什么时候回 Task、
contextId怎么把多个任务串成一次会话、任务为何不可变。 - 03 · 传输与绑定 —— A2A v1.0 的关键设计:一份 proto 是唯一真相,绑定到 JSON-RPC、gRPC、REST 三种传输,且必须功能等价。讲清楚 ProtoJSON、camelCase 约定、方法/错误码映射表。
- 04 · 流、异步与安全 —— 长跑任务怎么实时推送(SSE)、断线怎么用 push 通知 webhook 兜底、错误分类、以及企业级安全(SSRF 防护、回放攻击、鉴权)。
4. 巧妙之处(本组文档的“精华”预告)
- proto 当唯一真相,而非 gRPC 专用文件。 v1.0 把
a2a.proto从“gRPC 的实现细节”提升为所有绑定的规范源(docs/whats-new-v1.md主题 1)。JSON 序列化遵循 ProtoJSON——这让三种传输天然语义一致。 - “黑盒 agent”的强约束。 安全错误里明确要 求“不得泄露客户端无权访问的资源是否存在”(
docs/specification.md:514、:527),把不透明性写进了协议而非靠实现自觉。 - 任务不可变 + 同 context 多任务。 任务到终态就冻结,任何“改一改”都开新任务挂在同一个
contextId下(docs/topics/life-of-a-task.md:82)——换来干净的输入→输出溯源和并行 follow-up。 - 名片签名要先“规范化”。 签 Agent Card 前必须按 RFC 8785(JCS)规范化,并按 proto 的字段存在性语义决定哪些默认值字段要删——否则重建出来的名片对不上签名(
docs/specification.md:2006-2056)。
5. 代码地图(导航索引)
| 主题 | 文件 | 符号 / 锚点 |
|---|---|---|
| 唯一真相:全部数据模型 + RPC | specification/a2a.proto | service A2AService、message Task、message AgentCard |
| 人读规范(全文) | docs/specification.md | §3 操作语义、§5 绑定等价、§8 名片 |
| 核心概念速查 | docs/topics/key-concepts.md | “Fundamental Communication Elements” 表 |
| 任务生命周期叙事 | docs/topics/life-of-a-task.md | “Task Immutability”、“Parallel Follow-ups” |
| 发现策略 | docs/topics/agent-discovery.md | “Discovery Strategies” |
| 流 / 异步 | docs/topics/streaming-and-async.md | “Streaming with SSE”、“Push Notifications” |
| v1.0 变更全貌 | docs/whats-new-v1.md | “Overview of Major Themes” |
| A2A vs MCP | docs/specification.md | Appendix B(:3598) |