跳转至

2026-06-20

今日主题

  • A2A 协议整体架构
  • A2A Agent Card 结构与用途
  • A2A 核心数据模型关系
  • A2A contextId 和 Task 生命周期
  • A2A 三种交互模式
  • A2A 扩展机制设计
  • A2A 与 Orchestrator 的阻抗失配

新增认知

A2A 协议整体架构

  • 三层架构分离
    A2A 分为数据模型层(Task/Message/Part/Artifact)、操作层(SendMessage/GetTask 等抽象操作)、协议绑定层(JSONRPC/gRPC/HTTP+JSON)。
    三层各自独立,核心语义在操作层定义,绑定层只负责报文格式映射,因此同一操作可以用不同协议表达且语义等价。

  • JSON-RPC 是信封,A2A 是内容:JSON-RPC 2.0 只提供 method/params/id 的 RPC 调用结构,
    A2A 的所有业务语义(Task 状态机、contextId 会话、Artifact 产出)全在 params/result 里展开。两层各司其职,互不污染。

  • 三种协议绑定功能等价:JSONRPC(单端点+method 字段区分操作)、HTTP+JSON(多端点 REST 风格)、gRPC(proto 定义)。
    Agent Card 的 supportedInterfaces[] 按优先级声明,Client 选第一个自己支持的。

A2A Agent Card 结构与用途

  • Agent Card 是运行时合约:Client 在发起任何请求前先 GET /.well-known/agent-card.json,
    从中获得接入地址(supportedInterfaces)、能力开关(capabilities)、认证方式(securitySchemes)、技能列表(skills)。
    所有交互策略都由 Card 内容决定,不是硬编码。

  • skills.examples 是 LLM 路由索引:Agent Card 里每个 skill 的 examples 字段存放示例输入句子,
    Orchestrator 做 Agent 路由时可以把用户意图和 examples 做语义相似度匹配,决定把请求发给哪个 Agent 的哪个 skill。
    这是协议为大规模 Agent 发现预留的钩子。

  • extendedAgentCard 实现能力分层:Agent 可以发布两张 Card——公开 Card 暴露基础能力,
    认证后调 GetExtendedAgentCard 获取完整 Card(含内部/付费 skill)。
    Client 拿到 Extended Card 后应替换本地缓存的公开 Card。

  • v0.x 到 v1.0 的 Breaking Change:旧版顶层只有单个 url 字段,
    v1.0 改为 supportedInterfaces[] 数组,每条含 url+protocolBinding+protocolVersion,
    支持同一 Agent 暴露多种协议。看到顶层有 url 字段的 Card 是旧版本。

A2A 核心数据模型关系

  • Message 是过程,Artifact 是结果:Message 用于对话轮次(过程性说明、澄清问题、状态通知),
    Artifact 是 Task 的实质产出物(文件/图片/结构化数据)。规范明确:结果 SHOULD 用 Artifact 返回,不应用 Message 传递。
    Artifact 只能存在于 Task 里,不能独立存在。

  • status.message 和 artifacts[] 位置不同:status.message 是附着在 Task 状态上的临时说明(随状态变化),
    artifacts[] 是 Task 的持久产出物。前者是 Agent 说的话,后者是 Agent 交的东西。

  • Part 是统一内容容器:v1.0 用单一 Part 结构替代了 v0.3 的 TextPart/FilePart/DataPart,
    Part 内部用 oneof 持有 text/raw/url/data 之一,附带可选的 mediaType/filename/metadata。
    这是 v1.0 的 Breaking Change 之一。

A2A contextId 和 Task 生命周期

  • contextId 由 Server 在首次响应时生成:Client 第一次发消息不带 contextId,
    Server 返回时附上新生成的 contextId。后续请求 Client 带回 contextId 表示同一会话。
    contextId 在 Server 内部用于维护 LLM 对话历史/会话状态。

  • taskId 可以替代 contextId:续接已有 Task 时只需带 taskId,Server 会从 task 自动推断 contextId。
    但如果同时带了两者且不匹配,Server 必须拒绝请求(MUST reject)。

  • Task 不可重启,终态不可变:Task 一旦到达终态(COMPLETED/FAILED/CANCELED/REJECTED)就不能再修改。
    后续需要在同一 contextId 下创建新 Task,可用 referenceTaskIds 引用原 Task。这保证了 Task 作为工作单元的可追溯性。

  • INPUT_REQUIRED 是中断态不是终态:Task 进入 INPUT_REQUIRED 时流暂停但不关闭,
    等 Client 发 SendMessage(taskId=xxx) 续接后继续。这是协议支持人在回路(human-in-the-loop)的核心机制,
    但要求 Orchestrator 能处理多轮中断,实现复杂。

A2A 三种交互模式

  • SendMessage 默认 blocking:规范明确 return_immediately 默认 false,
    Server MUST 等到终态或中断态才返回。想要轮询必须显式传 returnImmediately:true 先拿到 taskId,
    再用 GetTask 轮询。不是 Server 实现自由选择,是规范强制。

  • 流式只在响应侧,请求永远是一次性 POST:A2A 没有流式请求,Client 请求是完整的一次性 POST。只有响应可以流式(SSE)。设计合理:
    Agent 间传递的是完整指令,不需要流式输入;大文件走 url 引用不走上传。

  • SSE 流里三种事件交错推送:SendStreamingMessage 响应流中,第一条是 task 或 message,
    后续是 statusUpdate(状态变化)和 artifactUpdate(产出物分块)交错出现。
    artifactUpdate 用 append/lastChunk 控制分块拼装。流关闭信号是 statusUpdate{final:true}。

  • GetTask 轮询拿不到中间分块:轮询模式只能拿到 Task 的当前状态快照,无法获得流式场景下的 artifact 中间分块,
    只能等全部完成后拿完整 artifact。要实现打字机效果必须用 SendStreamingMessage。

A2A 扩展机制设计

  • 扩展是给人读的规范,不是运行时协议:Extension URI 指向的规范文档由开发者阅读并手动实现,不是 Agent 运行时动态解析的。
    激活扩展靠 HTTP Header A2A-Extensions,数据靠 metadata(key 为扩展 URI)。
    message.extensions[] 是冗余字段,信息已由 Header 和 metadata key 完全覆盖。

  • 四种扩展类型的能力边界
    Data-only(附加数据不改流程)、Profile(约束现有字段取值范围)、Method/Extended Skills(新增 JSON-RPC 方法,
    本质是在路由表加 handler)、State Machine(新增 Task 状态)。扩展不能修改核心数据结构字段定义,也不能新增 enum 值。

A2A 与 Orchestrator 的阻抗失配

  • A2A 假设 Orchestrator 是有状态调度系统
    协议设计隐含 Orchestrator 能持续跟踪 Task 状态、处理多轮中断、管理并发 Task。
    但 LLM+tool call 模式的 Orchestrator 是无状态线性推理过程,两者天然不匹配,导致 INPUT_REQUIRED 处理复杂且脆弱。

  • 实际落地往往降级使用:生产中大多数 A2A 实现会避免 INPUT_REQUIRED(Server 内部消化多轮澄清),
    只用 COMPLETED/FAILED,把 A2A 当增强版 HTTP API 而非真正的 Agent 协议。
    真正的 Agent-to-Agent 对接(双方都有自己的调度能力)才是 A2A 的理想场景,MCP 更适合 LLM+tool 模式。