A2A 互操作速查:协议绑定、版本与线上格式差异
两个 A2A 实现要能互通,需要在四件事上达成一致:调用哪个接口、使用哪个协议版本、每个操作在所选绑定中长什么样、错误如何传回。这一页就是这四件事的速查。它为"连不上"的时刻而写,也为必须同时支持 v0.3 和 v1.0 的场景而写。
所有内容都对照 A2A v1.0.1(该标签下的 a2a.proto 和规范)核对过,并与 v0.3.0(specification/grpc/a2a.proto、specification/json/a2a.json,以及该标签下的规范)做了比较。v1.0.1 的服务定义与提交 98279d6 处的 main 完全一致。规范与 proto 不一致时,以 proto 为准(第 1.4 节);具体情形见第 2 节
。
消息内部的对象在别处讲:状态与事件见 Task 运行时指南 ,卡片字段见 Agent Card Proto 字段编号 ,九种错误见 A2A 错误码 。
1. 两端能连上吗?
按这个顺序检查。第一个失败的步骤就是原因。
| 步骤 | 检查 | 规则 | 失败时 |
|---|---|---|---|
| 1. 选择接口 | 按顺序读取 supportedInterfaces,取第一个您支持其 protocolBinding、并且会说其 protocolVersion 的条目。调用该条目的 url | 第 8.3.2、3.6.2 和 3.6.3 节 | 没有可用条目:就此停下。规范没有为此定义错误;第 5.2 节只要求客户端实现向其他协议的回退 |
| 2. 发送版本 | 在每个请求上发送 A2A-Version,取 Major.Minor 形式、来自该条目 protocolVersion 的值,例如 1.0 | 第 3.6.1 和 14.2.1 节 | VersionNotSupportedError。空值按 0.3 处理(第 3.6.2 节) |
| 3. 携带租户 | 把条目的 tenant 复制到每个请求消息的 tenant 字段;条目没有时省略该字段 | 第 8.3.2 节 | 规范没有为错误的值定义错误;由服务器决定。见多租户 |
| 4. 检查能力 | 在流式传输、使用推送通知或获取扩展卡片之前,先读卡片上的 capabilities | 第 3.3.4 节 | UnsupportedOperationError、PushNotificationNotSupportedError 或 ExtendedAgentCardNotConfiguredError |
| 5. 声明扩展 | 在 A2A-Extensions 中发送您支持的扩展 URI,用逗号分隔 | 第 3.2.6 和 3.3.4 节 | 返回 ExtensionSupportRequiredError:卡片把某个扩展标为 required: true,而您没有声明 |
| 6. 按绑定读取错误 | 依据 A2A 错误判断,不要只看 HTTP 或 gRPC 状态 | 第 5.4 和 11.6 节 | 见第 3 节 |
服务端如何解读 A2A-Version
| 发送的请求头 | 接口暴露的版本 | 结果 | 规则 |
|---|---|---|---|
| 缺失或为空 | 仅 1.0 | VersionNotSupportedError,因为空值按 0.3 处理 | 第 3.6.2 节 |
| 缺失或为空 | 0.3 | 按 0.3 语义处理 | 第 3.6.2 节 |
1.0 | 1.0 | 按 1.0 语义处理 | 第 3.6.2 节 |
0.3 | 仅 1.0 | VersionNotSupportedError | 第 3.6.2 节 |
0.3 | 一个 0.3 接口和一个 1.0 接口,在同一 URL 或不同 URL 上 | 每个接口应用各自的版本;调用声明 0.3 的那个 | 第 3.6.2 节 |
2.0,或任何没有接口声明的值 | 任意 | VersionNotSupportedError | 第 3.6.2 节 |
1.0.1 | 1.0 | 规范未定义。补丁号不得参与协商(第 3.6 节),但注册的请求头值是 Major.Minor(第 14.2.1 节)。请发送 1.0 | 第 3.6 和 14.2.1 节 |
2. 三种绑定下的操作
JSON-RPC 与 gRPC 使用同一个名字。HTTP+JSON 的路由和返回类型取自 v1.0.1 a2a.proto 中 A2AService 的 HTTP 注解与签名。
| 操作 | JSON-RPC 方法与 gRPC RPC | HTTP+JSON 路由 | 返回 |
|---|---|---|---|
| 发送消息 | SendMessage | POST /message:send | SendMessageResponse:task 或 message |
| 发送并流式返回 | SendStreamingMessage | POST /message:stream | StreamResponse 流 |
| 获取任务 | GetTask | GET /tasks/{id} | Task |
| 列出任务 | ListTasks | GET /tasks | ListTasksResponse |
| 取消任务 | CancelTask | POST /tasks/{id}:cancel | Task |
| 订阅任务 | SubscribeToTask | GET /tasks/{id}:subscribe | StreamResponse 流 |
| 创建推送配置 | CreateTaskPushNotificationConfig | POST /tasks/{task_id}/pushNotificationConfigs | TaskPushNotificationConfig |
| 获取推送配置 | GetTaskPushNotificationConfig | GET /tasks/{task_id}/pushNotificationConfigs/{id} | TaskPushNotificationConfig |
| 列出推送配置 | ListTaskPushNotificationConfigs | GET /tasks/{task_id}/pushNotificationConfigs | ListTaskPushNotificationConfigsResponse |
| 删除推送配置 | DeleteTaskPushNotificationConfig | DELETE /tasks/{task_id}/pushNotificationConfigs/{id} | Empty |
| 获取扩展卡片 | GetExtendedAgentCard | GET /extendedAgentCard | AgentCard |
说明:
- 每条路由还存在带
/{tenant}前缀的形式,例如POST /{tenant}/message:send。proto 把它声明为附加绑定;规范的路由列表(第 11.3 节)只给出不带租户的形式。十一个操作的请求消息都有tenant字段。 - proto 把路径变量命名为
task_id和id;规范的表格(第 5.3 节)写的是{id}和{configId}。URL 的形状相同。 GET和DELETE没有请求体。它们的参数放在路径里,或放在驼峰命名的查询参数中,例如pageSize和contextId(第 11.5 节)。
文档之间不一致之处
| 主题 | 文档的说法 | 以谁为准 |
|---|---|---|
HTTP+JSON 上的 SubscribeToTask | 第 5.3 和 11.3.2 节写的是 POST /tasks/{id}:subscribe;proto 注解写的是 GET | 以 proto 为准。处理方式见 Task 运行时指南 |
JSON-RPC 的 method | 基础请求结构(第 9.3 节)写的是 "method": "category/action",即 v0.3 的模式。第 9.1 和 9.4 节规定的是 PascalCase 名称 | 以第 9.1 和 9.4 节为准:SendMessage、GetTask 等 |
示例里的 A2A-Version | 第 3.2.6、9.2、10.2 和 11.2 节的示例发送 0.3;第 3.6.1 节和 1.0 的卡片示例用 1.0 | 发送您所调用接口的 protocolVersion。把示例原样贴到只支持 1.0 的接口上,会返回 VersionNotSupportedError |
| 请求头注册 | 第 14.2.1 节指出定义它的是第 3.2.5 节;服务参数实际在第 3.2.6 节 | 以第 3.2.6 节为准 |
| 租户路由 | 第 11.3 节没有列出 /{tenant} 路由;proto 里有 | 以 proto 为准 |
3. 绑定之间有什么不同
支持多个绑定的服务器必须返回等价的结果,并一致地映射错误(第 5.1 节)。传输层的细节仍然各不相同。
| 方面 | JSON-RPC | gRPC | HTTP+JSON | 出处 |
|---|---|---|---|---|
| 调用 | POST 到接口 url,体为 {jsonrpc, id, method, params};params 就是请求消息 | A2AService 上的一元 RPC 或服务端流式 RPC | 每个操作一条路由(第 2 节 ) | 第 9.4、10.3 和 11.3 节 |
| 参数 | 在 params 中 | 在请求消息中 | POST 用 JSON 体;GET 和 DELETE 用路径和驼峰命名的查询参数 | 第 9.4 和 11.5 节 |
| 服务参数 | HTTP 请求头,不区分大小写 | gRPC metadata;键以小写到达,例如 a2a-version | HTTP 请求头,不区分大小写 | 第 9.2、10.2 和 11.2 节 |
| 内容类型 | application/json | 未规定 | application/a2a+json(SHOULD) | 第 9.1 和 11.1 节 |
| 租户 | params.tenant | 消息中的 tenant | tenant 字段,或 /{tenant}/... 路由前缀 | 第 8.3.2 节与 proto |
| 错误载体 | error.code、error.message 和 error.data(类型化对象数组) | status.code、status.message 和 status.details | HTTP 状态,加上 error.code、error.message 和 error.details | 第 9.5、10.6 和 11.6 节 |
| 具体是哪个 A2A 错误 | 错误码(-32001 到 -32009)每个错误唯一 | 六个错误共用 FAILED_PRECONDITION;请读 ErrorInfo.reason | 七个错误共用 400;请读 ErrorInfo.reason | 第 5.4、10.6 和 11.6 节 |
| 流式传输 | SSE;每行 data: 是一个完整的 JSON-RPC 响应,其 result 是 StreamResponse | StreamResponse 的服务端流式 RPC | SSE;每行 data: 是一个裸的 StreamResponse | 第 9.4.2、10.7 和 11.7 节 |
由此得出两点。第一,解析 JSON-RPC 流行的客户端必须解开 result;解析 REST 流行的客户端则不能解。第二,HTTP 404 或 400 只有在响应体带有 domain 为 a2a-protocol.org 的 ErrorInfo 时,才表示 A2A 错误;代理返回的或路由写错导致的裸 404 不是。
4. 从 v0.3 到 v1.0.1 的线上变化
名称与路由
v0.3.0 的 gRPC 名称和 REST 路由取自 proto,JSON-RPC 名称是 JSON schema 中的方法常量。v1.0.1 的 JSON-RPC 和 gRPC 使用第 2 节
中的操作名。
| 操作 | v0.3.0 JSON-RPC | v0.3.0 gRPC | v0.3.0 REST | v1.0.1 REST |
|---|---|---|---|---|
SendMessage | message/send | SendMessage | POST /v1/message:send | POST /message:send |
SendStreamingMessage | message/stream | SendStreamingMessage | POST /v1/message:stream | POST /message:stream |
GetTask | tasks/get | GetTask | GET /v1/tasks/{id} | GET /tasks/{id} |
ListTasks | 无 | 无 | 无 | GET /tasks |
CancelTask | tasks/cancel | CancelTask | POST /v1/tasks/{id}:cancel | POST /tasks/{id}:cancel |
SubscribeToTask | tasks/resubscribe | TaskSubscription | GET /v1/tasks/{id}:subscribe | GET /tasks/{id}:subscribe |
CreateTaskPushNotificationConfig | tasks/pushNotificationConfig/set | CreateTaskPushNotificationConfig | POST /v1/task/{id}/pushNotificationConfigs | POST /tasks/{task_id}/pushNotificationConfigs |
GetTaskPushNotificationConfig | tasks/pushNotificationConfig/get | GetTaskPushNotificationConfig | GET /v1/tasks/{id}/pushNotificationConfigs/{configId} | GET /tasks/{task_id}/pushNotificationConfigs/{id} |
ListTaskPushNotificationConfigs | tasks/pushNotificationConfig/list | ListTaskPushNotificationConfig | GET /v1/tasks/{id}/pushNotificationConfigs | GET /tasks/{task_id}/pushNotificationConfigs |
DeleteTaskPushNotificationConfig | tasks/pushNotificationConfig/delete | DeleteTaskPushNotificationConfig | DELETE /v1/tasks/{id}/pushNotificationConfigs/{configId} | DELETE /tasks/{task_id}/pushNotificationConfigs/{id} |
GetExtendedAgentCard | agent/getAuthenticatedExtendedCard | GetAgentCard | GET /v1/card | GET /extendedAgentCard |
v0.3.0 的文档有哪些错误,以及应当照什么来构建:
ListTasks在 v0.3.0 中并不存在。 规范的方法表(v0.3.0 第 3.5.6 节)及其tasks/list小节列出了ListTask和GET /v1/tasks,但 JSON schema 没有定义这个方法,proto 也没有这个 RPC。v1.0 客户端对 v0.3 智能体调用ListTasks,无处可达。- 推送配置的 RPC 名称。 v0.3.0 的表把四个 RPC 写成不带
Config后缀(CreateTaskPushNotification及其同类)。具有规范效力的 proto 带有该后缀,而且它的列表 RPC 是单数:ListTaskPushNotificationConfig。v1.0.1 改成了复数。 - 创建路由。 v0.3.0 规范给出的是
POST /v1/tasks/{id}/pushNotificationConfigs。v0.3.0 proto 的注解是/v1/{parent=task/*/pushNotificationConfigs},展开后是task,单数。 SubscribeToTask。 v0.3.0 的表写POST,proto 写GET。同样的不一致在 v1.0.1 中依然存在。- 资源名。 v0.3.0 的 gRPC 和 REST 请求用一个
name字符串标识资源,例如tasks/{id}或tasks/{id}/pushNotificationConfigs/{push_id},或者用parent。v1.0.1 使用独立的id和task_id字段。
变化的参数
下表对比 v0.3.0 的 JSON-RPC params(取自 JSON schema)与 v1.0.1 的请求消息(取自 proto,每个都多了 tenant)。
| 操作 | v0.3.0 params | v1.0.1 params | 会在哪里出问题 |
|---|---|---|---|
GetTask | id、historyLength、metadata | id、historyLength | metadata 没有字段可承载 |
SubscribeToTask | id、metadata | id | metadata 没有字段可承载 |
CancelTask | id、metadata | id、metadata | 无 |
CreateTaskPushNotificationConfig | taskId,以及 pushNotificationConfig(内含 id、url、token、authentication) | taskId、id、url、token、authentication | pushNotificationConfig 这层包装没有了;旧形态的请求把 URL 放在了服务器不再读取的位置 |
GetTaskPushNotificationConfig | id 是任务 ID;pushNotificationConfigId 是配置 ID;metadata | taskId 是任务 ID;id 是配置 ID | id 名称不变而含义改变;metadata 没有字段 |
DeleteTaskPushNotificationConfig | id 是任务 ID;pushNotificationConfigId 是配置 ID;metadata | taskId 是任务 ID;id 是配置 ID | 与获取调用相同的无声变化 |
ListTaskPushNotificationConfigs | id 是任务 ID;metadata | taskId、pageSize、pageToken | id 变成 taskId;列表改为分页;metadata 没有字段 |
带 id 的几行最危险。旧调用仍然是格式正确的 JSON,仍有一个叫 id 的字段,所以解析器那一关不会报错。服务器却把这个值当成配置 ID 来读。
其他线上变化
| 方面 | v0.3.0 | v1.0.1 | 出处 |
|---|---|---|---|
| 版本信号 | 规范里没有版本请求头 | 每个请求带 A2A-Version;空值按 0.3 处理 | 第 3.6 节 |
| 接口声明 | url、preferredTransport 和 additionalInterfaces;整张卡片一个 protocolVersion | supportedInterfaces,按偏好排序;每个条目有自己的 protocolVersion,格式为 Major.Minor | v0.3.0 第 5.6 节;第 8.3.1 和 8.3.2 节 |
| REST 内容类型 | application/json(MUST) | application/a2a+json(SHOULD) | v0.3.0 第 3.2.3 节;第 11.1 节 |
| REST 路由前缀 | /v1 | 无;版本属于接口 | v0.3.0 第 3.5.3 节;第 5.3 节 |
| 租户 | 没有 tenant 字段 | tenant 出现在每个请求消息和 AgentInterface 中 | 第 8.3.2 节 |
| JSON-RPC 错误码 | -32001 到 -32007;-32007 是 AuthenticatedExtendedCardNotConfiguredError | 编号相同;-32007 是 ExtendedAgentCardNotConfiguredError;-32008 和 -32009 是新增的 | v0.3.0 第 8 节;第 5.4 节 |
| 错误详情 | data 字段可以承载详情;其结构未定义 | data 是类型化对象数组,A2A 错误带有 ErrorInfo.reason 和 domain | 第 9.5 和 11.6 节 |
| REST 流式传输 | SSE,“类似于 JSON-RPC”;数据格式未定义 | 每行 data: 是一个裸的 StreamResponse | v0.3.0 第 3.3.3 节;第 11.7 节 |
| proto 位置 | specification/grpc/a2a.proto | specification/a2a.proto | v0.3.0 第 3.2.2 节;第 10.1 节 |
消息层面的变化(kind、枚举写法、parts、final 和阻塞默认值)见 Task 运行时指南
。卡片层面的变化见 Agent Card Proto 字段编号
。
5. 互操作失败时
| 您看到的现象 | 可能的原因 | 处理 |
|---|---|---|
JSON-RPC -32601(MethodNotFoundError),出现在对 SendMessage 的调用上 | 智能体是 v0.3,提供的是 message/send | 读取卡片。调用声明 1.0 的接口,或对 v0.3 接口使用 v0.3 的方法名 |
JSON-RPC -32601,出现在对 message/send 的调用上 | 智能体是 v1.0,提供的是 SendMessage | 同上,方向相反 |
gRPC UNIMPLEMENTED,出现在对 ListTasks、SubscribeToTask 或 GetExtendedAgentCard 的调用上 | gRPC 服务器对自己没有定义的方法返回 UNIMPLEMENTED(这是 gRPC 的规则,不是 A2A 的)。v0.3 服务器没有这几个名字 | 检查接口的 protocolVersion;对 v0.3 使用 TaskSubscription 和 GetAgentCard |
REST 404,出现在 /v1/message:send 或 /message:send 上 | 两个版本的路由前缀不同 | v0.3:/v1/...;v1.0:无前缀。是否为 A2A 的 404(TaskNotFoundError),要看 error.details:其中有 ErrorInfo,且 reason 为 TASK_NOT_FOUND |
VersionNotSupportedError | 请求头缺失、写了接口没有声明的版本,或者照抄了规范里的 0.3 示例 | 把条目的 protocolVersion 以 Major.Minor 发送(第 1 节
) |
客户端已设置版本,gRPC 服务器却把每个调用都当作 0.3 | gRPC 会把 metadata 的键转成小写,所以键以 a2a-version 到达。按 A2A-Version 查找的服务器什么也找不到,读到的是空值 | 按小写键查找(第 10.2 节) |
HTTP 400 与您预期的错误对不上 | 七个错误共用 400 | 读取 error.details[].reason(第 11.6 节) |
| 获取或删除推送配置时什么也找不到,或找到了错误的配置 | v0.3 客户端把任务 ID 放进 id;v1.0 把 id 当作配置 ID | 按第 4 节
发送 taskId 和 id |
| 流解析器在第一行就失败 | JSON-RPC 的行被包在 {jsonrpc, id, result} 里;REST 的行是裸的 StreamResponse | 只对 JSON-RPC 解开 result |
HTTP+JSON 上的 SubscribeToTask 因方法错误被拒 | 规范写 POST,proto 写 GET | 调用 GET;对端回复方法错误时,换另一个动词再试 |
| 智能体同时提供 1.0,客户端却落在了旧版本上 | 一张卡片把同一个绑定列了两次,protocolVersion 分别是 0.3 和 1.0,客户端取了第一个条目 | 按 protocolVersion 选择,不要只看位置(第 3.6.2 节) |
客户端找不到 supportedInterfaces | 这是一张 v0.3 卡片,只有 url 和 preferredTransport | 按 v0.3 的规则读取(v0.3.0 第 5.6.3 节),或请卡片所有者升级。见 Agent Card 实现指南 |
6. 互操作测试场景
对服务器(S)或客户端(C)运行这些场景。服务器场景可以用 curl 或任意 gRPC 客户端驱动。客户端场景只需要一张可读的卡片,I14 还需要一个会回复方法错误的对端。
| ID | 端 | 场景 | 预期 | 出处 |
|---|---|---|---|---|
| I1 | S | 在卡片声明的每个绑定上发送同一条消息 | 语义等价的结果 | 第 5.1 节 |
| I2 | S | 在每个绑定上触发同一种失败,例如未知的任务 ID | 该绑定的原生错误(-32001、NOT_FOUND、404),加上一个 ErrorInfo,其 reason 为 TASK_NOT_FOUND、domain 为 a2a-protocol.org | 第 5.4、9.5、10.6 和 11.6 节 |
| I3 | S | 调用只支持 1.0 的接口,且不带 A2A-Version | VersionNotSupportedError | 第 3.6.2 节 |
| I4 | S | 先带 A2A-Version: 1.0 调用,再带 9.9 调用 | 第一次成功;第二次返回 VersionNotSupportedError | 第 3.6.2 节 |
| I5 | S | 带 A2A-Version: 1.0.1 调用 | 记录结果;规范没有定义 | 第 3.6 和 14.2.1 节 |
| I6 | S | 在 HTTP 请求头中以 a2a-version 发送版本,并在 gRPC metadata 中以 a2a-version 发送 | 与 A2A-Version 的处理完全一致 | 第 3.2.6、9.2、10.2 和 11.2 节 |
| I7 | S | 发送带两个 URI(逗号分隔)的 A2A-Extensions;再省略一个必需的扩展 | 两个 URI 都被识别;省略则返回 ExtensionSupportRequiredError | 第 3.3.4 和 9.2 节 |
| I8 | S | 接口带有 tenant 时,通过 HTTP+JSON 分别用 tenant 字段和 /{tenant} 路由前缀各调用一次 | 两者到达同一个租户 | 第 8.3.2 节与 proto |
| I9 | C | 读取一张先列 JSONRPC、再列 GRPC 的卡片,客户端只会说 gRPC | 客户端调用第二个条目的 url | 第 8.3.2 节 |
| I10 | C | 读取一张同一绑定有两个条目(0.3 和 1.0)的卡片 | 客户端调用与其所发送版本匹配的条目 | 第 3.6.2 和 3.6.3 节 |
| I11 | S | 分别通过 JSON-RPC 和 HTTP+JSON 做流式传输 | JSON-RPC 的行可解析为 JSON-RPC 响应;REST 的行是裸的 StreamResponse 对象 | 第 9.4.2 和 11.7 节 |
| I12 | S | GET /tasks?contextId=...&status=TASK_STATE_WORKING&pageSize=10 | 过滤条件和页大小生效 | 第 11.5 节 |
| I13 | S | 在请求里加一个未知字段 | 请求被正常处理;该字段被忽略 | 第 5.7 节 |
| I14 | C | 通过 HTTP+JSON 订阅 | 客户端调用 GET;遇到方法错误时改用 POST 重试 | 第 5.3 和 11.3.2 节,以及 proto |
出处:A2A 官方规范:协议绑定要求与互操作(5)、版本控制(3.6)、Agent Card 协议声明(8.3),以及 JSON-RPC、gRPC、HTTP+JSON 三种绑定(9 至 11)
;v1.0.1
与 v0.3.0
标签下的 a2a.proto;v0.3.0 JSON schema
和规范