跳到主要内容

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 端点
发消息SendMessageSendMessagePOST /message:send
流式发消息SendStreamingMessageSendStreamingMessagePOST /message:stream
查任务GetTaskGetTaskGET /tasks/{id}
列任务ListTasksListTasksGET /tasks
取消任务CancelTaskCancelTaskPOST /tasks/{id}:cancel
订阅任务SubscribeToTaskSubscribeToTaskPOST /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 上的(例如 SendMessagepost: "/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_versionprotocolVersion
context_idcontextId
default_input_modesdefaultInputModes
push_notification_configpushNotificationConfig

枚举值:用 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-32001NOT_FOUND404
TaskNotCancelableError-32002FAILED_PRECONDITION400
PushNotificationNotSupportedError-32003FAILED_PRECONDITION400
UnsupportedOperationError-32004FAILED_PRECONDITION400
ContentTypeNotSupportedError-32005INVALID_ARGUMENT400
InvalidAgentResponseError-32006INTERNAL500
ExtendedAgentCardNotConfiguredError-32007FAILED_PRECONDITION400
ExtensionSupportRequiredError-32008FAILED_PRECONDITION400
VersionNotSupportedError-32009FAILED_PRECONDITION400

错误负载(无论哪种绑定)都必须携带:机器可读的错误码、人读的消息、可选的结构化 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:651)。它是个不透明路由标识——协议不规定其格式语义,客户端只要把名片里选中接口的 tenant 值原样带上即可(specification/a2a.proto:344-350)。

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.protoservice A2AService(:19)
多租户字段specification/a2a.protoSendMessageRequest.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.mdADR-001
自定义绑定docs/topics/custom-protocol-bindings.md全篇