A2A 详解
一句话概括:A2A(Agent2Agent)是 Google Cloud 于 2025 年推出的开放协议,用于解决多个 AI Agent 之间如何互相发现、通信与协作的问题。它与 MCP「一横一纵」互补——MCP 让 Agent 向下连工具,A2A 让 Agent 向外连 Agent。
一、什么是 A2A 协议
A2A(Agent2Agent)协议是由 Google Cloud 推出的开放协议,旨在促进不同 AI Agent 之间的互操作性。其核心目标是:让由不同供应商构建、使用不同技术框架的 Agent,能够在动态的多 Agent 生态中有效通信与协作。
1.1 为什么需要 A2A:单 Agent 的天花板
一个 Agent 的本质是「一个 LLM + 一组工具 + 一段上下文窗口」,这三个维度都有各自的上限。当任务足够复杂时,单 Agent 就会力不从心:
| 瓶颈维度 | 问题描述 | A2A 的解法 |
|---|---|---|
| 工具数量 | 一个 Agent 装上百个工具,模型选择效率极低、易混乱 | 拆分为多个专业 Agent,各自聚焦少量工具 |
| 上下文窗口 | 128K token 看似很多,中间产物(搜索结果、草稿、反思)会迅速塞满,后段生成顾不上前文 | 子任务在独立 Agent 内闭环,上下文互不污染 |
| 专业能力 | 同一 Agent 既做代码审查又做市场分析,效果不如各自专精的 Agent | 为不同任务配置/微调专用 Agent,再汇总 |
典型场景:「做一份 AI 编程工具竞品分析报告,含行业趋势、技术对比、商业模式与 SWOT」。单 Agent 做会被搜索结果撑满上下文;更优解是一个调度 Agent 拆分任务,交给市场分析 Agent、技术研究 Agent 并行处理,最后汇总——这正是 A2A 要支撑的多 Agent 协作。
1.2 A2A 的五大设计原则
| 设计原则 | 含义 |
|---|---|
| 拥抱 Agent 能力 | 允许 Agent 以自然、非结构化的方式协作,无需共享内存、工具或上下文,实现真正的多 Agent 场景 |
| 基于现有标准 | 建立在 HTTP、SSE、JSON-RPC 2.0 等广泛接受的标准之上,便于集成企业现有 IT 栈 |
| 默认安全 | 原生支持企业级身份验证与授权,确保只有授权用户/系统可访问 Agent |
| 支持长任务 | 从秒级快速任务到分钟/小时级深度研究都能覆盖,执行过程中可提供实时反馈、通知与状态更新 |
| 模态无关 | 支持文本、音频/视频流、表单、iframe 等多种交互形式,适应力强 |
1.3 A2A 的三个参与者
相比 MCP 的「客户端-主机-服务器」三方结构,A2A 只定义了三个参与者,缺少 Host 角色——这是设计取舍:A2A 选择规范协作机制,把安全、Agent 发现等交给外部实现。
| 参与者 | 职责 |
|---|---|
| 用户 User | 使用 Agent 系统完成任务的人或服务,是需求的最终来源 |
| 客户端 Client | 代表用户向远程 Agent 发起操作请求的实体(可以是一个应用、Agent 或调度器) |
| 服务端 Server | 不透明(黑盒)的远程 Agent,即 A2A Server;对外只暴露能力,内部实现完全隐藏 |
三者的关系与协作载体如下图所示:
flowchart LR
classDef blue fill:#EAF4FF,stroke:#4C8FD9,stroke-width:1.5px,color:#123B66
classDef green fill:#EAF8EF,stroke:#5B9B6B,stroke-width:1.5px,color:#1F4D2E
classDef orange fill:#FFF4E8,stroke:#D79A4A,stroke-width:1.5px,color:#7A4B12
linkStyle default stroke:#90A4AE,stroke-width:1.2px
USER([用户 User]):::green --> CLIENT[客户端 Client
代表用户发起请求]:::blue
CLIENT -->|1. GET Agent Card 发现能力| SERVER[服务端 Server
黑盒远程 Agent]:::orange
CLIENT -->|2. tasks/send 提交 Task| SERVER
SERVER -.->|3. 状态更新 / 流式推送| CLIENT
SERVER -.->|4. 返回 Artifact 结果| CLIENT
二、A2A 协议与 MCP 协议的区别
这是面试与实践中最常被问到的问题。最简单的理解方式是看方向:MCP 是 Agent 向下连工具,A2A 是 Agent 向外连其他 Agent。两者解决的是完全不同维度的问题,不存在谁替代谁。
2.1 一横一纵:直观示意
flowchart TB
classDef blue fill:#EAF4FF,stroke:#4C8FD9,stroke-width:1.5px,color:#123B66
classDef green fill:#EAF8EF,stroke:#5B9B6B,stroke-width:1.5px,color:#1F4D2E
classDef orange fill:#FFF4E8,stroke:#D79A4A,stroke-width:1.5px,color:#7A4B12
linkStyle default stroke:#90A4AE,stroke-width:1.2px
A1[市场分析 Agent]:::green
A2[调度 Agent]:::blue
A3[技术研究 Agent]:::green
A2 -.->|A2A 横向协作| A1
A2 -.->|A2A 横向协作| A3
A1 -->|MCP 纵向连工具| T1[(搜索引擎)]:::orange
A3 -->|MCP 纵向连工具| T2[(代码执行器)]:::orange
A3 -->|MCP 纵向连工具| T3[(数据库)]:::orange
协作关系:在一个 Agent 内部用 MCP 连各种工具(数据库、浏览器、代码执行器),用 Function Calling 触发调用;多个 Agent 之间用 A2A 互相通信、分工。复杂系统里两者通常同时使用。
2.2 逐项对比
| 特性 | A2A | MCP |
|---|---|---|
| 主要用途 | Agent 间通信与协作 | 为模型提供工具和上下文,连接外部资源 |
| 核心架构 | 客户端-服务器(Agent-Agent) | 客户端-主机-服务器(应用-LLM-外部资源) |
| 标准接口 | Agent Card、Task、Message、Artifact | Resource、Tool、Memory、Prompt |
| 通信协议 | HTTP + JSON-RPC 2.0 + SSE | JSON-RPC 2.0(stdio / HTTP+SSE) |
| 关键特性 | 多模态、动态协作、能力发现、任务管理、安全 | 模块化、安全边界、可复用连接器、SDK、工具发现 |
| 性能重点 | 异步通信、处理长任务负载 | 高效上下文管理、并行处理、缓存提升吞吐 |
| 连接方向 | 横向:Agent ↔ Agent | 纵向:Agent ↓ 工具/资源 |
三、A2A 协议原理与流程
A2A 建立在 JSON-RPC 2.0 之上,定义了一套围绕 Task(任务) 展开的通信机制。本节先拆解核心概念,再看状态机与完整交互流程。
3.1 核心概念全景
| 概念 | 说明 | 载体形式 |
|---|---|---|
| Agent Card | Agent 的「名片」,JSON 文件,声明名称、能力、Skill 列表、鉴权与支持的模态。托管在 /.well-known/agent-card.json |
JSON 文件 |
| Task | 协作的基本单位,有状态的实体。由客户端创建,状态由远程 Agent 决定,可归属同一 sessionId | 有状态对象 |
| Message | 承载非工件内容:思考、指令、用户上下文、状态更新等。role 为 user 或 agent | 消息对象 |
| Artifact | Agent 作为任务最终结果生成的输出,不可变,可命名,可含多个 Part,可流式追加 | 结果对象 |
| Part | Message/Artifact 的最小内容单元:TextPart、FilePart、DataPart | 内容片段 |
上表是概念速览。下面按最新 A2A 协议数据模型逐一给出四大核心对象的字段级定义(字段名/类型取自官方 a2a-js SDK 类型定义,与协议规范一致):
① Task(任务)——有状态的工作单元,判别字段 kind 恒为 "task":
| 字段 | 类型 | 必填 | 含义 |
|---|---|---|---|
id |
string |
是 | 任务唯一标识,新任务由服务端生成 |
contextId |
string |
是 | 服务端生成的上下文 ID,跨多个相关任务维持上下文 |
status |
TaskStatus |
是 | 当前状态,含 state / 可选 message / 可选 timestamp |
kind |
"task" |
是 | 对象类型判别式,恒为 task |
history |
Message[] |
否 | 任务过程中交换的消息数组(对话历史) |
artifacts |
Artifact[] |
否 | 执行期间生成的产物集合 |
metadata |
{[k]:unknown} |
否 | 扩展用元数据,键为扩展特定标识符 |
② Message(消息)——一次通信轮次,承载指令 / 思考 / 状态说明等非工件内容,判别字段 kind 恒为 "message":
| 字段 | 类型 | 必填 | 含义 |
|---|---|---|---|
role |
"user"|"agent" |
是 | 发送方:user 为客户端,agent 为服务方 |
parts |
Part[] |
是 | 消息体的内容片段数组,可混合多种类型 |
messageId |
string |
是 | 消息唯一标识,通常为发送方生成的 UUID |
kind |
"message" |
是 | 对象类型判别式,恒为 message |
taskId |
string |
否 | 所属任务 ID;新任务的首条消息可省略 |
contextId |
string |
否 | 上下文 ID,用于分组相关交互 |
referenceTaskIds |
string[] |
否 | 引用的其他任务 ID,提供附加上下文 |
extensions |
string[] |
否 | 与本消息相关的扩展 URI 列表 |
③ Part(内容片段)——Message / Artifact 的最小内容单元,是一个由 kind 判别的联合类型 TextPart | FilePart | DataPart:
| 类型 | kind | 关键字段 | 含义 |
|---|---|---|---|
| TextPart | "text" |
text: string |
纯文本内容 |
| FilePart | "file" |
file: FileWithBytes | FileWithUri |
文件,以内联 base64 字节或 URI 二选一提供 |
| DataPart | "data" |
data: {[k]:unknown} |
结构化数据(如 JSON) |
其中文件二选一:FileWithBytes(bytes 为 base64 字符串 + 可选 mimeType / name)或 FileWithUri(uri 指向文件 URL + 可选 mimeType / name),凭是否含 bytes / uri 区分,本身不带 kind。
④ Artifact(工件)——Agent 作为任务结果产出的输出,不可变、可命名、可流式追加;注意它没有 kind 判别字段:
| 字段 | 类型 | 必填 | 含义 |
|---|---|---|---|
artifactId |
string |
是 | 产物在任务范围内的唯一标识 |
parts |
Part[] |
是 | 构成该产物的内容片段数组 |
name |
string |
否 | 产物的可读名称 |
description |
string |
否 | 产物的可读描述 |
extensions |
string[] |
否 | 与该产物相关的扩展 URI 列表 |
metadata |
{[k]:unknown} |
否 | 扩展用元数据 |
四者关系:
Task是有状态容器;执行中的每次交流是一条Message,最终产出沉淀为Artifact;而Message和Artifact的内容都由若干Part(文本 / 文件 / 数据)拼装。字段定义参考官方 SDK a2aproject/a2a-js。
下图以类图形式直观呈现四者的包含与组合关系:Task 聚合 Message 与 Artifact,二者的内容再由 Part 拼装,而 Part 又派生出三种具体片段类型。
classDiagram
class Task {
+string id
+string contextId
+TaskStatus status
+kind = "task"
+Message[] history
+Artifact[] artifacts
}
class Message {
+role user|agent
+Part[] parts
+string messageId
+kind = "message"
}
class Artifact {
+string artifactId
+Part[] parts
+string name
}
class Part {
+kind text|file|data
}
class TextPart {
+text string
}
class FilePart {
+file FileWithBytes|FileWithUri
}
class DataPart {
+data object
}
Task "1" o-- "*" Message : history
Task "1" o-- "*" Artifact : artifacts
Message "1" *-- "1..*" Part : parts
Artifact "1" *-- "1..*" Part : parts
Part <|-- TextPart
Part <|-- FilePart
Part <|-- DataPart
3.2 Agent Card:能力发现的基石
每个 A2A Agent 都在 /.well-known/agent-card.json 发布一张名片。任何想协作的 Agent 先 HTTP GET 拿到它,再决定是否委托任务。这让整个系统可插拔:新加 Agent 只需发布 Card,调度 Agent 无需改代码即可发现并使用它。
mindmap
root((Agent Card))
基本信息
name 名称
description 描述
url 地址
version 版本
provider 提供方
capabilities 能力
streaming SSE 流式
pushNotifications 异步回调
stateTransitionHistory 状态历史
authentication 鉴权
schemes 认证方式
credentials 凭证
defaultModes 默认模态
inputModes 输入类型
outputModes 输出类型
skills 技能列表
id 唯一标识
name 技能名
description 描述
tags 标签
examples 示例
Skill 是最关键的字段。每个 Skill 描述一类能力(如「竞品分析」「行业趋势分析」)并带示例输入。调度 Agent 正是用这些 Skill 描述做任务路由决策:「这个任务和哪个 Agent 的哪个 Skill 最匹配?」
3.3 Task 生命周期状态机
Task 是 A2A 的「一等公民」。它被设计为支持长时间任务:调度 Agent 提交后可去做别的事,通过轮询或推送通知得知完成。其状态流转如下:
stateDiagram-v2
[*] --> submitted: 客户端创建 Task
submitted --> working: Agent 开始执行
submitted --> rejected: Agent 拒收任务
working --> input_required: 需要更多信息
input_required --> working: 客户端补充输入
working --> auth_required: 需要鉴权
auth_required --> working: 鉴权通过
working --> completed: 执行成功
working --> failed: 执行出错
working --> canceled: 客户端取消
working --> rejected: 判定无法/不愿继续
completed --> [*]
failed --> [*]
canceled --> [*]
rejected --> [*]
| 状态 | 含义 |
|---|---|
submitted |
已提交,等待处理 |
working |
正在执行中 |
input-required |
需要客户端补充更多信息才能继续(中断态,补充后回到 working) |
auth-required |
需要完成鉴权才能继续(中断态,鉴权通过后回到 working) |
completed |
已完成,可取 Artifact 结果(终态) |
canceled |
被客户端取消(终态) |
failed |
执行失败(终态) |
rejected |
Agent 拒绝执行:创建时拒收,或执行中判定无法/不愿继续(终态) |
unknown |
未知状态 |
3.4 通信机制与异步支持
A2A 支持三种通信模式,核心价值在于异步——让客户端在处理长任务时不必阻塞等待:
| 机制 | 工作方式 | 适用场景 |
|---|---|---|
| 请求/响应 | 标准 JSON-RPC,同步返回(tasks/send) | 秒级快速任务 |
| SSE 流式 | tasks/sendSubscribe,服务端持续推送状态与工件 | 需实时看进度的任务 |
| Push Notification | 客户端注册 webhook,任务完成后 Agent 主动回调 | 数分钟以上的长任务 |
A2A 通信机制:三种可选传输协议绑定。上表描述的是「交互时序」(同步 / 流式 / 回调);而在传输层,最新 A2A 协议不再绑定单一协议,而是定义了三种功能对等的传输绑定,Agent 至少实现其一,并在 Agent Card 的 supportedInterfaces 中声明,客户端据此协商选择:
| 传输绑定 | 形态 | 特点与适用 |
|---|---|---|
| JSON-RPC 2.0 (over HTTP) |
POST + JSON-RPC 请求体,流式用 SSE | 最通用、生态默认;a2a-js SDK 默认走此绑定,本文档示例均基于它 |
| gRPC | Protocol Buffers + HTTP/2 双向流 | 强类型、高性能、原生流式;适合内网高吞吐 Agent 间调用 |
| HTTP+JSON / REST | RESTful URL 路径 + JSON,流式用 SSE | 贴近传统 REST 习惯,易于用通用 HTTP 工具集成 |
功能对等原则:三种绑定在方法语义、错误码、数据模型上必须功能等价——同一个操作(如 sendMessage / getTask / cancelTask)在任一协议下行为一致,只是「线上表示」不同。这让 A2A 既能兼容 REST 生态,又能在需要时切换到 gRPC 追求性能,而上层业务逻辑无需改动。传输与协商细节见规范。
3.5 完整交互流程(时序图)
下图展示一次典型的流式协作全过程:从发现能力到提交任务、流式推送、返回工件:
sequenceDiagram
autonumber
participant U as 用户 User
participant C as 客户端 Client
participant S as 服务端 Server (Agent)
U->>C: 提出需求
C->>S: GET /.well-known/agent.json
S-->>C: 返回 Agent Card(能力/Skill)
Note over C,S: 客户端据 Skill 判断能否委托
C->>S: tasks/sendSubscribe(创建 Task + 订阅 SSE)
S-->>C: 状态更新 submitted
S-->>C: 状态更新 working(Generating...)
loop 流式产出
S-->>C: TaskArtifactUpdateEvent(工件片段)
end
S-->>C: 状态更新 completed(final=true)
C-->>U: 汇总并呈现结果
3.6 错误处理:标准 JSON-RPC 错误码
| 错误码 | 信息 | 描述 |
|---|---|---|
-32700 |
JSON parse error | 发送了无效 JSON |
-32600 |
Invalid Request | 请求负载校验错误 |
-32601 |
Method not found | 非法方法 |
-32602 |
Invalid params | 无效的方法参数 |
-32603 |
Internal error | 内部 JSON-RPC 错误 |
-32001 |
Task not found | 找不到指定 ID 的任务 |
-32002 |
Task cannot be canceled | 任务无法被取消 |
-32003 |
Push notifications not supported | Agent 不支持推送通知 |
-32004 |
Unsupported operation | 操作不支持 |
-32005 |
Incompatible content types | 内容类型不兼容 |
四、A2A 与微服务架构的对比
如果把每个 Agent 看作一个「会思考的服务」,A2A 几乎照搬了微服务架构的治理思路——把成熟的分布式系统方法论平移到了多 Agent 世界。二者的映射关系一目了然:
flowchart LR
classDef ms fill:#EAF4FF,stroke:#4C8FD9,stroke-width:1.5px,color:#123B66
classDef a2a fill:#F3EAFB,stroke:#9B6BC4,stroke-width:1.5px,color:#4A1F66
linkStyle default stroke:#90A4AE,stroke-width:1.2px
subgraph MS[微服务架构]
direction TB
M1[OpenAPI / Swagger 文档]:::ms
M2[服务注册与发现]:::ms
M3[独立部署的 HTTP 服务]:::ms
M4[REST / gRPC 接口]:::ms
M5[消息队列 / 异步任务]:::ms
M6[OAuth2 / API Key 鉴权]:::ms
end
subgraph A2A[A2A 协议]
direction TB
A1[Agent Card]:::a2a
A2[/.well-known/agent-card.json/]:::a2a
A3[每个独立 Agent]:::a2a
A4[JSON-RPC / gRPC / REST 绑定]:::a2a
A5[有状态 Task + SSE/Push]:::a2a
A6[SecurityScheme 声明]:::a2a
end
M1 -.对应.-> A1
M2 -.对应.-> A2
M3 -.对应.-> A3
M4 -.对应.-> A4
M5 -.对应.-> A5
M6 -.对应.-> A6
4.1 相似点逐项对照
| 维度 | 微服务架构 | A2A 协议 |
|---|---|---|
| 能力描述 | OpenAPI / Swagger 描述接口契约 | Agent Card 声明技能、输入输出模态与鉴权 |
| 服务发现 | 注册中心 / DNS 条目 | 约定路径 /.well-known/agent-card.json |
| 独立性 | 各服务独立部署、独立技术栈 | 各 Agent 独立进程,不绑框架、不绑语言 |
| 通信协议 | REST / gRPC / 消息协议多选 | JSON-RPC / gRPC / HTTP+JSON 三种对等绑定 |
| 异步与长任务 | 消息队列 + 回调 / 轮询 | 有状态 Task 状态机 + SSE 流式 + Push 回调 |
| 标准错误 | 统一 HTTP / gRPC 状态码 | 标准 JSON-RPC 错误码(-32700 等) |
| 安全鉴权 | OAuth2 / API Key / mTLS | SecurityScheme:OAuth2 / APIKey / HTTP / mTLS / OIDC |
| 松耦合 | 调用方只依赖接口契约,不关心实现 | Client 只依赖 Agent Card,Server 内部完全黑盒 |
4.2 相同的架构哲学
本质一致:A2A 与微服务共享同一套「契约先行 + 服务自治 + 标准通信 + 独立演进」的分布式哲学。二者都用一份机器可读的契约(OpenAPI ↔ Agent Card)解决「发现与对接」,用标准协议解决「通信」,用松耦合边界让每个单元独立开发、独立部署、独立扩缩容。可以说 A2A 就是「Agent 时代的微服务」。
关键差异:微服务接口大多是确定性、无状态、秒级的 RPC 调用;而 A2A 面向的是 Agent 之间非确定性、有状态、可长时运行的协作——因此额外引入了 Task 生命周期状态机、流式增量产出(Artifact)、多轮上下文(contextId)等微服务通常不具备的一等公民抽象。
五、A2A Client:发起协作的一方
Client 是代表用户向远程 Agent 发起请求的实体,可以是一个应用、一个调度 Agent 或命令行工具。它的职责可归纳为四步:发现能力 → 提交任务 → 接收流式更新 → 汇总结果。
4.1 Client 的核心职责
| 职责 | 做什么 | 对应方法 |
|---|---|---|
| 能力发现 | GET 远程 Agent 的 Agent Card,判断能否委托 | agentCard() |
| 提交任务 | 构造 Message 与 Task,通过 JSON-RPC 发送 | sendTask() |
| 订阅流式 | 建立 SSE 连接,持续接收状态与工件 | sendTaskSubscribe() |
| 查询/取消 | 轮询任务状态或主动取消 | getTask() / cancelTask() |
4.2 Client 内部结构
flowchart TB
classDef blue fill:#EAF4FF,stroke:#4C8FD9,stroke-width:1.5px,color:#123B66
classDef green fill:#EAF8EF,stroke:#5B9B6B,stroke-width:1.5px,color:#1F4D2E
linkStyle default stroke:#90A4AE,stroke-width:1.2px
APP[上层应用 / 调度器]:::green --> CLI[A2AClient]:::blue
CLI --> M1[_makeHttpRequest
封装 JSON-RPC POST]:::blue
CLI --> M2[_handleJsonResponse
解析同步响应]:::blue
CLI --> M3[_handleStreamingResponse
解析 SSE 事件流]:::blue
CLI --> M4[agentCard 缓存
GET 能力名片]:::blue
M1 --> NET[(远程 A2A Server)]:::green
M3 --> NET
M4 --> NET
4.3 SSE 流式响应的解析
流式是 Client 最关键的能力。服务端以 text/event-stream 持续推送,Client 需按 \n\n 切分事件、剥离 data: 前缀、逐条 JSON 解析,并根据 final 标志决定何时结束。
sequenceDiagram
autonumber
participant App as 上层应用
participant Cli as A2AClient
participant Srv as A2A Server
App->>Cli: sendTaskSubscribe(params)
Cli->>Srv: POST tasks/sendSubscribe
Srv-->>Cli: HTTP 200 text/event-stream
loop 持续读取字节流
Srv-->>Cli: data: {status/artifact}<br><br>
Cli->>Cli: 按 <br><br> 切分 + 去 data: 前缀
Cli->>App: yield 解析后的事件对象
end
Srv-->>Cli: data: {final:true}<br><br>
Cli->>App: 结束迭代
实现要点:Client 用 异步生成器(async generator) 把 SSE 事件逐条
yield给上层,上层用for await消费。Agent Card 一般会本地缓存,避免每次协作都重复拉取。
六、A2A Server:提供能力的一方
Server 是一个不透明(黑盒)的远程 Agent——对外只暴露能力,内部实现完全隐藏。它基于 HTTP 框架(如 Express)搭建,核心是两个端点 + 一个 JSON-RPC 路由 + 一个异步任务处理器。
5.1 Server 的两个 HTTP 端点
| 端点 | 方法 | 作用 |
|---|---|---|
/.well-known/agent-card.json |
GET | 发布 Agent Card,供 Client 发现能力 |
/(basePath) |
POST | 接收所有 JSON-RPC 请求,按 method 路由 |
5.2 Server 内部处理架构
flowchart TB
classDef blue fill:#EAF4FF,stroke:#4C8FD9,stroke-width:1.5px,color:#123B66
classDef green fill:#EAF8EF,stroke:#5B9B6B,stroke-width:1.5px,color:#1F4D2E
classDef orange fill:#FFF4E8,stroke:#D79A4A,stroke-width:1.5px,color:#7A4B12
linkStyle default stroke:#90A4AE,stroke-width:1.2px
REQ[Client 请求]:::green --> EP[endpoint 路由器
解析 JSON-RPC]:::blue
EP -->|tasks/send| H1[handleTaskSend
同步执行]:::blue
EP -->|tasks/sendSubscribe| H2[handleTaskSendSubscribe
SSE 流式]:::blue
EP -->|tasks/get / cancel| H3[查询 / 取消]:::blue
H1 --> GEN[TaskHandler
异步生成器]:::orange
H2 --> GEN
GEN --> MERGE[applyUpdate
不可变状态合并]:::blue
MERGE --> STORE[(TaskStore
内存 / 文件持久化)]:::orange
5.3 核心方法路由
| 方法 | 返回形式 | 说明 |
|---|---|---|
tasks/send |
同步 JSON | 提交任务,一次性返回最终结果 |
tasks/sendSubscribe |
SSE 流 | 提交并订阅,持续推送状态与工件 |
tasks/get |
同步 JSON | 按 ID 查询任务当前状态 |
tasks/cancel |
同步 JSON | 取消指定任务 |
tasks/pushNotification/set|get |
同步 JSON | 配置 / 查询推送回调 |
tasks/resubscribe |
SSE 流 | 断线后重新订阅任务流 |
5.4 异步生成器:TaskHandler 的核心范式
Server 把每个 Agent 的业务逻辑实现为一个异步生成器函数。它接收 TaskContext(任务、用户消息、取消标志、历史),在执行过程中不断 yield 出「状态更新」或「工件片段」,框架负责把这些 yield 转成 SSE 事件推给 Client。
stateDiagram-v2
[*] --> Working: yield state=working
Working --> Streaming: yield Artifact 片段
Streaming --> Streaming: 持续 yield 更多片段
Streaming --> Completed: yield state=completed
Working --> Failed: 抛出异常
Working --> Canceled: isCancelled() 为真
Completed --> [*]
Failed --> [*]
Canceled --> [*]
设计精髓:用异步生成器把「长任务的中间进度」自然表达为一串
yield,框架无需关心业务细节,只需把每次 yield 落库(TaskStore)并转发为 SSE 事件。这让业务逻辑与传输/持久化彻底解耦。
5.5 状态存储:TaskStore
| 实现 | 特点 |
|---|---|
InMemoryTaskStore |
Map 存储,返回副本防止外部篡改;进程重启即丢失,适合开发/测试 |
FileStore |
任务与历史分文件持久化(.a2a-tasks 目录),防目录穿越,适合生产落盘 |
七、A2A 实践:以 LangGraph A2A Demo(TypeScript + Node.js)项目为例
Demo 仓库地址:https://github.com/lugezuishuai/a2a_demo
本节以当前项目 langgraph-a2a-demo 为例,说明如何用 TypeScript、Node.js、LangChain.js、LangGraph 与官方 @a2a-js/sdk 搭建一个可运行、可调试、可验证的 A2A 1.0 Client / Server 闭环。它不是“Client 直接调接口”的薄封装:Client 本身也是一个 LangGraph Agent,会先理解用户意图,再按语义选择本地回应或通过 A2A 委派给 Server Agent。
7.1 项目整体架构:Client Agent 与 Server Agent 的协作
flowchart TB
classDef entry fill:#EAF8EF,stroke:#5B9B6B,stroke-width:1.5px,color:#1F4D2E
classDef client fill:#EAF4FF,stroke:#4C8FD9,stroke-width:1.5px,color:#123B66
classDef proto fill:#FFF4E8,stroke:#D79A4A,stroke-width:1.5px,color:#7A4B12
classDef server fill:#F3EAFB,stroke:#9B6BC4,stroke-width:1.5px,color:#4A1F66
classDef infra fill:#F5F5F5,stroke:#9E9E9E,stroke-width:1.2px,color:#424242
linkStyle default stroke:#90A4AE,stroke-width:1.2px
subgraph CP[Client 进程]
direction TB
CE[client-entry.ts
同步 CLI 入口 · 维持 contextId]:::entry
CAE[client-async-entry.ts
异步 CLI 入口 · 启动 push webhook]:::entry
CA[client-agent.ts · Client Agent
LangGraph + delegate_to_server_agent 工具]:::client
CC[client.ts · A2ADemoClient
sendMessageStream 同步流式]:::proto
CCA[async-client.ts · A2AAsyncDemoClient
sendMessage + push 回调]:::proto
CE --> CA
CAE --> CA
CA -->|委派·同步| CC
CA -->|委派·异步| CCA
end
subgraph SP[Server 进程]
direction TB
SV[server.ts · A2A Server
Express + DefaultRequestHandler + InMemoryTaskStore
capabilities: streaming + pushNotifications]:::proto
EX[langgraph-executor.ts · Executor
Task 生命周期 → LangGraph]:::proto
SA[server-agent.ts · Server Agent
LangGraph StateGraph + MemorySaver]:::server
SV --> EX --> SA
end
CC -.->|1 GET Agent Card 发现能力| SV
CC -.->|2 sendMessageStream JSON-RPC+SSE| SV
SV -.->|3 working / artifact / completed 流式回传| CC
CCA -.->|A sendMessage + taskPushNotificationConfig| SV
SV -.->|B push StreamResponse 回调 /a2a/push| CCA
MF[model-factory.ts
OpenAI / DeepSeek / Claude]:::infra
CFG[config.ts · Zod 环境变量校验]:::infra
CA -.-> MF
SA -.-> MF
CFG -.-> CP
CFG -.-> SP
| 分层 | 核心文件 | 职责 |
|---|---|---|
| 交互入口层 | client-entry.ts / client-async-entry.ts |
读取终端输入、维持多轮 contextId;分别对应同步流式与异步推送两种接入入口 |
| Client Agent 层 | client-agent.ts |
理解意图,决定本地回答或 A2A 委派(同步、异步复用同一 Agent) |
| A2A 协议层 | client.ts / async-client.ts / server.ts / langgraph-executor.ts |
Agent Card 发现、JSON-RPC 传输、同步 SSE 流式与异步 push 回调事件收发 |
| Server Agent 层 | server-agent.ts |
用 LangGraph 执行实际任务并保存会话状态 |
**解耦要点:**业务 Agent 不感知 HTTP / 协议协商 / 事件格式,A2A Client 也不理解模型推理细节;两者通过 Agent Card + JSON-RPC + SSE 三者衔接。
config.ts与model-factory.ts作为横切基础设施,同时服务于 Client 与 Server 两个进程。
7.2 关键代码模块与职责映射
| 文件 | 职责 | 在协作链路中的位置 |
|---|---|---|
src/config.ts |
用 Zod 校验环境变量,统一模型、Key、超时、重试、Server URL、MAX_TOKENS 与 push webhook 地址。 |
运行配置入口 |
src/model-factory.ts |
根据 Provider 创建 ChatOpenAI 或 ChatAnthropic;DeepSeek 默认使用官方兼容地址。 |
模型适配层 |
src/client-entry.ts |
同步入口:支持单次命令与交互循环,通过 sendMessageStream 在同一连接消费 A2A 事件。 |
同步 Client CLI 入口 |
src/client-async-entry.ts |
异步入口:启动本地 push webhook,复用同一 Client Agent,改由 push notification 回填结果。 | 异步 Client CLI 入口 |
src/client-agent.ts |
构建 Client LangGraph;向模型提供 delegate_to_server_agent 工具,同步/异步共用。 |
语义路由与委派决策 |
src/client.ts |
封装 Agent Card 发现、JSON-RPC transport 工厂与事件归一化,并实现 sendMessageStream 同步流式客户端。 |
同步 A2A Client 核心 |
src/async-client.ts |
启动本地 /a2a/push webhook,发送 taskPushNotificationConfig + returnImmediately 请求,并按 token 聚合 push 事件为最终结果。 |
异步 A2A Client 核心 |
src/langchain-stream-helpers.ts |
从 LangChain / LangGraph 流式 chunk 中统一提取可展示文本。 | 流式文本辅助 |
src/a2a-helpers.ts |
构造文本 Part,提取 Message 与 Artifact 中的文本。 | 协议数据辅助 |
src/server-entry.ts |
装配配置、模型、Server Agent、Executor 与 Express Server,并优雅关停。 | Server 进程入口 |
src/server.ts |
构建 Agent Card(声明 streaming 与 pushNotifications 能力),注册健康检查、Agent Card 发现与 JSON-RPC 路由。 | A2A Server 边界 |
src/langgraph-executor.ts |
把 A2A Task / Message 转换为 LangGraph 执行,并产出状态和 Artifact 更新(同步/异步共用同一执行器)。 | 协议与业务适配层 |
src/server-agent.ts |
维护 Server Agent 的 LangGraph 对话和同一 contextId 的会话状态。 |
Server 业务 Agent |
src/doctor.ts |
校验 Node、配置、网络与 Agent Card 可达性,便于本地排障。 | 运行前诊断 |
7.3 Agent Card:从发布到被 Client 消费
Agent Card 在 Server 启动时被构建和发布,在 Client 发起实际 A2A 委派时被消费。buildAgentCard(config) 生成 Agent 名称、技能、能力、输入输出模式和 supportedInterfaces;其中接口 URL 使用 A2A_PUBLIC_URL,确保容器或反向代理部署时对外地址正确。随后 createA2AServer() 用 SDK 常量 AGENT_CARD_PATH 注册发现路由。
1 | |
当前 @a2a-js/sdk 的 AGENT_CARD_PATH 为 .well-known/agent-card.json,所以本地调试的完整地址是 http://127.0.0.1:10000/.well-known/agent-card.json。线上环境应使用 https://<domain>/.well-known/agent-card.json。旧版材料中常见的 /.well-known/agent-card.json 是历史路径;本项目遵循当前 A2A 1.0 的 agent-card.json 标准。
当 Client Agent 决定调用 delegate_to_server_agent 时,工具会调用 A2ADemoClient.send(),再进入 stream()。其中 ClientFactory.createFromUrl(serverUrl) 会先拉取并解析 Agent Card,再依据 supportedInterfaces 与 preferredTransports: ['JSONRPC'] 选择 JSON-RPC 服务地址;只有完成发现和协议选择后,才调用 sendMessageStream() 发送 Task。
1 | |
这也意味着:如果 Client Agent 把一个问候或澄清问题留在本地回答,则不会触发 Agent Card 发现;只有发生远端委派才会访问该端点。
7.4 一次完整的 A2A 交互流程
以用户输入”用三句话解释 A2A 协议”为例(同步模式),Client Agent 将其判定为实质性请求并调用委派工具。Client 发现 Server 的 Agent Card,选择 JSON-RPC 接口,随后以 sendMessageStream 流式发送消息;Server 在同一连接上创建并维护 Task,将请求交给 LangGraph Server Agent,最终把状态、文本消息或 Artifact 更新以 SSE 逐条回传。异步推送模式的差异见下一节。

