A2A 协议:Agent 间通信的开放标准
适用读者:需要构建多 Agent 系统、跨服务调用 Agent 的开发者 前置知识:理解 Agent 管道、IAgent 接口、Hosting 层基本用法 本文目标:从报文字段到底层数据流,讲透 A2A 协议的设计哲学、消息格式和常见误区
概述
什么是 A2A?
A2A(Agent-to-Agent)是由 Google 发起、Microsoft Agent Framework 原生实现的开放 Agent 间通信协议。它的目标是为不同框架、不同语言、不同运行时上的 Agent 提供一套通用的"互操作语言"。
想象一下:你有一个用 MAF(C#)写的客服 Agent,想调用另一个用 Python 写的订单查询 Agent——没有 A2A 的话,你需要手写 REST 客户端、定义自定义 schema、处理错误重试、管理任务生命周期。有了 A2A,两个 Agent 只需各自实现一个 A2AService 端点,就能像调用本地方法一样相互通信。
A2A 与 MCP 有何不同?
这是开发者最常混淆的点。两者的定位完全不同:
| 维度 | A2A | MCP(Model Context Protocol) |
|---|---|---|
| 通信双方 | Agent ↔ Agent | LLM ↔ 工具/数据源 |
| 抽象层级 | 任务编排层 | 工具调用层 |
| 调用模式 | 异步任务 + 长期运行 | 同步请求-响应 |
| 状态管理 | Task 生命周期 + 上下文隔离 | 无状态工具调用 |
| 典型场景 | 多 Agent 协作、Orchestrator-Worker | 数据库查询、文件读写、API 调用 |
一句话总结:MCP 让 LLM 能调用工具,A2A 让 Agent 能找其他 Agent 干活。
A2A 协议栈总览
A2A 定义了一个完整的通信栈,从上到下:
┌───────────────────────────────────────────────────┐
│ Agent Card │ ← 发现/注册
│ 名字 · 描述 · 技能列表 · 能力声明 · 安全需求 │
├───────────────────────────────────────────────────┤
│ 任务层 Task │ ← 编排
│ SendMessage · GetTask · CancelTask · Subscribe │
│ 状态机: SUBMITTED → WORKING → COMPLETED/FAILED │
├───────────────────────────────────────────────────┤
│ 消息层 Message │ ← 通信
│ Message + Part:文本 · 文件 · 结构化数据 · 引用 │
│ context_id · task_id · role · metadata │
├───────────────────────────────────────────────────┤
│ 传输层 Transport │ ← 传输
│ HTTP+JSON · gRPC · JSON-RPC · Server-Sent Events │
└───────────────────────────────────────────────────┘核心概念
1. Agent Card:Agent 的名片
每个 A2A Agent 都必须暴露一个 AgentCard,它是 Agent 的自我声明。客户端通过读取 AgentCard 来了解这个 Agent 能做什么、怎么联系它。
{
"name": "订单查询助手",
"description": "查询用户订单状态、物流追踪信息",
"version": "1.0.0",
"provider": {
"organization": "比特矩阵",
"url": "https://www.bytemetrix.com"
},
"documentation_url": "https://docs.bytemetrix.com/order-agent",
"icon_url": "https://cdn.bytemetrix.com/agent-icons/order-agent.png",
"supported_interfaces": [
{
"url": "https://api.bytemetrix.com/a2a/order-agent",
"protocol_binding": "HTTP+JSON",
"protocol_version": "1.0"
}
],
"capabilities": {
"streaming": true,
"push_notifications": true
},
"skills": [
{
"id": "order_query",
"name": "订单查询",
"description": "根据订单号查询订单状态、物流信息",
"tags": ["订单", "物流", "查询"],
"examples": ["帮我查一下订单 ORD-2024-0001 的状态"]
},
{
"id": "order_return",
"name": "退货处理",
"description": "处理退货申请、查询退货进度",
"tags": ["退货", "售后"],
"examples": ["我要退货,订单号是 ORD-2024-0001"]
}
],
"default_input_modes": ["text/plain"],
"default_output_modes": ["text/plain", "application/json"]
}关键字段:
supported_interfaces:该 Agent 支持的所有通信端点。可以同时暴露 HTTP+JSON 和 gRPC 接口,第一个为首选。capabilities:能力声明——是否支持流式、推送通知、扩展 Agent Card 等。skills:技能列表。这是 Agent 的"能力目录",客户端可以通过技能描述决定是否调用此 Agent。security_schemes/security_requirements:安全认证需求(可选)。
⚠️ 重要:
AgentCard是静态声明。Agent 说自己能做某件事(skill)并不代表一定能成功——服务器可能在运行时拒绝任务(REJECTED状态)。
MAF 中如何暴露 AgentCard
在 MAF 中,当你调用 AddA2AServer() 和 MapA2A() 后,AgentCard 自动基于注册的 Agent 生成:
// AgentCard 由 MAF 自动生成,基于 Agent 的 Name 和默认配置
builder.Services.AddSingleton(sp =>
{
var agent = sp.GetRequiredService<PirateAgent>();
return agent; // 自动暴露为 A2A Agent
});如果你需要自定义 AgentCard,可以显式提供:
builder.Services.AddA2AServer(options =>
{
options.AgentCardProvider = async (context, cancellationToken) =>
{
return new AgentCard
{
Name = "支持助手",
Description = "处理客服工单",
Version = "1.0.0",
Skills = new List<AgentSkill>
{
new()
{
Id = "ticket",
Name = "工单处理",
Description = "创建、查询、关闭客服工单",
Tags = { "工单", "客服" }
}
}
};
};
});2. Task:任务——A2A 的核心抽象
在 A2A 协议中,一次交互就是一个 Task。Task 是 A2A 协议中最核心的概念,它封装了:
- 一个唯一的
task_id - 一个可选的
context_id(上下文分组) - 一个状态机(
TaskStatus) - 一组输出产物(
Artifact)
{
"id": "task-001-abc-123",
"context_id": "session-98765",
"status": {
"state": "WORKING",
"timestamp": "2024-06-12T06:30:00Z"
},
"artifacts": [],
"history": [],
"metadata": {
"source": "web",
"priority": "high"
}
}Task 状态机
┌──────────┐
│SUBMITTED │
└────┬─────┘
│
┌────▼─────┐
┌───────┤ WORKING │◄──────────┐
│ └────┬──────┘ │
│ │ │
┌─────▼─────┐ ┌───▼────────┐ ┌────┴──────┐
│ COMPLETED │ │INPUT_REQUIRED│ │AUTH_REQUIRED│
└───────────┘ └───┬────────┘ └────┬──────┘
│ │
│ (继续处理后) │
└──────┬──────────┘
│
┌────▼──────┐
│ WORKING │
└────┬──────┘
│
┌──────────┼──────────┐
│ │ │
┌─────▼──┐ ┌───▼────┐ ┌───▼────┐
│FAILED │ │CANCELED│ │REJECTED│
└────────┘ └────────┘ └────────┘
状态类型:
─────────
终端状态:COMPLETED · FAILED · CANCELED · REJECTED
中断状态:INPUT_REQUIRED · AUTH_REQUIRED(可恢复)
活跃状态:SUBMITTED · WORKING状态类型详解:
| 状态 | 类型 | 含义 |
|---|---|---|
SUBMITTED | 活跃 | 任务已提交,等待处理 |
WORKING | 活跃 | 正在处理中 |
COMPLETED | 终端 ✅ | 成功完成 |
FAILED | 终端 ❌ | 执行出错 |
CANCELED | 终端 🛑 | 被客户端取消 |
REJECTED | 终端 🚫 | Agent 拒接(不匹配技能、策略拒绝) |
INPUT_REQUIRED | 中断 ↩️ | Agent 需要更多输入才能继续 |
AUTH_REQUIRED | 中断 🔐 | 需要认证 |
关键设计:中断状态(
INPUT_REQUIRED/AUTH_REQUIRED)是 A2A 相比传统 REST API 最大的不同。Agent 不是简单地"接受请求→返回响应",它可以在对话中主动要求客户端提供更多信息,然后在同一个 Task 里继续处理。这让 A2A 天然支持多轮对话和人工介入场景。
3. Message 与 Part:通信的原子单位
Message 结构
{
"message_id": "msg-xxx-001",
"context_id": "session-98765",
"task_id": "task-001-abc-123",
"role": "USER",
"parts": [
{
"text": "帮我查一下订单 ORD-2024-0001 的状态"
}
],
"metadata": {
"source": "telegram"
},
"reference_task_ids": ["task-000-old-ref"]
}Part 的四种类型
Part 是消息内容的容器,有四种形式:
// 1. 文本 (text)
{ "text": "你好,有什么可以帮你的?" }
// 2. 文件原始字节 (raw) —— JSON 中为 base64 编码
{
"raw": "iVBORw0KGgo...",
"filename": "screenshot.png",
"media_type": "image/png"
}
// 3. 文件 URL (url)
{
"url": "https://cdn.example.com/report.pdf",
"filename": "report.pdf",
"media_type": "application/pdf"
}
// 4. 结构化数据 (data) —— 任意 JSON 值
{
"data": {
"order_id": "ORD-2024-0001",
"status": "shipped",
"estimated_delivery": "2024-06-15"
},
"media_type": "application/json"
}4. Artifact:任务输出产物
当 Agent 完成一个任务,结果以 Artifact 形式返回:
{
"artifact_id": "art-001",
"name": "订单查询结果",
"description": "用户请求的订单状态查询结果",
"parts": [
{
"text": "订单 ORD-2024-0001 当前状态:已发货"
},
{
"data": {
"order_id": "ORD-2024-0001",
"status": "shipped",
"items": ["机械键盘", "鼠标垫"],
"logistics": {
"carrier": "顺丰速运",
"tracking_number": "SF1234567890"
}
},
"media_type": "application/json"
}
]
}协议方法详解
A2A 协议共定义了 8 个 RPC 方法,分为三组:
消息方法
SendMessage:发送消息(核心方法)
这是最常用的方法。发送一条消息给 Agent,Agent 返回一个 Task。
请求:
POST /message:send
Authorization: Bearer <token>
{
"message": {
"message_id": "msg-001",
"context_id": "ctx-123",
"role": "USER",
"parts": [
{
"text": "我要退货,订单号 ORD-2024-0001"
}
]
},
"configuration": {
"accepted_output_modes": ["text/plain", "application/json"],
"return_immediately": false,
"history_length": 20
}
}关键参数:
return_immediately:false(默认)则阻塞等待直到 Task 进入终端或中断状态;true则立即返回 Task 引用(配合GetTask/SubscribeToTask异步查询结果)。history_length:控制返回的对话历史数量。0表示不要历史,不设置则不限制。task_push_notification_config:可选的推送通知配置,让 Agent 在 Task 状态变化时主动通知客户端。
响应(return_immediately=false,等待完成):
{
"task": {
"id": "task-001-abc-123",
"context_id": "ctx-123",
"status": {
"state": "COMPLETED",
"timestamp": "2024-06-12T06:30:05Z"
},
"artifacts": [
{
"artifact_id": "art-001",
"name": "退货申请结果",
"parts": [
{
"text": "退货申请已提交,退货编号:RMA-2024-0001"
}
]
}
],
"history": [
{
"message_id": "msg-001",
"role": "USER",
"parts": [{ "text": "我要退货,订单号 ORD-2024-0001" }]
},
{
"message_id": "msg-002",
"role": "AGENT",
"parts": [{ "text": "退货申请已提交,退货编号:RMA-2024-0001" }]
}
]
}
}响应(return_immediately=true,立即返回):
{
"task": {
"id": "task-001-abc-123",
"context_id": "ctx-123",
"status": {
"state": "SUBMITTED",
"timestamp": "2024-06-12T06:30:00Z"
},
"artifacts": []
}
}SendStreamingMessage:流式消息
适用于需要实时反馈的场景(如打字机效果、进度条)。请求体与 SendMessage 相同,但响应是 SSE(Server-Sent Events)流。
POST /message:stream
→ 请求体同 SendMessage
← 事件流(SSE):
data: {"type":"taskStatusUpdate","taskId":"task-001","contextId":"ctx-123","status":{"state":"SUBMITTED"}}
data: {"type":"taskStatusUpdate","taskId":"task-001","contextId":"ctx-123","status":{"state":"WORKING"}}
data: {"type":"taskStatusUpdate","taskId":"task-001","contextId":"ctx-123","status":{"state":"WORKING","message":{"role":"AGENT","parts":[{"text":"正在查询您的订单..."}]}}}
data: {"type":"taskArtifactUpdate","taskId":"task-001","contextId":"ctx-123","artifact":{"artifactId":"art-001","parts":[{"text":"退货申请已提交"}]},"append":false,"lastChunk":true}
data: {"type":"taskStatusUpdate","taskId":"task-001","contextId":"ctx-123","status":{"state":"COMPLETED"}}流中包含两类事件:
taskStatusUpdate:状态变化通知taskArtifactUpdate:产物更新通知(append=true表示增量追加,lastChunk=true表示最终块)
任务管理方法
GetTask:查询任务状态
GET /tasks/task-001-abc-123
→ 响应:完整 Task 对象(当前状态 + artifacts + history)ListTasks:列出任务
GET /tasks?page_size=20&page_token=xxx
→ 响应:
{
"tasks": [...],
"next_page_token": "yyy"
}CancelTask:取消任务
POST /tasks/task-001-abc-123:cancel
→ 响应:已取消的 Task(state="CANCELED")SubscribeToTask:订阅任务更新
GET /tasks/task-001-abc-123:subscribe
→ SSE 流:持续推送 taskStatusUpdate 和 taskArtifactUpdate,直到任务进入终端状态发现方法
GetExtendedAgentCard:获取 Agent Card
GET /extendedAgentCard
→ 响应:AgentCard 对象(同上面 AgentCard 示例)扩展版 Agent Card 通常需要认证后才能获取,提供比公开信息更详细的能力声明。
⚠️ 上下文共享:最常见的误解
这是 A2A 协议中最容易踩坑的地方,值得专门解释。
误解 1:"A2A Agent 之间共享对话上下文"
❌ 错误理解:Agent A 向 Agent B 发了消息,Agent B 能"看到"Agent A 的所有对话历史。
✅ 事实:A2A 不自动共享上下文。Agent B 只知道自己 Task 范围内的消息。context_id 只是一个关联标识符,不是"上下文传输通道"。
❌ 错误模型:
Agent A 的完整对话 ──────────────→ Agent B
✅ 实际模型:
Agent A ──(SendMessage, 携带选定的消息)──→ Agent B
Agent B 只知道这个 Task 的消息误解 2:"context_id 等于会话共享"
❌ 错误理解:如果 Agent A 和 Agent B 使用相同的 context_id,它们就共享同一个会话。
✅ 事实:context_id 只是一个用于关联 Task 的逻辑分组键。它让客户端能说"这堆消息属于同一个用户的同一通对话",但它不负责数据同步。
两个 Task,同一个 context_id:
Task-1 (context_id="user-001")
Task-2 (context_id="user-001")
但 Task-2 不会"继承"Task-1 的消息历史,
除非客户端显式在 SendMessage 中设置 reference_task_ids误解 3:"A2A 会自动传整个历史"
❌ 错误理解:每次 SendMessage 都会自动把之前的对话历史一起发过去。
✅ 事实:history 是服务器返回的(由 history_length 控制),不是客户端发送的。客户端每次 SendMessage 只发送当前消息——如果想要历史,必须:
- 客户端自己维护历史
- 在单次请求中用
reference_task_ids告知服务器去关联之前的 Task - 或者完全由客户端在
message.parts中显式拼接上下文
正确的上下文共享做法
方案 A:客户端拼接上下文(推荐)
{
"message": {
"message_id": "msg-003",
"context_id": "ctx-123",
"role": "USER",
"parts": [
{
"text": "根据之前我们讨论的订单信息(上一步的结果:退货单已创建),请帮我联系物流方修改地址。"
}
],
"reference_task_ids": ["task-001", "task-002"]
}
}这里 reference_task_ids 告诉服务器"这个请求引用了之前 Task 的上下文",但服务器是否使用这些引用取决于 Agent 的实现。你不能假设服务器会自动合并历史。
方案 B:客户端显式携带历史
{
"message": {
"message_id": "msg-003",
"context_id": "ctx-123",
"role": "USER",
"parts": [
{ "text": "之前的对话总结:" },
{ "text": "1. 用户查询订单 ORD-2024-0001 状态 → 已发货" },
{ "text": "2. 用户要求退货 → 退货单已创建 RMA-2024-0001" },
{ "text": "---" },
{ "text": "现在请帮我修改退货地址" }
]
}
}方案 C:利用 MAF 的 AgentSession(MAF 特有,非 A2A 标准)
在 MAF 中,如果你在同进程中用 A2AAgent 调用远程 Agent,MAF 会通过 AgentSession 维护会话上下文。但这不是 A2A 协议层面的行为,是 MAF 框架帮你做的——客户端(MAF)在幕后管理上下文串联。
总结:什么共享,什么不共享
| 概念 | 是否跨 Task 共享 | 说明 |
|---|---|---|
context_id | ✅ 联想标识 | 只是键,不传数据 |
| 消息历史 | ❌ 不自动共享 | 需客户端管理或 reference_task_ids |
reference_task_ids | ✅️ 引用声明 | 通知"有关联",具体如何合并由 Agent 决定 |
| Agent 内部状态 | ❌ 绝对不共享 | 每个 Agent 实例独立 |
| AgentSession(MAF) | 🔶 MAF 框架层 | 同进程内管理,跨 A2A 网络边界无效 |
MAF 中的 A2A 实现
A2AAgent 类
在 MAF 中,A2AAgent 是对远程 Agent 的本地代理。你不需要手动构造 HTTP 请求——A2AAgent 负责 A2A 协议的客户端实现。
// 1. 创建 A2A Agent 客户端(连接远程 Agent)
var a2aAgent = new A2AAgent(
new Uri("https://order-service.example.com/a2a"),
httpClient);
// 2. 像用本地 Agent 一样发消息
var response = await a2aAgent.RunAsync(
"帮我查订单 ORD-2024-0001",
cancellationToken: ct);与 ChatClientAgent 的关键区别:
| 特性 | ChatClientAgent | A2AAgent |
|---|---|---|
| 执行位置 | 本地(本进程) | 远程(通过 HTTP/gRPC) |
| IChatClient | ✅ 需要本地配置 | ❌ 不需要(远程管理) |
| 中间件支持 | 完整三层管道 | 仅代理级中间件 |
| 调用模式 | 进程内同步调用 | 基于 Task 的异步编排 |
| 多轮对话 | 通过 AgentSession | 通过 context_id 关联 |
服务端暴露 Agent
在 MAF 中通过 Hosting 层暴露 A2A 端点极为简单:
// Program.cs - 完整的最小化 A2A 服务端
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.A2A; // NuGet: Microsoft.Agents.AI.Hosting.A2A.AspNetCore
var builder = WebApplication.CreateBuilder(args);
// 1. 定义 Agent
var agent = new ChatClientAgent("support-agent",
sp => sp.GetRequiredService<IChatClient>());
// 2. 注册到 DI
builder.AddAIAgent("support", agent);
// 3. 添加 A2A 服务
builder.Services.AddA2AServer();
var app = builder.Build();
// 4. 映射 A2A 端点
app.MapA2A(agent, path: "/a2a/support");
app.Run();这会自动暴露:
GET /a2a/support/extendedAgentCard → AgentCard
POST /a2a/support/message:send → SendMessage
GET /a2a/support/tasks/{id} → GetTask
POST /a2a/support/tasks/{id}:cancel → CancelTask
GET /a2a/support/tasks → ListTasks
GET /a2a/support/tasks/{id}:subscribe → SubscribeToTaskA2A Agent 中间件
A2A Agent 支持代理级中间件(即 Agent Run Middleware),但不支持聊天级中间件(因为没有本地 IChatClient):
// 为远程 A2A Agent 添加中间件
var a2aAgent = new A2AAgent(orderServiceUrl, httpClient)
.Use(async (context, next) =>
{
Console.WriteLine($"[A2A] 发送消息至远程: {context.Input}");
var result = await next(context);
Console.WriteLine($"[A2A] 收到响应: {result}");
return result;
});完整的多 Agent 协作示例
// 客服 Agent → 订单 Agent → 物流 Agent 的三级编排
public class CustomerServiceAgent : AIAgent
{
private readonly A2AAgent _orderAgent;
private readonly A2AAgent _logisticsAgent;
public CustomerServiceAgent(
IOptions<AgentOptions> options,
IHttpClientFactory httpFactory) : base(options)
{
var http = httpFactory.CreateClient();
_orderAgent = new A2AAgent(
new Uri("https://order.internal/a2a"), http);
_logisticsAgent = new A2AAgent(
new Uri("https://logistics.internal/a2a"), http);
}
public override async Task<AgentResponse> RunAsync(AgentRequest request)
{
// 1. 先查询订单
var orderResult = await _orderAgent.RunAsync(
$"查询订单信息: {request.Input}",
cancellationToken: request.CancellationToken);
// 2. 根据订单结果查询物流
var logisticsResult = await _logisticsAgent.RunAsync(
$"根据以下信息查询物流: {orderResult.Message}",
cancellationToken: request.CancellationToken);
// 3. 合并结果返回
return new AgentResponse
{
Message = $"📋 订单信息:\n{orderResult.Message}\n\n🚚 物流状态:\n{logisticsResult.Message}"
};
}
}完整实战:搭建 A2A 服务端 + 客户端
服务端:暴露 Agent Card 和处理 SendMessage
// 文件: Programs.cs (服务端)
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.A2A;
using Microsoft.Agents.AI.Hosting;
var builder = WebApplication.CreateBuilder(args);
// 配置聊天客户端
builder.Services.AddChatClient(b => b
.UseFunctionInvocation()
.UseOpenAI(apiKey: "sk-xxx", model: "gpt-4o"));
// 定义 Agent
builder.AddAIAgent("weather", sp =>
new ChatClientAgent("weather-agent",
sp.GetRequiredService<IChatClient>()));
// 注册 A2A 服务
builder.Services.AddA2AServer();
var app = builder.Build();
// 暴露 A2A 端点
app.MapA2A("weather", path: "/a2a/weather");
app.Run();客户端:通过 A2AAgent 调用远程 Agent
// 文件: Client.cs
using Microsoft.Agents.AI.A2A;
public class WeatherClient
{
private readonly A2AAgent _agent;
public WeatherClient()
{
_agent = new A2AAgent(
new Uri("http://localhost:5000/a2a/weather"),
new HttpClient());
}
public async Task QueryWeatherAsync(string city)
{
// 同步调用(等待完成)
var result = await _agent.RunAsync(
$"查询 {city} 的天气",
cancellationToken: CancellationToken.None);
Console.WriteLine(result.Message);
// 或者流式调用
await foreach (var chunk in _agent.RunStreamingAsync(
$"查询 {city} 的天气并逐步展示",
cancellationToken: CancellationToken.None))
{
Console.Write(chunk);
}
}
}看看实际的 HTTP 报文
当 A2AAgent.RunAsync() 被调用时,实际发出的 HTTP 请求长这样:
请求报文:
POST /a2a/weather/message:send HTTP/1.1
Host: localhost:5000
Content-Type: application/json
Authorization: Bearer <token>
{
"message": {
"message_id": "msg-8a3f7e21",
"role": "USER",
"parts": [
{
"text": "查询 北京 的天气"
}
]
},
"configuration": {
"return_immediately": false,
"accepted_output_modes": [
"text/plain"
]
}
}响应报文:
HTTP/1.1 200 OK
Content-Type: application/json
{
"task": {
"id": "task-x7k9m2n1",
"status": {
"state": "COMPLETED",
"timestamp": "2024-06-12T06:35:00Z"
},
"artifacts": [
{
"artifact_id": "art-w4r5t6y7",
"parts": [
{
"text": "🌤 北京 当前天气\n\n温度: 28°C\n湿度: 45%\n风向: 南风 3级\n空气质量: 良 (AQI 65)\n\n今天最高温 32°C,最低温 22°C,建议携带防晒用品。"
}
]
}
],
"history": [
{
"message_id": "msg-8a3f7e21",
"role": "USER",
"parts": [{"text": "查询 北京 的天气"}]
},
{
"message_id": "msg-9b4g8f32",
"role": "AGENT",
"parts": [{"text": "🌤 北京 当前天气..."}]
}
]
}
}高级场景
Agent Card 签名(JWS)
为防止被篡改,AgentCard 可以附带 JSON Web Signature:
{
"name": "订单查询助手",
"signatures": [
{
"protected": "eyJhbGciOiJSUzI1NiJ9...",
"signature": "cC9jL25hMER2VzRo...",
"header": {
"kid": "order-agent-key-2024"
}
}
]
}推送通知
对于长期运行的任务(如数据分析、多步骤工作流),客户端可以注册推送通知:
{
"message": {
"message_id": "msg-001",
"role": "USER",
"parts": [{"text": "分析过去一年的销售数据"}]
},
"configuration": {
"return_immediately": true,
"task_push_notification_config": {
"url": "https://my-app.com/a2a/notifications",
"authentication": {
"scheme": "Bearer",
"credentials": "nt-xxx-yyy"
}
}
}
}当 Task 状态变化时,Agent 会主动向 url 发送通知:
POST /a2a/notifications HTTP/1.1
Content-Type: application/json
{
"task_id": "task-001",
"context_id": "ctx-123",
"status": {
"state": "COMPLETED"
}
}多协议的 Interface 绑定
一个 Agent 可以同时暴露多种协议:
{
"supported_interfaces": [
{
"url": "https://api.example.com/a2a/rest",
"protocol_binding": "HTTP+JSON",
"protocol_version": "1.0"
},
{
"url": "https://api.example.com/a2a/grpc",
"protocol_binding": "GRPC",
"protocol_version": "1.0"
}
]
}最佳实践
1. 异步优先
对于任何可能耗时超过几秒的任务,使用 return_immediately=true + SubscribeToTask 或推送通知,而非阻塞等待。
2. 合理的 history_length
{
"configuration": {
"history_length": 10
}
}- 多数情况下 10-20 条消息足够
- 设为
0可节省带宽(客户端自己维护上下文时) - 不设置则服务器可能返回全部历史,产生大量冗余数据
3. 显式声明技能
在 AgentCard 中详细描述 skills,让调用方可以做出路由决策。技能声明越精确,多 Agent 系统的编排效果越好。
4. 安全认证
生产环境必须配置 security_schemes:
{
"security_schemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
},
"security_requirements": [
{
"schemes": {
"bearerAuth": ["read:orders", "write:orders"]
}
}
]
}5. 容错设计
- 调用远程 Agent 时始终设超时
- 处理
REJECTED状态——Agent 可能因为策略拒绝任务 - 处理
INPUT_REQUIRED——Agent 需要更多信息时,客户端应准备好交互式补充 - 检查
FAILED状态的具体错误信息,不要简单重试
性能与安全
性能考虑
| 模式 | 延迟 | 适合场景 |
|---|---|---|
| 同步(return_immediately=false) | 毫秒-秒 | 短查询、即时回答 |
| 异步+订阅 | 秒-分钟 | 数据分析、多步骤工作流 |
| 推送通知 | 秒-小时 | 后台批处理、定时任务 |
| 流式(SSE) | 低延迟 | 打字机效果、进度更新 |
安全层
A2A 协议本身不指定具体的安全机制,但推荐:
- TLS:生产环境必须使用 HTTPS
- 认证:Bearer Token / OAuth2 / API Key
- 授权:基于 skill 的细粒度权限控制
- 签名:AgentCard 可附加 JWS 签名防篡改
与其他协议的关系
┌──────────────────────┐
│ 你的应用 │
└────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼────┐ ┌─────▼─────┐ ┌──────▼─────┐
│ A2A │ │ OpenAI │ │ AG-UI │
│ 多Agent │ │ 兼容端点 │ │ 调试界面 │
│ 互操作 │ │ 单Agent │ │ │
└──────────┘ └───────────┘ └────────────┘- A2A:用于多 Agent 系统中的横向协作(Orchestrator ↔ Worker)
- OpenAI 兼容端点:用于第三方客户端接入(如自定义 UI)
- AG-UI:用于开发和调试(Web 交互界面)
参考资源
- Google A2A Protocol Specification (protobuf) — 完整的协议定义
- A2A Protocol ADRs — 架构决策记录
- MAF Hosting 文档 - A2A 集成 — MAF 中的 A2A 配置详情
- Microsoft Learn - Agent Framework Hosting — 官方 Hosting 文文章