A2A 互操作速查:协议绑定、版本与线上格式差异

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.0VersionNotSupportedError,因为空值按 0.3 处理第 3.6.2 节
缺失或为空0.3按 0.3 语义处理第 3.6.2 节
1.01.0按 1.0 语义处理第 3.6.2 节
0.3仅 1.0VersionNotSupportedError第 3.6.2 节
0.3一个 0.3 接口和一个 1.0 接口,在同一 URL 或不同 URL 上每个接口应用各自的版本;调用声明 0.3 的那个第 3.6.2 节
2.0,或任何没有接口声明的值任意VersionNotSupportedError第 3.6.2 节
1.0.11.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 RPCHTTP+JSON 路由返回
发送消息SendMessagePOST /message:sendSendMessageResponse:task 或 message
发送并流式返回SendStreamingMessagePOST /message:streamStreamResponse 流
获取任务GetTaskGET /tasks/{id}Task
列出任务ListTasksGET /tasksListTasksResponse
取消任务CancelTaskPOST /tasks/{id}:cancelTask
订阅任务SubscribeToTaskGET /tasks/{id}:subscribeStreamResponse 流
创建推送配置CreateTaskPushNotificationConfigPOST /tasks/{task_id}/pushNotificationConfigsTaskPushNotificationConfig
获取推送配置GetTaskPushNotificationConfigGET /tasks/{task_id}/pushNotificationConfigs/{id}TaskPushNotificationConfig
列出推送配置ListTaskPushNotificationConfigsGET /tasks/{task_id}/pushNotificationConfigsListTaskPushNotificationConfigsResponse
删除推送配置DeleteTaskPushNotificationConfigDELETE /tasks/{task_id}/pushNotificationConfigs/{id}Empty
获取扩展卡片GetExtendedAgentCardGET /extendedAgentCardAgentCard

