文章

【笔记】A2A 协议的任务模型与 Agent 协作机制

【笔记】A2A 协议的任务模型与 Agent 协作机制

1. 一句话心智模型

A2A(Agent2Agent Protocol)解决的不是“模型怎样调用一个函数”,而是“两个内部实现互不透明的 Agent,怎样发现彼此、交换信息并协作完成可能持续很久的任务”。

可以把它压缩成一条链路:

flowchart LR
    A[Agent Card<br/>发现能力和入口] --> B[Message<br/>表达本轮意图]
    B --> C{是否需要持续追踪}
    C -->|否| D[Message<br/>直接回答]
    C -->|是| E[Task<br/>保存状态和结果]
    E --> F[轮询 / 流式 / Push<br/>交付后续更新]

这里最关键的分界不是同步与异步,而是是否需要一个可寻址、可持续更新的工作单元:

  • 一次回复已经足够时,服务端可以直接返回 Message
  • 工作需要经历状态变化、用户补充输入或逐步产生结果时,服务端返回 Task

A2A 因而不是“远程 Tool Call 格式”。它把 Agent 当作拥有自己能力描述、会话语境和任务生命周期的独立系统。

2. A2A 在解决什么问题

普通 HTTP API 假设调用方已经知道接口地址、参数和返回结构。Agent 协作还多出几个问题:

  1. 调用方怎样知道远端 Agent 能做什么、支持什么输入输出?
  2. 一次自然语言消息究竟只是问答,还是启动了一个需要追踪的任务?
  3. 任务执行几分钟甚至几小时,状态和中间产物怎样交付?
  4. 任务要求补充信息时,后续消息怎样回到原来的上下文?
  5. 两端使用不同框架和传输技术时,怎样共享同一套业务语义?

A2A 给出的答案可以分成三层:

层次解决的问题主要元素
规范数据模型双方在谈论什么Agent Card、Message、Task、Part、Artifact
抽象操作双方可以做什么发送消息、查询或取消任务、订阅更新、管理 Push 配置
协议绑定消息怎样在网络上传输JSON-RPC、gRPC、HTTP+JSON,也允许自定义绑定

这种分层让“任务是什么”不依赖某一种传输方式。JSON-RPC 的方法名、REST 的路径和 gRPC 的 RPC 形式不同,但应表达同一组抽象操作。

3. 从发现到执行的完整链路

3.1 Agent Card:远端 Agent 的可发现契约

Agent Card 是一份描述 Agent 身份、能力和接入方式的 JSON 文档。标准的公开发现地址是:

1
https://agent.example.com/.well-known/agent-card.json

调用方主要从中读取:

  • namedescriptionprovider:它是谁。
  • skills:它声明了哪些能力,以及能力的输入输出模式和示例。
  • supportedInterfaces:每个入口的 URL、协议绑定和 A2A 协议版本。
  • capabilities:是否支持流式更新、Push Notification、扩展卡片等能力。
  • securitySchemessecurity:怎样取得并携带认证凭证。

Agent Card 解决的是发现和协商,不承担业务调用。客户端仍需根据卡片选定接口、完成认证,再发送 Message

skills 也不是可以直接执行的本地函数清单。它更像远端 Agent 对外暴露的语义能力:调用方可以用规则、检索或 LLM 从多个 Agent 中选择候选,真正调用仍通过 A2A 操作完成。

3.2 Message:一轮通信,而不是任务本身

Message 表示客户端或服务端发出的一轮通信。核心字段包括:

  • messageId:消息自身的唯一标识。
  • role:消息来自用户侧还是 Agent 侧。
  • parts:消息内容。
  • 可选的 contextIdtaskId:把消息关联到某个交互上下文或具体任务。

Part 是统一的内容容器。A2A v1.0 的规范模型允许在同一个结构中承载文本、原始字节、URL 或结构化数据,并用 mediaTypefilenamemetadata 等字段补充语义。

因此 Message 不等同于纯文本聊天消息。一次消息可以同时包含自然语言、文件引用和结构化参数。

3.3 Message 或 Task:由远端决定是否建立工作单元

发送消息后,远端可以返回两类结果:

  • 返回 Message:这一轮已经回答完毕,没有需要继续追踪的工作状态。
  • 返回 Task:服务端建立了一个有身份、有状态、可以继续查询或订阅的工作单元。

这个设计允许简单 Agent 保持简单,也允许复杂 Agent 暴露长任务。调用方不能预设“每次调用必然得到 Task”,必须处理两种结果。