sequenceDiagram
autonumber
participant U as 用户
participant CLI as client-entry.ts
participant CA as Client Agent
participant C as A2ADemoClient
participant S as A2A Server
participant E as LangGraph Executor
participant SA as Server Agent
U->>CLI: 输入请求
CLI->>CA: respond(prompt, contextId)
CA->>CA: 判断是否调用 delegate_to_server_agent
CA->>C: send(prompt, remoteContextId)
C->>S: GET /.well-known/agent-card.json
S-->>C: Agent Card(JSONRPC 接口、技能、能力)
C->>S: sendMessageStream(User Message)
S->>E: 执行 A2A Task
E->>SA: invoke / stream LangGraph
SA-->>E: working、文本 / Artifact、completed
E-->>S: Task 状态与流式更新
S-->>C: StreamResponse 序列
C-->>CA: 归一化后的最终结果
CA-->>CLI: 最终回答与路由结果
CLI-->>U: [route] Server Agent via A2A
多轮时,client-entry.ts 保留 Client 侧 contextId,而 client-agent.ts 用 Map 保存每个 Client 会话对应的远端 A2A contextId。下一条委派请求会携带该远端上下文,因此 Server Agent 可以延续同一段对话;不同 Client 会话之间不会串线。
7.5 异步推送模式:sendMessage + push notification 回调
除了在同一连接上消费 SSE 的同步流式模式,项目还提供了一条异步推送链路:Client 发出请求后立即拿到 Task 创建响应,Server 在后台执行,完成后通过 push notification 回调 Client 本地的 webhook 回填结果。二者复用同一个 Client Agent、同一个 createJsonRpcClientFactory 与同一个 Server 端 Executor,仅在”如何拿结果”这一层不同。
| 维度 | 同步流式(SSE) | 异步推送(Push Notification) |
|---|---|---|
| 入口 | client-entry.ts · npm run client |
client-async-entry.ts · npm run client:async |
| 核心客户端 | A2ADemoClient(client.ts) |
A2AAsyncDemoClient(async-client.ts) |
| 发送方法 | sendMessageStream,同一连接上持续接收事件 |
sendMessage + returnImmediately: true,立即返回首个 Task |
| 结果获取 | 逐条消费 SSE StreamResponse 直至终态 | Server 回调本地 /a2a/push webhook,按 token 聚合事件 |
| 关键配置 | 无需额外配置 | taskPushNotificationConfig(回调 URL + token)+ A2A_PUSH_* 环境变量 |
| 适用场景 | 实时交互、期望增量输出、连接可长时保持 | 长耗时任务、Client 不宜挂起连接、需后台执行后回调 |
异步入口在启动时用 Express 在本地拉起一个 push webhook(默认 http://127.0.0.1:10001/a2a/push),发送请求时把该地址与一个随机 token 写入 taskPushNotificationConfig,并设置 returnImmediately: true;sendMessage 一拿到首个 Task 就返回,最终结果通过 Server 的 push 回调按 token 匹配回填,到达终态(COMPLETED / FAILED / CANCELED / REJECTED)时才 resolve。这依赖 Server 在 Agent Card 中声明的 pushNotifications: true 能力。
1 | |
sequenceDiagram
autonumber
participant U as 用户
participant CLI as client-async-entry.ts
participant CA as Client Agent
participant AC as A2AAsyncDemoClient
participant WH as 本地 webhook /a2a/push
participant S as A2A Server
participant E as LangGraph Executor
U->>CLI: 输入请求
CLI->>CA: respond(prompt, contextId)
CA->>AC: send(prompt, remoteContextId)
AC->>WH: 启动本地 push webhook
AC->>S: sendMessage(+ taskPushNotificationConfig, returnImmediately)
S-->>AC: 立即返回首个 Task(submitted)
Note over S,E: Server 后台异步执行 Task
S->>E: 执行 A2A Task
E-->>S: working / artifact / completed
S->>WH: push StreamResponse(携带 token)
WH->>AC: 按 token 聚合事件
AC-->>CA: 终态后 resolve 最终结果
CA-->>CLI: 最终回答与路由结果
CLI-->>U: [route] Server Agent via A2A push
7.6 配置、运行、调试与验证
所有可变信息都由环境变量注入,避免将模型名称或密钥写入源码。先执行 npm run env:init 由 .env.example 初始化 .env;如 .env 已存在,原内容不会被覆盖。
| 配置类别 | 关键环境变量 | 说明 |
|---|---|---|
| 模型与鉴权 | MODEL_PROVIDER、MODEL、API_KEY |
支持 openai/gpt、deepseek、anthropic/claude;也可使用 Provider 专属 Key。 |
| 模型行为 | BASE_URL、MODEL_TEMPERATURE、MAX_TOKENS、MODEL_TIMEOUT_MS、MODEL_MAX_RETRIES |
MAX_TOKENS 为可选正整数;为空时由模型 Provider 使用默认值。 |
| DeepSeek | MODEL_PROVIDER=deepseek、MODEL=deepseek-v4-flash |
默认使用 https://api.deepseek.com;仅 deepseek-v4-flash 显式设置 useResponsesApi: true,其他 DeepSeek 模型走 Chat Completions。 |
| A2A 地址 | SERVER_HOST、SERVER_PORT、A2A_PUBLIC_URL、A2A_SERVER_URL |
A2A_PUBLIC_URL 写入 Agent Card;Client 通过 A2A_SERVER_URL 或 --url 定位远端服务。 |
| 异步推送 | A2A_PUSH_HOST、A2A_PUSH_PORT、A2A_PUSH_PUBLIC_URL、A2A_PUSH_TIMEOUT_MS |
仅异步模式使用;指定本地 push webhook 的监听地址、对 Server 可达的回调地址与等待超时(默认 120000ms)。 |
1 | |
Trae 可直接启动 Compound 配置 A2A: Debug Client + Server,它会同时启动 Server 与交互式 Client,并在停止时一并结束。建议在 src/client.ts 的 createFromUrl(this.serverUrl) 处设置断点观察 Agent Card 的消费;在 src/server.ts 的 Agent Card 路由处设置断点观察发现请求;再在 src/client-agent.ts 的委派工具和 src/langgraph-executor.ts 中查看语义路由、Task 执行与流式产出。
**验证标准:**集成测试会在随机本地端口启动真实 Express Server,依次读取 /.well-known/agent-card.json、发送 JSON-RPC 消息、消费 task / status / artifact 流式事件,并断言最终状态为 TASK_STATE_COMPLETED。测试使用确定性的 LangChain fake model,因此不需要真实 API Key。