说明:

  • 每条路由还存在带 /{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-RPCgRPCHTTP+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-versionHTTP 请求头,不区分大小写第 9.2、10.2 和 11.2 节
内容类型application/json未规定application/a2a+json(SHOULD)第 9.1 和 11.1 节
租户params.tenant消息中的 tenanttenant 字段,或 /{tenant}/... 路由前缀第 8.3.2 节与 proto
错误载体error.code、error.message 和 error.data(类型化对象数组)status.code、status.message 和 status.detailsHTTP 状态,加上 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 是 StreamResponseStreamResponse 的服务端流式 RPCSSE;每行 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-RPCv0.3.0 gRPCv0.3.0 RESTv1.0.1 REST
SendMessagemessage/sendSendMessagePOST /v1/message:sendPOST /message:send
SendStreamingMessagemessage/streamSendStreamingMessagePOST /v1/message:streamPOST /message:stream
GetTasktasks/getGetTaskGET /v1/tasks/{id}GET /tasks/{id}
ListTasks无无无GET /tasks
CancelTasktasks/cancelCancelTaskPOST /v1/tasks/{id}:cancelPOST /tasks/{id}:cancel
SubscribeToTasktasks/resubscribeTaskSubscriptionGET /v1/tasks/{id}:subscribeGET /tasks/{id}:subscribe
CreateTaskPushNotificationConfigtasks/pushNotificationConfig/setCreateTaskPushNotificationConfigPOST /v1/task/{id}/pushNotificationConfigsPOST /tasks/{task_id}/pushNotificationConfigs
GetTaskPushNotificationConfigtasks/pushNotificationConfig/getGetTaskPushNotificationConfigGET /v1/tasks/{id}/pushNotificationConfigs/{configId}GET /tasks/{task_id}/pushNotificationConfigs/{id}
ListTaskPushNotificationConfigstasks/pushNotificationConfig/listListTaskPushNotificationConfigGET /v1/tasks/{id}/pushNotificationConfigsGET /tasks/{task_id}/pushNotificationConfigs
DeleteTaskPushNotificationConfigtasks/pushNotificationConfig/deleteDeleteTaskPushNotificationConfigDELETE /v1/tasks/{id}/pushNotificationConfigs/{configId}DELETE /tasks/{task_id}/pushNotificationConfigs/{id}
GetExtendedAgentCardagent/getAuthenticatedExtendedCardGetAgentCardGET /v1/cardGET /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 paramsv1.0.1 params会在哪里出问题
GetTaskid、historyLength、metadataid、historyLengthmetadata 没有字段可承载
SubscribeToTaskid、metadataidmetadata 没有字段可承载
CancelTaskid、metadataid、metadata无
CreateTaskPushNotificationConfigtaskId,以及 pushNotificationConfig(内含 id、url、token、authentication)taskId、id、url、token、authenticationpushNotificationConfig 这层包装没有了;旧形态的请求把 URL 放在了服务器不再读取的位置
GetTaskPushNotificationConfigid 是任务 ID;pushNotificationConfigId 是配置 ID;metadatataskId 是任务 ID;id 是配置 IDid 名称不变而含义改变;metadata 没有字段
DeleteTaskPushNotificationConfigid 是任务 ID;pushNotificationConfigId 是配置 ID;metadatataskId 是任务 ID;id 是配置 ID与获取调用相同的无声变化
ListTaskPushNotificationConfigsid 是任务 ID;metadatataskId、pageSize、pageTokenid 变成 taskId;列表改为分页;metadata 没有字段

带 id 的几行最危险。旧调用仍然是格式正确的 JSON,仍有一个叫 id 的字段,所以解析器那一关不会报错。服务器却把这个值当成配置 ID 来读。

其他线上变化

方面v0.3.0v1.0.1出处
版本信号规范里没有版本请求头每个请求带 A2A-Version;空值按 0.3 处理第 3.6 节
接口声明url、preferredTransport 和 additionalInterfaces;整张卡片一个 protocolVersionsupportedInterfaces,按偏好排序;每个条目有自己的 protocolVersion,格式为 Major.Minorv0.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: 是一个裸的 StreamResponsev0.3.0 第 3.3.3 节;第 11.7 节
proto 位置specification/grpc/a2a.protospecification/a2a.protov0.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.3gRPC 会把 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端场景预期出处
I1S在卡片声明的每个绑定上发送同一条消息语义等价的结果第 5.1 节
I2S在每个绑定上触发同一种失败,例如未知的任务 ID该绑定的原生错误(-32001、NOT_FOUND、404),加上一个 ErrorInfo,其 reason 为 TASK_NOT_FOUND、domain 为 a2a-protocol.org第 5.4、9.5、10.6 和 11.6 节
I3S调用只支持 1.0 的接口,且不带 A2A-VersionVersionNotSupportedError第 3.6.2 节
I4S先带 A2A-Version: 1.0 调用,再带 9.9 调用第一次成功;第二次返回 VersionNotSupportedError第 3.6.2 节
I5S带 A2A-Version: 1.0.1 调用记录结果;规范没有定义第 3.6 和 14.2.1 节
I6S在 HTTP 请求头中以 a2a-version 发送版本,并在 gRPC metadata 中以 a2a-version 发送与 A2A-Version 的处理完全一致第 3.2.6、9.2、10.2 和 11.2 节
I7S发送带两个 URI(逗号分隔)的 A2A-Extensions;再省略一个必需的扩展两个 URI 都被识别;省略则返回 ExtensionSupportRequiredError第 3.3.4 和 9.2 节
I8S接口带有 tenant 时,通过 HTTP+JSON 分别用 tenant 字段和 /{tenant} 路由前缀各调用一次两者到达同一个租户第 8.3.2 节与 proto
I9C读取一张先列 JSONRPC、再列 GRPC 的卡片,客户端只会说 gRPC客户端调用第二个条目的 url第 8.3.2 节
I10C读取一张同一绑定有两个条目(0.3 和 1.0)的卡片客户端调用与其所发送版本匹配的条目第 3.6.2 和 3.6.3 节
I11S分别通过 JSON-RPC 和 HTTP+JSON 做流式传输JSON-RPC 的行可解析为 JSON-RPC 响应;REST 的行是裸的 StreamResponse 对象第 9.4.2 和 11.7 节
I12SGET /tasks?contextId=...&status=TASK_STATE_WORKING&pageSize=10过滤条件和页大小生效第 11.5 节
I13S在请求里加一个未知字段请求被正常处理;该字段被忽略第 5.7 节
I14C通过 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 和规范