4. Task 怎样承载长任务

4.1 Task 的内部结构

一个 Task 可以抽象为:

1
2
3
4
5
6
7
8
9
10
Task
├── id                         任务标识
├── contextId                  所属交互上下文
├── status
│   ├── state                  当前状态
│   ├── message                对当前状态的说明
│   └── timestamp
├── artifacts[]                已产生的工作成果
├── history[]                  可选的消息历史
└── metadata                   可选扩展信息

其中最容易混淆的是 status.messageartifacts

内容回答的问题例子
status.messageAgent 当前想告诉调用方什么“还缺少出发日期”
artifacts任务已经产出了什么行程单、报告、生成文件

消息解释过程,Artifact 承载成果。Artifact 由一个或多个 Part 组成,因此既可以是文本,也可以是文件、URL 或结构化数据。

4.2 状态机的真正分界是可继续还是已终结

Task 的常见状态关系如下:

stateDiagram-v2
    [*] --> Submitted
    Submitted --> Working
    Working --> InputRequired
    Working --> AuthRequired
    InputRequired --> Working: 补充消息
    AuthRequired --> Working: 完成认证
    Working --> Completed
    Working --> Failed
    Submitted --> Canceled
    Working --> Canceled
    Submitted --> Rejected

规范明确的终态是:

  • completed
  • failed
  • canceled
  • rejected

input-requiredauth-required 不是终态。它们表示当前执行无法继续,但客户端仍可围绕原任务发送补充消息。

终态之后不能再订阅这个 Task;当前规范要求 SubscribeToTask 对终态任务返回不支持操作错误。如果同一业务上下文还要继续工作,应创建新的 Task,而不是把已经结束的 Task 当作可复用会话。

4.3 contextId 与 taskId 是两个维度

taskId 标识一个具体工作单元;contextId 用于把相关 Message 和 Task 归入同一交互上下文。

1
2
3
4
5
context-42
├── message: 澄清需求
├── task-A: 搜集资料(completed)
├── message: 修改范围
└── task-B: 生成新报告(working)

由此可以得到两个实用判断:

  • 续接一个尚未结束的工作,需要关联 taskId
  • 旧 Task 已经终结,但后续工作仍属于同一轮业务语境时,可以保留 contextId 并建立新 Task。

两者都不是客户端本地聊天记录的替代品。服务端是否保留完整历史、历史保留多久,仍属于实现和部署策略。

5. 三种更新交付方式

A2A 把任务状态与状态的交付方式分开。同一个 Task 可以通过轮询、持续连接或回调获得更新。

5.1 轮询:反复读取权威快照

客户端周期性调用 GetTask,获取任务当前状态和已有 Artifact。

优点是实现简单、天然适合跨网络边界;代价是延迟和额外请求。轮询读取的是当前快照,不应依赖它获得每一次短暂的中间事件。

5.2 流式:在连接上接收增量事件

SendStreamingMessage 在发送消息后直接返回服务端流;SubscribeToTask 则为一个尚未进入终态的既有 Task 建立更新流。

流中可能出现:

  • 完整的 TaskMessage
  • TaskStatusUpdateEvent:状态变化。
  • TaskArtifactUpdateEvent:Artifact 新建或增量更新。

Artifact 更新中的 append 表示当前内容是否追加到既有 Artifact;lastChunk 表示该 Artifact 的分块是否结束。这两个标志描述 Artifact 的组装,不能直接推导整个 Task 已进入终态。Task 是否结束仍要看状态更新。

在 HTTP 绑定中,服务端流通常使用 SSE;gRPC 绑定则使用 server streaming。SSE 是传输手段,Task 更新事件才是 A2A 的业务语义。

5.3 Push Notification:把更新送到客户端端点

对于不适合长期保持连接的任务,客户端可以为 Task 注册 Push Notification 配置,由服务端向指定 Webhook 投递更新。

这种方式适合服务到服务的长任务,但引入了额外安全问题:

  • 服务端需要验证回调地址,避免 SSRF。
  • 客户端需要验证通知来源和完整性。
  • 回调失败需要重试、去重和幂等处理。
  • 客户端在不确定本地状态时,应通过 GetTask 重新读取权威状态。

轮询、流式和 Push 并不是三套任务模型,只是同一 Task 的三种更新通道。

6. 协议绑定与认证边界

6.1 三种标准绑定共享同一语义

A2A v1.0 当前定义了 JSON-RPC、gRPC 和 HTTP+JSON 绑定。Agent Card 的 supportedInterfaces 可以同时列出多个入口,并按顺序表达偏好。

