数据截至 (上游 commit 16ba52690519)
03 · 传输与绑定
本章讲 A2A v1.0 最核心的架构决定:一份 proto 当唯一真相,投影到三种传输,而且必须语义等价。 这是“同一个 agent 能同时用 JSON-RPC、gRPC、REST 而行为一致”的根基。
1. 一个核心思想:proto 是规范,不是 gRPC 的实现细节
A2A v1.0 把 specification/a2a.proto 从“gRPC 专用文件”提升为所有绑定的通用、规范源(docs/whats-new-v1.md 主题 1)。也就是说:
┌───────────────────────────────┐
│ specification/a2a.proto │ ← 唯一真相(数据模型 + 11 个 RPC)
│ (lf.a2a.v1) │
└───────────────┬───────────────┘
│ 投影到三种传输,必须功能等价
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────────┐
│JSON-RPC │ │ gRPC │ │ HTTP+JSON │
│ 2.0/HTTP│ │ /HTTP2 │ │ REST │
└─────────┘ └─────────┘ └──────────────┘
好处是:数据结构只定义一次,JSON 表示交给 ProtoJSON 规则自动推导,三种传输天然一致。坏处是:proto 的字段存在性语义(optional/REQUIRED)会渗透到 JSON 层(见 §3、以及第 01 章的名片签名)。
“功能等价”是硬要求(docs/specification.md:1145-1152)。 一个 agent 若支持多种传输,所有传输必须:提供相同操作集合、对相同请求返回语义等价结果、一致地映射错误、支持相同的鉴权方案。
2. 11 个操作,一张方法映射表
协议的全部能力就是 service A2AService(specification/a2a.proto:19)里的 11 个 RPC。它们在三种传输里的名字/路径如下(docs/specification.md:1160-1174):
| 功能 | JSON-RPC 方法 | gRPC 方法 | REST 端点 |
|---|---|---|---|
| 发消息 | SendMessage | SendMessage | POST /message:send |
| 流式发消息 | SendStreamingMessage | SendStreamingMessage | POST /message:stream |
| 查任务 | GetTask | GetTask | GET /tasks/{id} |
| 列任务 | ListTasks | ListTasks | GET /tasks |
| 取消任务 | CancelTask | CancelTask | POST /tasks/{id}:cancel |
| 订阅任务 | SubscribeToTask | SubscribeToTask | POST /tasks/{id}:subscribe |
| 建 push 配置 | CreateTaskPushNotificationConfig | 同名 | POST /tasks/{id}/pushNotificationConfigs |
| 取 push 配置 | GetTaskPushNotificationConfig | 同名 | GET …/pushNotificationConfigs/{configId} |
| 列 push 配置 | ListTaskPushNotificationConfigs | 同名 | GET …/pushNotificationConfigs |
| 删 push 配置 | DeleteTaskPushNotificationConfig | 同名 | DELETE …/pushNotificationConfigs/{configId} |
| 取扩展名片 | GetExtendedAgentCard | 同名 | GET /extendedAgentCard |
proto 里这些 HTTP 路径其实是用 google.api.http 注解直接标在每个 rpc 上的(例如 SendMessage 的 post: "/message:send",specification/a2a.proto:21-30),REST 绑定即由此而来。注意每条都额外带一个 /{tenant}/… 变体——这是 v1.0 新增的多租户路由(见 §5)。
3. ProtoJSON:JSON 长什么样的规则
JSON 类绑定(JSON-RPC、REST,以及任何自定义 JSON 传输)的序列化必须遵循 ProtoJSON 规范, 这条由 ADR-001 拍板(adrs/adr-001-protojson-serialization.md)。两条最常踩的规则:
字段名:proto 的 snake_case → JSON 的 camelCase(docs/specification.md:1202-1211):
| proto 字段 | JSON 字段 |
|---|---|
protocol_version | protocolVersion |
context_id | contextId |
default_input_modes | defaultInputModes |
push_notification_config | pushNotificationConfig |
枚举值:用 proto 里定义的字符串名,即 SCREAMING_SNAKE_CASE(docs/specification.md:1213-1220):
TASK_STATE_INPUT_REQUIRED→ JSON 值"TASK_STATE_INPUT_REQUIRED"ROLE_USER→ JSON 值"ROLE_USER"
⚠️ 这是 v1.0 的一个 breaking change:v0.3 的枚举是 kebab-case(如
input-required),v1.0 改成 SCREAMING_SNAKE_CASE 以贴合 ProtoJSON(docs/whats-new-v1.md主题 2)。
时间戳:统一 ISO 8601 UTC(docs/specification.md:1228-1255)。 proto 用 google.protobuf.Timestamp,JSON 里必须是 YYYY-MM-DDTHH:mm:ss.sssZ、只用 Z(不许其他时区偏移)、建议毫秒精度。
字段存在性(docs/specification.md:1257-1275)。 标 REQUIRED 的必须出现(required 数组至少一个元素);proto 的 optional 关键字用来区分“显式设过”和“没设”——这对默认值处理和名片签名规范化(第 01 章 §5)至关重要。实现应当忽略不认识的字段以便前向兼容。
4. 错误码映射:一个语义,三种表达
A2A 定义了一套与传输无关的错误类型,再映射到各传输的原生错误(docs/specification.md:1180-1190):
| A2A 错误类型 | JSON-RPC 码 | gRPC 状态 | HTTP 状态 |
|---|---|---|---|
TaskNotFoundError | -32001 | NOT_FOUND | 404 |
TaskNotCancelableError | -32002 | FAILED_PRECONDITION | 400 |
PushNotificationNotSupportedError | -32003 | FAILED_PRECONDITION | 400 |
UnsupportedOperationError | -32004 | FAILED_PRECONDITION | 400 |
ContentTypeNotSupportedError | -32005 | INVALID_ARGUMENT | 400 |
InvalidAgentResponseError | -32006 | INTERNAL | 500 |
ExtendedAgentCardNotConfiguredError | -32007 | FAILED_PRECONDITION | 400 |
ExtensionSupportRequiredError | -32008 | FAILED_PRECONDITION | 400 |
VersionNotSupportedError | -32009 | FAILED_PRECONDITION | 400 |
错误负载(无论哪种绑定)都必须携带:机器可读的错误码、人读的消息、可选的结构化 details(用 google.rpc 错误模型,每项带 @type)(docs/specification.md:538-549)。
除了这 9 个 A2A 专属错误,还有五大通用类别——鉴权 / 授权 / 校验 / 资源 / 系统错误,各有“服务端必须做什么”的规定(docs/specification.md:502-536)。
5. 版本协商:A2A-Version 头
A2A 用 Major.Minor(如 1.0)标识协议版本,patch 号不影响兼容性(docs/specification.md:706-708)。协商靠一个 service parameter:
- 客户端:每个请求必须带
A2A-Version头(也可作为请求参数);空头被解释为0.3(向后兼容老客户端)(docs/specification.md:710-724、:739)。 - 服务端:必须按请求的
Major.Minor语义处理;若该接口不支持此版本,必须返回VersionNotSupportedError(docs/specification.md:735-737)。 - 同一传输可在不同 URL 暴露多个版本的接口(
AgentInterface.protocol_version各自声明)。
多租户(v1.0 新增)。 几乎所有请求消息和 proto 里的每个 HTTP 注解都带一个可选 tenant 字段/路径段(如 SendMessageRequest.tenant,specification/a2a.proto:652)。它是个不透明路由标识——协议不规定其格式语义, 客户端只要把名片里选中接口的 tenant 值原样带上即可(specification/a2a.proto:345-351)。
6. 自定义绑定:把 A2A 跑在 WebSocket / MQTT 上
三种标准绑定之外,A2A 允许自定义协议绑定——换的是传输层本身(如 WebSocket、MQTT),区别于“扩展(Extension)”改的是交互行为(docs/topics/custom-protocol-bindings.md:8-13)。
自定义绑定在名片的 supported_interfaces 里用一个 URI 标识 protocolBinding(官方绑定用 https://a2a-protocol.org/bindings/ 前缀);破坏性变更必须换新 URI(docs/specification.md:1277-1302)。它必须:支持全部核心操作、保持数据模型功能等价(JSON 用 camelCase、时间戳 ISO 8601)、行为一致(docs/topics/custom-protocol-bindings.md:40-54)。
7. 巧妙之处
- 唯一真相 + 自动投影:数据模型只写一遍,JSON 表示由 ProtoJSON 推导,从机制上保证三传输一致,而不是靠三份手写文档对齐。
- 错误“语义优先”:先定义抽象错误类型,再映射到各传输码——自定义绑定也照此给映射表,跨传输的错误处理可移植。
- 空
A2A-Version= 0.3:用一个默认值悄悄保住了对升级前客户端的兼容。
8. 边界与坑
- kebab-case 枚举是 v1.0 之前的老格式:迁移时枚举值要全改大写蛇形,否则反序列化失败。
- camelCase vs snake_case:proto 里读到的是 snake_case,但 JSON 线上是 camelCase,别直接拿 proto 字段名当 JSON key。
- gRPC 用原生 proto,JSON 才转换:camelCase/ISO8601 这些是 JSON 绑定的约定,gRPC 走二进制 proto。
9. 代码地图
| 主题 | 文件 | 符号 / 锚点 |
|---|---|---|
| 11 个 RPC + HTTP 注解 | specification/a2a.proto | service A2AService(:19) |
| 多租户字段 | specification/a2a.proto | SendMessageRequest.tenant(:651)、AgentInterface.tenant(:344) |
| 方法映射表 | docs/specification.md | §5.3(:1160) |
| 错误码映射 | docs/specification.md | §5.4(:1176) |
| camelCase / 枚举约定 | docs/specification.md | §5.5(:1202) |
| 时间戳 / 存在性 | docs/specification.md | §5.6–5.7(:1224) |
| 版本协商 | docs/specification.md | §3.6(:706) |
| ProtoJSON 决策 | adrs/adr-001-protojson-serialization.md | ADR-001 |
| 自定义绑定 | docs/topics/custom-protocol-bindings.md | 全篇 |