A2A 协议 v1.0 完全指南:方法名、Agent Card、三种绑定与 FastAPI 实战(附 pre-1.0 迁移清单)
摘要:A2A(Agent2Agent)在 2026 年 3 月发布 v1.0.0 后,协议层发生了大量 breaking change------
tasks/send改名、Agent Card 结构重写、Part 统一、状态枚举全大写。本文基于官方 v1.0 规范与迁移文档,系统梳理 A2A 的定位、时间线、核心变更,并给出一个可直接跑的 FastAPI 接入示例,最后附上 12 条 pre-1.0 迁移避坑清单。适合正在做多智能体协作、或准备把现有 Agent 暴露成标准服务的同学收藏。
目录
- [一、A2A 到底是什么:先搞清楚分层](#一、A2A 到底是什么:先搞清楚分层)
- [二、时间线:从 2025-04 发布到 2026-03 v1.0](#二、时间线:从 2025-04 发布到 2026-03 v1.0)
- [三、v1.0 核心变更总览](#三、v1.0 核心变更总览)
- [四、Agent Card:结构与发现路径](#四、Agent Card:结构与发现路径)
- [五、方法名映射:pre-1.0 vs v1.0](#五、方法名映射:pre-1.0 vs v1.0)
- [六、三种协议绑定:JSON-RPC / gRPC / HTTP+JSON](#六、三种协议绑定:JSON-RPC / gRPC / HTTP+JSON)
- [七、FastAPI 实战:把 LangGraph Agent 包成 A2A Server](#七、FastAPI 实战:把 LangGraph Agent 包成 A2A Server)
- [八、12 条 pre-1.0 迁移避坑清单](#八、12 条 pre-1.0 迁移避坑清单)
- 九、FAQ
- 十、总结
一、A2A 到底是什么:先搞清楚分层
A2A(Agent2Agent)是 Agent 与 Agent 之间的水平协作协议,由 Google 于 2025 年 4 月发起,后捐入 Linux Foundation,2026 年并入 AAIF(Agentic AI Foundation),与 MCP 同源治理。
2026 年的 Agent 协议栈基本是三层格局:
| 协议 | 方向 | 类比 | 解决什么问题 |
|---|---|---|---|
| MCP | Agent → Tool(垂直接入) | 给 Agent 装"手" | 工具、数据源、API 的统一接入 |
| A2A | Agent ↔ Agent(水平协作) | 让 Agent 之间说"普通话" | 跨进程、跨厂商的 Agent 互操作 |
| PAP | Personal Agent Protocol | 个人身份/权限层 | 个人 Agent 的身份与授权 |
一句话记住官方在 v1.0 公告里重申的那句:"MCP inside agents, A2A between agents"。两者不冲突,是叠加关系。
很多项目里其实已经有"A2A 雏形"------比如主智能体按领域路由、delegate_task 派发任务、子智能体回收结果。这套机制如果绑定在同一进程、同一套代码里,就是"自研版 A2A";A2A 做的事,是把它标准化、跨进程化,让任何系统里的 Agent 都能用统一协议互相调用。
A2A 的核心价值:消灭 N² 点对点集成。N 个 Agent 两两对接要写 N² 套自定义接口;统一协议后,新增一个 Agent 只需实现 Agent Card + 标准端点。
二、时间线:从 2025-04 发布到 2026-03 v1.0
网上很多文章把这段历史压缩成一句话,导致"2025 年就捐给 AAIF"这类错误流传。准确时间线如下:
| 时间 | 事件 |
|---|---|
| 2025-04-09 | Google 发布 A2A,50+ 初始伙伴 |
| 2025-05 | Microsoft、SAP、Zoom 等加入 |
| 2025-06-23 | 在 Open Source Summit North America 上捐入 Linux Foundation |
| 2025 年下半年 | IBM 的 ACP(Agent Communication Protocol)在 LF AI & Data 伞下并入 A2A 生态 |
| 2025-12 | AAIF(Agentic AI Foundation)成立,MCP、AGENTS.md、goose 首批入驻 |
| 2026-03-12 | A2A v1.0.0 正式发布,首个稳定、生产就绪版本 |
| 2026-04-09 | 一周年:150+ 组织、22K stars、5 语言 SDK GA |
| 2026-08 | A2A 正式并入 AAIF,与 MCP 同源治理 |
所以正确表述是:2025-06 捐入 Linux Foundation,2026-08 并入 AAIF。这两个是不同事件,不要压缩。
三、v1.0 核心变更总览
v1.0 不是小版本迭代,协议层做了大量 breaking change。先看总表,后面逐项展开:
| 变更项 | pre-1.0(v0.3.0) | v1.0 |
|---|---|---|
| 方法名 | tasks/send、message/send |
SendMessage、SendStreamingMessage、GetTask、ListTasks、CancelTask、SubscribeToTask |
| Agent Card 顶层 | 有 protocolVersion、url、preferredTransport |
全部移除,合并为 supportedInterfaces |
| 发现路径 | /.well-known/agent.json |
/.well-known/agent-card.json |
| Task 状态 | "completed"、"input-required" |
TASK_STATE_COMPLETED、TASK_STATE_INPUT_REQUIRED |
| Message role | "user" / "agent" |
ROLE_USER / ROLE_AGENT |
| Part 判别 | 三种类型 + kind 字段 |
单一 Part,按 text/url/raw/data 字段存在判别 |
| 媒体类型字段 | mimeType |
mediaType |
| 流事件判别 | kind: "status-update" + final 字段 |
{"statusUpdate": {...}} 包装对象,终态靠流关闭 |
| ID | 复合 ID(tasks/{id}) |
简单 UUID |
| 错误模型 | RFC 9457 / 自定义 | google.rpc.Status + ErrorInfo(domain: "a2a-protocol.org") |
| OAuth | 支持 implicit / password | 移除;新增 Device Code + PKCE |
| 多租户 | 无 | 请求与 AgentInterface 均带 tenant 字段 |
| REST 绑定前缀 | /v1/... |
去掉 /v1,如 POST /message:send |
四、Agent Card:结构与发现路径
Agent Card 是 Agent 的"能力名片",客户端通过它发现服务、了解能力边界、选择协议绑定。
4.1 发现路径
标准路径是 /.well-known/agent-card.json (RFC 8615 well-known URI),Content-Type 建议 application/a2a+json。
注意:/.well-known/agent.json 是 pre-1.0 的旧路径,v1.0 客户端不会去拉它。
4.2 v1.0 结构示例
json
{
"name": "news-llm-agent",
"description": "新闻舆情分析与智能问答智能体",
"version": "1.0.0",
"supportedInterfaces": [
{
"url": "http://127.0.0.1:8000/a2a",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
},
{
"url": "http://127.0.0.1:8000/rest",
"protocolBinding": "HTTP+JSON",
"protocolVersion": "1.0"
}
],
"capabilities": {
"streaming": true,
"pushNotifications": false,
"extendedAgentCard": false
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
{
"id": "news-analysis",
"name": "新闻舆情分析",
"description": "基于实时新闻库回答舆情问题、生成分析报告",
"tags": ["news", "analysis", "risk"]
}
],
"signatures": []
}
关键点:
- 顶层不再有
protocolVersion、url、preferredTransport、additionalInterfaces,全部收进supportedInterfaces数组; - 每个 interface 声明自己的
url、protocolBinding(JSONRPC/GRPC/HTTP+JSON)、protocolVersion; signatures是可选字段,用于签名 Agent Card(JWS + RFC 8785 规范化),解决"名片是否由声明的域签发",不认证每一次请求。
五、方法名映射:pre-1.0 vs v1.0
v1.0 把核心方法名统一成 PascalCase。如果你还在用 tasks/send,v1.0 客户端会直接返回 Method not found。
| pre-1.0(v0.3.0) | v1.0 | 备注 |
|---|---|---|
message/send |
SendMessage |
明确 Task vs Message 返回语义 |
message/stream |
SendStreamingMessage |
流事件去掉 kind / final 字段 |
tasks/get |
GetTask |
仅返回调用者可见任务 |
tasks/list |
ListTasks |
v1.0 新增,游标分页(pageToken) |
tasks/cancel |
CancelTask |
明确可取消时机与状态迁移 |
tasks/resubscribe |
SubscribeToTask |
SSE 断线重连 |
agent/getAuthenticatedExtendedCard |
GetExtendedAgentCard |
卡片扩展获取 |
| push notification 四件套 | Create/Get/List/DeleteTaskPushNotificationConfig |
模型扁平化 |
版本协商:A2A-Version header
客户端请求应带 A2A-Version: 1.0。服务端支持时路由到 v1.0 handler;不支持则返回 VersionNotSupportedError。缺失或为 0.3 时,默认走 v0.3 兼容路径(如果你的服务同时支持两个版本)。
六、三种协议绑定:JSON-RPC / gRPC / HTTP+JSON
v1.0 正式定义了三种等价绑定,客户端可根据场景选择,服务端在 supportedInterfaces 中如实声明:
| 绑定 | 传输 | 序列化 | 适用场景 |
|---|---|---|---|
| JSON-RPC 2.0 | HTTP POST | JSON | 默认,兼容性最广,调试方便 |
| gRPC | HTTP/2 | Protobuf 3 | 高吞吐、强类型、内部服务间调用 |
| HTTP+JSON(REST) | HTTP 动词 | JSON | RESTful 集成,v1.0 已去掉 /v1 前缀 |
REST 绑定的典型路径(v1.0):
POST /message:sendGET /tasks/{id}POST /tasks/{id}:cancelGET /tasks
建议 :至少实现 JSON-RPC + REST 两种。gRPC 按需补,但一旦在 Agent Card 里声明了 GRPC,就必须真的可连,否则客户端探测会失败。
七、FastAPI 实战:把 LangGraph Agent 包成 A2A Server
假设你有一个 FastAPI + LangGraph 的新闻舆情智能体(全局变量 graph),把它暴露成 A2A v1.0 Server 只需要三件事:Agent Card → JSON-RPC 端点 → 把 Task 映射到 graph.ainvoke。
以下为演示版,生产环境建议直接用官方 Python SDK(
a2a-sdk),已覆盖三种绑定和 0.3→1.0 迁移逻辑。
7.1 服务端代码
python
# a2a_server.py ------ A2A v1.0 兼容演示版
import asyncio
import uuid
from datetime import datetime
from fastapi import FastAPI, Header
from pydantic import BaseModel
from langchain_core.messages import HumanMessage
from app import graph, _msg_text, _strip_manual_cite_list # 复用项目全局图
app = FastAPI()
AGENT_CARD = {
"name": "news-llm-agent",
"description": "新闻舆情分析与智能问答智能体",
"version": "1.0.0",
"supportedInterfaces": [
{
"url": "http://127.0.0.1:8000/a2a",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0",
}
],
"capabilities": {"streaming": True, "pushNotifications": False, "extendedAgentCard": False},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
{
"id": "news-analysis",
"name": "新闻舆情分析",
"description": "基于实时新闻库回答舆情问题、生成分析报告",
"tags": ["news", "analysis", "risk"],
}
],
"signatures": [],
}
# 生产级应落库,这里用内存字典演示
_active_tasks: dict = {}
class A2ARequest(BaseModel):
jsonrpc: str
id: str
method: str
params: dict
@app.get("/.well-known/agent-card.json")
async def agent_card():
return AGENT_CARD
@app.post("/a2a")
async def a2a_endpoint(req: A2ARequest, a2a_version: str = Header("1.0")):
handlers = {
"SendMessage": _send_message,
"GetTask": _get_task,
"CancelTask": _cancel_task,
"ListTasks": _list_tasks,
}
handler = handlers.get(req.method)
if handler is None or a2a_version not in ("1.0",):
return {
"jsonrpc": "2.0",
"id": req.id,
"error": {"code": -32601, "message": "Method not found"},
}
return await handler(req.id, req.params)
async def _send_message(req_id: str, params: dict):
task_id = params.get("id") or f"task-{uuid.uuid4().hex[:8]}"
# v1.0:统一 Part,无 kind 字段;role 为 ROLE_USER
parts = params.get("message", {}).get("parts", [])
prompt = "".join(p.get("text", "") for p in parts if isinstance(p, dict))
thread_id = f"a2a_{task_id}"
async def _run():
config = {"configurable": {"thread_id": thread_id, "web_enabled": True}}
try:
result = await graph.ainvoke(
{"messages": [HumanMessage(content=prompt)]}, config=config
)
content = _strip_manual_cite_list(_msg_text(result["messages"][-1].content))
_active_tasks[task_id] = {
"status": {
"state": "TASK_STATE_COMPLETED",
"timestamp": datetime.utcnow().isoformat(timespec="milliseconds") + "Z",
},
"artifacts": [
{
"name": "answer",
"parts": [{"text": content, "mediaType": "text/plain"}],
}
],
}
except Exception as e:
_active_tasks[task_id] = {
"status": {"state": "TASK_STATE_FAILED"},
"error": {"code": -32000, "message": str(e)},
}
_active_tasks[task_id] = {"status": {"state": "TASK_STATE_WORKING"}}
asyncio.create_task(_run())
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"id": task_id,
"status": {"state": "TASK_STATE_WORKING"},
"metadata": {"thread_id": thread_id},
},
}
async def _get_task(req_id: str, params: dict):
task_id = params.get("id", "")
task = _active_tasks.get(task_id)
if task is None:
return {
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32602, "message": "Task not found"},
}
return {"jsonrpc": "2.0", "id": req_id, "result": {"id": task_id, **task}}
async def _cancel_task(req_id: str, params: dict):
task_id = params.get("id", "")
if task_id in _active_tasks:
_active_tasks[task_id] = {"status": {"state": "TASK_STATE_CANCELED"}}
return {"jsonrpc": "2.0", "id": req_id, "result": {"id": task_id}}
async def _list_tasks(req_id: str, params: dict):
page_size = params.get("pageSize", 20)
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {"tasks": list(_active_tasks.items())[:page_size]},
}
7.2 客户端调用(curl)
bash
curl -X POST http://127.0.0.1:8000/a2a \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "SendMessage",
"params": {
"id": "t1",
"message": {
"role": "ROLE_USER",
"parts": [
{"text": "今天期货相关的重大舆情有哪些?", "mediaType": "text/plain"}
]
}
}
}'
7.3 客户端封装(Python)
python
import requests
def a2a_send(agent_url: str, prompt: str, timeout: int = 60):
body = {
"jsonrpc": "2.0",
"id": "req-001",
"method": "SendMessage",
"params": {
"id": "task-001",
"message": {
"role": "ROLE_USER",
"parts": [{"text": prompt, "mediaType": "text/plain"}],
},
},
}
r = requests.post(
agent_url.rstrip("/") + "/a2a",
json=body,
headers={"A2A-Version": "1.0"},
timeout=timeout,
)
r.raise_for_status()
return r.json()["result"]
核心就一句话 :把 A2A Task 翻译成对 graph.ainvoke 的一次调用。你的 SSE 流式、子智能体追踪、引用清洗、记忆抽取都可以复用,A2A 只是换一个入口。
八、12 条 pre-1.0 迁移避坑清单
如果你手上有基于 v0.3.0 写的 A2A 适配器,按下面清单逐条改:
- 发现路径 :
/.well-known/agent.json→/.well-known/agent-card.json - 方法名 :
tasks/send→SendMessage,tasks/get→GetTask,tasks/cancel→CancelTask,message/stream→SendStreamingMessage - Agent Card 顶层字段 :删掉
protocolVersion、url、preferredTransport、additionalInterfaces,改到supportedInterfaces数组 - Task 状态 :
"completed"→"TASK_STATE_COMPLETED",全大写 + 前缀 - Message role :
"user"→"ROLE_USER","agent"→"ROLE_AGENT" - Part 判别 :删掉
kind字段,改用text/url/raw/data哪个字段存在来判别类型 - 媒体类型 :
mimeType→mediaType - 流事件 :
kind: "status-update"→{"statusUpdate": {...}}包装对象;终态靠流关闭,不再有final字段 - Task ID :复合 ID(
tasks/{id})→ 简单 UUID - 错误模型 :改用
google.rpc.Status+ErrorInfo,reason用 UPPER_SNAKE_CASE,domain固定"a2a-protocol.org" - OAuth :移除 implicit / password flow,新增 Device Code flow(RFC 8628)+ PKCE(
pkceRequired) - REST 绑定 :去掉
/v1前缀,如POST /v1/message:send→POST /message:send
另外两个容易漏的点:
- 版本协商 :客户端要带
A2A-Versionheader,服务端要能识别并返回VersionNotSupportedError; - 多租户 :v1.0 新增
tenant字段,如果你的服务是多租户的,要在请求和AgentInterface里带上。
九、FAQ
Q1:A2A 和 MCP 到底什么关系?会不会重复?
不重复。MCP 是 Agent 接工具/数据的垂直接口,A2A 是 Agent 之间的水平协作协议。一个 Agent 完全可以"内部用 MCP 接工具,对外用 A2A 接其他 Agent",两层叠加,互不冲突。
Q2:必须实现三种绑定吗?
不必须。至少实现 JSON-RPC 2.0 就能跑通大多数场景,REST 绑定建议补上,gRPC 按需。关键是 Agent Card 里声明了什么就必须真的支持什么,否则客户端探测会失败。
Q3:签名 Agent Card 能让我的服务变安全吗?
不能单独靠它。签名 Agent Card 是 MAY(可选) 能力,解决的是"这张名片是否由声明的域签发",不认证每一次 A2A 请求。请求级鉴权(OAuth 2.0 / API Key)仍需单独设计。
Q4:v0.3 的客户端还能调用 v1.0 服务吗?
取决于服务端是否做双版本兼容。v1.0 定义了 A2A-Version header 做协商,服务端可以同时挂 v0.3 和 v1.0 的 handler。如果只实现 v1.0,v0.3 客户端会收到版本不支持错误。
Q5:官方 SDK 选哪个?
官方提供 Python / JS / Java / Go / .NET 五个 GA SDK。FastAPI 项目直接上 Python SDK(a2a-sdk),它已覆盖三种绑定和 0.3→1.0 迁移逻辑,比手写 JSON-RPC 路由省事得多。
十、总结
- A2A 的定位:Agent 之间的"标准普通话",与 MCP 是叠加关系,不是替代关系。
- v1.0 是分水岭:2026-03-12 发布,方法名 PascalCase 化、Agent Card 结构重写、Part 统一、状态枚举全大写、三种绑定正式定义。照着 pre-1.0 的教程写代码,大概率对不上。
- 接入成本不高 :对 FastAPI + LangGraph 项目,加一个 Agent Card 路由 + 一个 JSON-RPC 端点,把 Task 映射到
graph.ainvoke,剩下的全是复用。 - 迁移重点看清单:本文第八节的 12 条,基本覆盖了实际迁移中最容易踩的坑。
如果你正在做多智能体协作,或者准备把现有 Agent 暴露成标准服务,建议先把 Agent Card 和 SendMessage 跑通,再逐步补流式、ListTasks、鉴权和任务持久化。协议本身不复杂,复杂的是版本细节------这也是为什么 v1.0 的迁移文档值得逐条读一遍。
参考来源 :A2A 官方规范站 a2a-protocol.org(v1.0 规范与 what's-new 迁移文档)、a2aproject GitHub 发布公告、Linux Foundation / AAIF 相关公告。代码示例为演示用途,生产环境请使用官方 SDK 并补齐鉴权、持久化与错误处理。