📌 系列简介:「JS全栈AI Agent学习」系统学习 21 个 Agent 设计模式,篇数随学习进度持续更新。
📖 原书地址:adp.xindoo.xyz
前端转 JS 全栈,正在学 AI,理解难免有偏差,欢迎批评指正 ~
🗺️ 系列导航
| 篇 | 主题 |
|---|---|
| 第一篇 | 提示链 · 路由 · 并行化 |
| 第二篇 | 反思 · 工具使用 · 规划 |
| 第三篇 | 多智能体 · 记忆管理 · 学习适应 |
| 第四篇 | MCP:给AI工具世界造一个USB接口 |
| 第五篇 | 目标设定与监控 · 异常处理与恢复 |
| 第六篇 | Human-in-the-Loop 设计 |
| 第七篇 | 深入理解 RAG(检索增强生成)技术 |
| 本篇 | A2A 协议完全指南:理解 Agent 协作体系 |
写在前面
学到多 Agent 系统这里,有一个问题一直没想清楚:
多个 Agent 之间怎么协作?
不是"调用"——调用是单向的,一个 Agent 叫另一个干活。 而是"协作"——两个 Agent 各有分工,互相传递信息,共同完成一件事。
这中间需要一套协议,就像人与人之间需要语言一样。
A2A(Agent to Agent)协议,就是为了解决这个问题而生的。
这篇是学习笔记,记录了从零推导 A2A 协议的完整过程。 不只是"是什么",更重要的是"为什么这样设计"。
目录
- A2A 是什么,解决什么问题
- Agent Card:Agent 的自我介绍
- 通信机制:异步、流式与任务派发
- 任务生命周期
- 调度中心设计:优先级与上下文切换
- 任务去重与缓存
- 事件驱动:Plan A/B/C 动态切换
- 语义鸿沟:误差是如何累积的
- A2A vs MCP:两个协议的分工
- 安全与鉴权:Agent 凭什么信任对方
- 环形依赖死锁:僵局如何打破
- Human-in-the-loop:什么时候该让人介入
- 系统哲学:接受误差,设计反馈
1. A2A 是什么,解决什么问题
从一个场景开始
想象你要规划一次旅行,你告诉一个 AI 助手:"帮我安排下周去三亚的行程。"
这件事,一个 Agent 做不完:
- 要查机票 → 机票 Agent
- 要订酒店 → 酒店 Agent
- 要查天气 → 天气 Agent
- 要整合行程 → 行程规划 Agent
这些 Agent 之间需要协作,需要传递信息,需要知道彼此的能力边界。
A2A(Agent to Agent)协议,就是为了解决这个问题而生的:
让不同的 Agent 能够互相发现、互相理解、互相协作。
A2A 解决的核心问题
| 问题 | A2A 的解法 |
|---|---|
| Agent 之间怎么知道对方能做什么? | Agent Card 自我描述 |
| Agent 之间怎么传递任务? | 标准化任务协议 |
| 任务执行中途怎么同步状态? | 流式响应 + 事件机制 |
| 怎么保证安全,防止伪造? | OAuth 2.0 双向鉴权 |
2. Agent Card:Agent 的自我介绍
你怎么知道一家店能提供什么服务?
你走进一家餐厅,门口有菜单,上面写着:
- 我们提供:川菜、粤菜
- 营业时间:11:00 - 22:00
- 支持:堂食、外卖、预约
Agent Card 就是 Agent 的菜单。
没有这张菜单,Agent 再强大也是孤岛——能力藏在里面,没人知道,也没人能调用。
Agent Card 的标准位置
每个 Agent 都在固定路径暴露自己的 Card:
https://your-agent.com/.well-known/agent.json
这个路径是约定俗成的,就像每个网站的 robots.txt 一样,调用方知道去哪里找。
Agent Card 的内容
{
"name": "机票查询 Agent",
"description": "负责查询国内外航班信息",
"version": "1.0.0",
"capabilities": {
"streaming": true,
"pushNotifications": true
},
"skills": [
{
"id": "search_flight",
"name": "查询航班",
"inputModes": ["text"],
"outputModes": ["text", "structured_data"]
}
],
"authentication": {
"type": "oauth2",
"authorizationUrl": "https://auth.example.com/oauth/authorize"
}
}
3. 通信机制:异步、流式与任务派发
为什么不能用同步请求?
普通的 HTTP 请求是同步的:
你问 → 等待 → 收到答案
但 Agent 的任务往往很长:查航班可能要 30 秒,生成报告可能要 5 分钟。
如果同步等待,连接会超时,用户体验会很差。
这个问题在做简历押题系统的时候就踩过——AI 生成要好几分钟,同步等待根本不现实,后来改成 SSE 流式推送才解决。
A2A 的解法:异步 + 流式
你发任务 → 立刻收到 task_id(不用等结果)
↓
Agent 在后台执行
↓
执行过程中,持续推送进度
↓
完成后,推送最终结果
这就是流式响应(Streaming):不是等全部做完再给你,而是做一点给你一点。
就像你在餐厅点了一桌菜,厨师不会等所有菜都做完再上,而是做好一道上一道。
任务派发的标准结构
{
"id": "task_20240401_001",
"skill": "search_flight",
"input": {
"from": "北京",
"to": "三亚",
"date": "2024-04-08"
},
"callback": "https://scheduler.example.com/callback",
"timeout": 60
}
4. 任务生命周期
一个任务的完整旅程
submitted(已提交)
↓
working(执行中)
↓
├── input-required(需要更多信息)→ 等待输入 → 回到 working
├── paused(暂停)→ 等待恢复 → 回到 working
└── completed(完成)
或 failed(失败)
或 canceled(取消)
每个状态的含义
| 状态 | 含义 | 下一步 |
|---|---|---|
submitted | 任务已收到,排队中 | → working |
working | 正在执行 | → 各种结果状态 |
input-required | Agent 需要更多信息才能继续 | → 等人回答 → working |
paused | 主动暂停,等待外部条件 | → 条件满足 → working |
completed | 成功完成 | 终态 |
failed | 执行失败 | 终态(可重试) |
canceled | 被取消 | 终态 |
为什么需要 input-required 这个状态?
因为 Agent 不是万能的。有时候任务进行到一半,发现信息不够:
"你让我查机票,但你没说几号回程,我查不了往返票。"
这时候 Agent 不应该瞎猜,也不应该直接失败,而是主动暂停,等待补充信息。
知道自己不知道,比假装知道更重要。这是一种负责任的设计。
5. 调度中心设计:优先级与上下文切换
调度中心是什么?
在多 Agent 系统里,通常有一个调度中心(Orchestrator),负责:
- 接收用户任务
- 拆解成子任务
- 分发给各个 Agent
- 汇总结果
就像一个项目经理,自己不写代码,但负责协调所有人。
优先级抢占
当多个任务同时存在,调度中心需要决定谁先执行:
任务队列:
[低优先级] 生成月报 等待中...
[中优先级] 查询本周数据 执行中...
[高优先级] 紧急:客户投诉处理 ← 新来的!
处理:
暂停"查询本周数据"
保存它的执行状态(快照)
先处理"客户投诉"
投诉处理完,恢复"查询本周数据"
上下文快照(Context Snapshot)
暂停一个任务,不是直接丢掉,而是保存现场:
{
"task_id": "task_weekly_data",
"status": "paused",
"snapshot": {
"progress": "已查询3个部门,还剩2个",
"partial_results": { "部门A": 120, "部门B": 98, "部门C": 156 },
"next_step": "查询部门D"
}
}
恢复的时候,从快照继续,不用从头开始。
这个设计和操作系统的进程调度本质上是同一件事:保存现场,切换,恢复现场。 底层的逻辑,跨越了层次,是相通的。
6. 任务去重与缓存
问题:重复任务怎么办?
在分布式系统里,同一个任务可能被发送多次:
- 网络超时,客户端重试
- 用户手抖,点了两次
- 系统故障,任务重放
如果每次都重新执行,会造成:
- 资源浪费
- 结果不一致
- 甚至重复付款、重复发邮件等严重问题
解法一:Singleton Task(单例任务)
同一个任务 ID,只执行一次。
第二次收到相同 ID,直接返回第一次的结果。
// 第一次请求
{ "id": "task_flight_001", "skill": "search_flight", ... }
→ 开始执行
// 第二次请求(相同 ID)
{ "id": "task_flight_001", "skill": "search_flight", ... }
→ 检测到已存在,直接返回第一次的结果
解法二:Result Cache(结果缓存)
对于相同输入的任务,缓存结果:
查询:北京 → 三亚,2024-04-08
第一次:调用航班 API,耗时 3 秒,缓存结果,有效期 10 分钟
第二次:直接返回缓存,耗时 0.01 秒
第三次:直接返回缓存,耗时 0.01 秒
10 分钟后:缓存过期,重新查询
幂等性:设计的基本原则
幂等性:同一个操作,执行一次和执行多次,结果相同。
这是分布式系统设计的黄金原则。
前端开发里也有这个概念——同一个按钮,点一次和点十次,结果应该一样。 从前端到后端到多 Agent 系统,这个原则从来没变过。
7. 事件驱动:Plan A/B/C 动态切换
静态计划的局限
传统的任务执行是线性的:
步骤1 → 步骤2 → 步骤3 → 完成
但现实世界是动态的,计划赶不上变化。
事件驱动的思路
不是"按步骤执行",而是"根据事件响应":
初始计划(Plan A):
查机票 → 订酒店 → 安排接送
事件触发:
机票查询结果 → 目标日期无票!
切换 Plan B:
改签日期 → 重新查机票 → 继续订酒店
事件触发:
改签日期也无票!
切换 Plan C:
改变出发城市 → 查其他机场 → 重新规划
条件触发的实现
{
"task_id": "travel_plan",
"plan": "A",
"on_event": {
"flight_not_found": {
"action": "switch_plan",
"target": "B",
"params": { "adjust": "date", "range": "±3days" }
},
"all_dates_full": {
"action": "switch_plan",
"target": "C",
"params": { "adjust": "departure_city" }
}
}
}
系统的智慧,在于知道什么时候该变——不是死守计划,而是在事件触发时做出正确的响应。
8. 语义鸿沟:误差是如何累积的
一个看似简单的任务
"帮我安排一个舒适的旅行。"
这句话里,有多少个不确定性?
"舒适" → 你的舒适 ≠ 我理解的舒适
商务舱?还是经济舱靠窗?
五星酒店?还是精品民宿?
"旅行" → 几天?几个人?预算多少?
国内还是国外?
"安排" → 只查信息?还是直接预订?
需要我做决定吗?
每一个词,都携带着不确定性。
误差累积的数学
假设每一步理解的准确率是 90%:
1个Agent理解: 90%
2个Agent串联: 90% × 90% = 81%
3个Agent串联: 90% × 90% × 90% = 72.9%
4个Agent串联: 90% × 90% × 90% × 90% = 65.6%
每多一层转发,误差就多累积一次。 这就是语义鸿沟——不是一次大的误解,而是无数次小偏差的叠加。
看到 65.6% 这个数字的时候,停了一下。 四个 Agent 串联,准确率已经不到七成——这不是理论上的担忧,是真实会发生的事。
解法:在每一层做语义对齐
不好的设计:
用户 → Agent A → Agent B → Agent C → 执行
(每层只传递结果,不传递意图)
好的设计:
用户 → Agent A → Agent B → Agent C → 执行
(每层都携带原始意图 + 当前理解 + 置信度)
{
"original_intent": "安排一个舒适的旅行",
"current_interpretation": "预算15000元,3天2夜,商务出行风格",
"confidence": 0.78,
"assumptions": [
"舒适 = 商务级别",
"旅行 = 国内短途",
"安排 = 提供方案,不直接预订"
]
}
当置信度低于阈值,自动触发 Human-in-the-loop 确认。
9. A2A vs MCP:两个协议的分工
一个场景,两个协议
天气 Agent 需要做两件事:
- 调用天气 API 查询数据
- 把结果告诉旅行规划 Agent
这两件事,是不同协议负责的。
分工边界
旅行规划 Agent
│
│ "帮我查三亚明天的天气"
│ ────── A2A ──────────────→
│
天气 Agent 收到任务
│
│ 调用天气API工具
│ ────── MCP ──────────────→ 天气 API
│ │
│ ←───── MCP ──────────────── 返回数据
│
│ "明天晴,28℃,适合出行"
│ ←───── A2A ───────────────
│
旅行规划 Agent 收到结果
| 协议 | 全称 | 管的是 | 关系 | 方向 |
|---|---|---|---|---|
| A2A | Agent to Agent | Agent ↔ Agent 通信 | 对等协作 | 双向 |
| MCP | Model Context Protocol | Agent → 工具调用 | 调用使用 | 单向 |
一句话记住区别
MCP:Agent 的"手" — 伸出去拿东西用的
A2A:Agent 的"嘴" — 和其他 Agent 说话协作的
MCP 是 Anthropic 提出的,专门负责 LLM/Agent 如何调用外部工具。 A2A 是 Google 提出的,专门负责 Agent 之间的协作通信。 两个协议不是竞争关系,而是互补关系。
一开始觉得两个协议有点重叠,后来想清楚了:调用工具和和同伴协作,本来就是两件不同的事,当然要分开设计。各司其职,才能各得其位。
10. 安全与鉴权:Agent 凭什么信任对方
问题:没有鉴权会怎样?
有一个"转账 Agent"
任何人发一条消息:
"帮我把账户里的钱全转走"
它就执行了……
这显然不行。
A2A 的鉴权机制:OAuth 2.0 + JWT
Step 1:Agent Card 声明鉴权方式
"我支持 OAuth 2.0,
你要调用我,先去这个地址拿 Token"
Step 2:调用方获取 Token
调度中心 → Auth Server → 拿到 JWT
JWT 里包含:
who: 我是调度中心
what: 我有权调用机票Agent的查询接口
when: 这个Token 1小时后过期
Step 3:带 Token 发请求
Header: Authorization: Bearer eyJhbGci...
Step 4:被调用方验证
✅ Token 有效 + 有权限 → 执行任务
❌ Token 过期 → 拒绝,要求重新授权
❌ Token 伪造 → 拒绝,告警
A2A 特有的威胁:Agent 身份伪造
普通网站鉴权是人 → 系统,A2A 鉴权是Agent → Agent。
这带来一个新威胁:
恶意 Agent 伪装成"调度中心",发指令给其他 Agent,执行恶意任务。
解法是双向验证:
普通鉴权(单向):调用方证明自己是谁
A2A 鉴权(双向):调用方证明自己是谁 + 验证对方是谁
就像你打电话给银行:
- 银行验证你是不是本人 ✅
- 你也验证接电话的是不是真银行 ✅
- 防钓鱼,防伪装
11. 环形依赖死锁:僵局如何打破
死锁场景
机票 Agent A:
"我要等酒店 Agent B 确认目的地,才能查航班"
等待中...
酒店 Agent B:
"我要等机票 Agent A 确认出发城市,才能查酒店"
等待中...
结果:
A 等 B → B 等 A → A 等 B → B 等 A → ∞
这就像两个吵架的情侣,心里都想着"如果他先开口,我就原谅他",结果谁都没开口,僵局永远持续。
解法一:TTL 超时机制
task = {
"id": "task_001",
"timeout": 30, # 最多等 30 秒
"max_retries": 3, # 最多重试 3 次
"on_timeout": "cancel" # 超时就取消,主动上报
}
关键:超时后必须有明确的退出状态。 不是"继续等",不是"静默失败", 而是主动上报,让调度中心知道,重新规划。
知道在哪里停下来,才能稳定——这是打破僵局的前提。
解法二:依赖图检测(死锁预防)
在任务派发之前,先画出依赖关系:
A → 依赖 → B
B → 依赖 → A ← ⚠️ 检测到环!
调度中心发现成环,直接拒绝派发。
就像出门前先看地图,发现是死胡同就不走,而不是走进去再掉头。
事前预防 > 事后处理。
解法三:外部介入
A 等 B 超过阈值
↓
系统自动升级:发出 Human-in-the-loop 信号
↓
人工介入:
"A 你先用上海作为默认出发城市"
"B 你先用三亚作为默认目的地"
↓
死锁打破,任务继续
12. Human-in-the-loop:什么时候该让人介入
四种必须让人介入的情况
情况一:权限/能力边界
Agent 遇到了自己无权或无法处理的事,继续走下去 = 越权或偏离预期。
例:查文档时发现需要更高权限
例:直接查不到,需要用另一种方式,但这种方式可能偏离原始目标
→ 暂停,申请授权或确认方向
情况二:死锁/僵局
系统自己解不开,必须引入外部条件。
(见上一章节)
情况三:高风险不可逆操作
Agent 准备执行:
- 删除数据
- 转账付款
- 发送邮件给 100 个客户
- 部署上线
这些操作做了就很难撤回,错了代价极大。
→ 不管 Agent 多"确定",都必须人工确认。
就像银行大额转账,系统再智能,也会弹出: "您确定要转账 ¥50,000 吗?"
情况四:置信度低于阈值
查航班 → 置信度 99% → Agent 自己做主
翻译文件 → 置信度 95% → Agent 自己做主
法律条款解读 → 置信度 60% → ⚠️ 需要人工复核
医疗诊断建议 → 置信度 72% → ⚠️ 必须人工确认
不是"不会"才请示,是"不够确定"就请示。 有时候说"我不确定,建议人工核实",才是负责任的表现。
介入也是成本:找到平衡点
介入太少 → Agent 乱来,出错没人管
介入太多 → 每步都问人,还不如不用 Agent
Human-in-the-loop 的设计核心,不是"什么时候让人介入",而是:
让人在最值得介入的地方介入。
低价值重复的事 → Agent 全自动
高风险关键节点 → 人来拍板
出了意外的边界 → 人来兜底
13. 系统哲学:接受误差,设计反馈
工程的本质不是消灭误差,而是管理误差
在 A2A 这样的多 Agent 系统里,误差是不可避免的:
- 语义理解有偏差
- 工具调用有失败
- 网络传输有延迟
- Agent 判断有偏差
追求零误差,是一个危险的幻觉。
真正的工程智慧,是:
接受误差的存在
↓
设计反馈回路
↓
让系统能够感知误差
↓
让系统能够自我修正
反馈回路的设计
执行 → 观察结果 → 与预期对比 → 发现偏差 → 修正 → 再执行
↑___________________________________________________|
这个回路越短,系统越健壮。
总结
| 章节 | 核心一句话 |
|---|---|
| A2A 是什么 | 让 Agent 之间能够发现、理解、协作 |
| Agent Card | Agent 的菜单,声明自己能做什么 |
| 通信机制 | 异步非阻塞,做一点给一点 |
| 任务生命周期 | 每个状态都有意义,终态必须明确 |
| 调度中心 | 识时务,懂优先级,会保存现场 |
| 去重与缓存 | 幂等性是分布式系统的黄金原则 |
| 事件驱动 | 计划赶不上变化,系统要能动态切换 |
| 语义鸿沟 | 每层转发都会累积误差,要携带意图 |
| A2A vs MCP | A2A 是嘴,MCP 是手,各司其职 |
| 安全鉴权 | 双向验证,防伪装,防越权 |
| 环形死锁 | 超时退出,预防检测,外部介入 |
| Human-in-the-loop | 让人在最值得介入的地方介入 |
| 系统哲学 | 接受误差,设计反馈,动态中守住不变的原则 |
写在最后
学完这一章,停下来想了一下。
A2A 协议里有一个细节让我印象很深——
任务状态里有一个 input-required,不是失败,不是继续,是主动暂停,等待补充。
Agent 知道自己不知道,所以停下来问。
这让我想到《大学》里的一句话,"知止而后有定"。 知道在哪里停下来,才能稳定。
系统设计里的超时退出、Human-in-the-loop、置信度阈值—— 本质上都是在回答同一个问题:什么时候该停,什么时候该问人。
不是越自动越好,而是知道自己的边界在哪里。 Agent 如此,人也如此。
昇哥 · 2026年4月 学 AI Agent 系列途中,把想清楚的事写下来