调用方不应只看到 URL 就猜测协议。它需要同时读取:

  • protocolBinding:怎样编码和调用操作。
  • protocolVersion:使用哪一版 A2A 语义。
  • url:具体服务入口。

自定义绑定也可以存在,但必须用唯一 URI 标识,并说明怎样把抽象操作映射到实际传输。

6.2 认证发生在调用之前

Agent Card 声明支持的安全方案,但不会替客户端签发凭证。客户端通常需要先通过 OAuth、OpenID Connect、API Key 或部署方约定的机制取得凭证,再在传输层携带。

因此 A2A 规定的是认证方案的描述和协商位置,不替代身份提供方,也不替代业务授权。

公开 Agent Card 还可能只暴露基础信息。若卡片声明支持扩展卡片,认证后的客户端可以通过 GetExtendedAgentCard 获取更完整的能力描述。

7. 扩展机制的边界

A2A 扩展以 URI 作为身份。Agent Card 先声明自己支持哪些扩展,客户端在请求中表明希望激活的扩展,扩展数据通常放入对象的 metadata

扩展的价值是让双方在标准核心之外约定额外语义,同时仍能识别“这段数据属于哪个扩展”。但扩展不是随意修改核心协议的后门:

  • 未协商的扩展不能假定对端理解。
  • 标记为 required 的扩展无法满足时,请求不应悄悄降级。
  • 网关需要感知的版本和扩展协商信息属于传输层;具体业务扩展数据属于消息体。
  • 一旦扩展改变核心状态机或操作语义,互操作成本会明显上升,需要独立规范和版本治理。

8. A2A、MCP 与普通 Tool Call 的边界

三者的抽象对象不同:

机制对端被视为什么主要关注点
本地 Tool Call当前 Agent 可调用的函数参数与返回值
MCP可发现的工具、资源或提示能力提供方Agent 如何使用外部能力
A2A拥有自主执行能力的远端 Agent发现、消息、任务生命周期和异步协作

这不是互斥选型。一个 A2A Agent 内部完全可以通过 MCP 使用工具;一个编排器也可以把远端 A2A Agent 包装成本地模型眼中的工具。

判断是否需要 A2A,可以问三个问题:

  1. 对端是否拥有独立的执行循环和任务状态?
  2. 是否需要跨进程、跨团队或跨框架协作?
  3. 是否需要标准化发现、长任务追踪或异步更新?

如果答案都是否,普通 HTTP API 或 Tool Call 往往更直接。

9. 实现时容易踩混的地方

9.1 流式响应不等于流式请求

SendStreamingMessage 的“streaming”描述的是服务端持续返回更新。客户端仍提交一条完整 Message。若需要不断补充输入,应发送新的 Message,并通过 taskIdcontextId 建立关联。

9.2 收到最后一个 Artifact 分块不等于任务完成

lastChunk 只结束当前 Artifact 的分块。Agent 还可能生成其他 Artifact,或者随后进入 input-required。Task 的最终状态必须以状态更新或重新读取的 Task 为准。

9.3 Push 不能省掉状态对账

Webhook 可能重复、乱序或暂时失败。客户端需要用事件标识或任务版本做幂等处理,并在状态不确定时重新调用 GetTask。不能把“收到一次回调”等同于“本地已经拥有完整且最新的 Task”。

9.4 Agent Card 是契约,不是可信事实

卡片声明了能力和安全方案,但实际调用仍可能失败。生产系统需要处理版本不兼容、能力声明与实现不一致、认证失败和部分协议绑定不可用。

10. 复习索引

  • 中心模型:Agent Card 负责发现,Message 负责一轮表达,Task 负责持续状态,轮询、流式和 Push 负责交付更新。
  • 核心分界:Message 是通信单元;Task 是可寻址、可持续更新的工作单元。
  • 成果与过程status.message 解释当前状态,artifacts 保存任务产出。
  • 两个标识taskId 指向具体工作,contextId 组织相关交互。
  • 终态:completed、failed、canceled、rejected;input-required 和 auth-required 仍可继续。
  • 流式锚点appendlastChunk 只描述 Artifact 分块;Task 是否结束看状态。
  • 绑定关系:JSON-RPC、gRPC、HTTP+JSON 共享核心数据模型和抽象操作。
  • 边界判断:A2A 面向独立 Agent 之间的协作,MCP 面向 Agent 使用外部能力,两者可以组合。

11. 参考资料

本文由作者按照 CC BY 4.0 进行授权