「JS全栈AI Agent学习」八、A2A 协议完全指南:从零理解 Agent 协作体系

4863 字
24 分钟
「JS全栈AI Agent学习」八、A2A 协议完全指南:从零理解 Agent 协作体系

📌 系列简介:「JS全栈AI Agent学习」系统学习 21 个 Agent 设计模式,篇数随学习进度持续更新。 📖 原书地址adp.xindoo.xyz 前端转 JS 全栈,正在学 AI,理解难免有偏差,欢迎批评指正 ~


写在前面#

学到多 Agent 系统这里,有一个问题一直没想清楚:

多个 Agent 之间怎么协作?

不是”调用”——调用是单向的,一个 Agent 叫另一个干活。 而是”协作”——两个 Agent 各有分工,互相传递信息,共同完成一件事。

这中间需要一套协议,就像人与人之间需要语言一样。

A2A(Agent to Agent)协议,就是为了解决这个问题而生的。

这篇是学习笔记,记录了从零推导 A2A 协议的完整过程。 不只是”是什么”,更重要的是”为什么这样设计”。


目录#

  1. A2A 是什么,解决什么问题
  2. Agent Card:Agent 的自我介绍
  3. 通信机制:异步、流式与任务派发
  4. 任务生命周期
  5. 调度中心设计:优先级与上下文切换
  6. 任务去重与缓存
  7. 事件驱动:Plan A/B/C 动态切换
  8. 语义鸿沟:误差是如何累积的
  9. A2A vs MCP:两个协议的分工
  10. 安全与鉴权:Agent 凭什么信任对方
  11. 环形依赖死锁:僵局如何打破
  12. Human-in-the-loop:什么时候该让人介入
  13. 系统哲学:接受误差,设计反馈

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-requiredAgent 需要更多信息才能继续→ 等人回答 → 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 收到结果
协议全称管的是关系方向
A2AAgent to AgentAgent ↔ Agent 通信对等协作双向
MCPModel Context ProtocolAgent → 工具调用调用使用单向

一句话记住区别#

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 CardAgent 的菜单,声明自己能做什么
通信机制异步非阻塞,做一点给一点
任务生命周期每个状态都有意义,终态必须明确
调度中心识时务,懂优先级,会保存现场
去重与缓存幂等性是分布式系统的黄金原则
事件驱动计划赶不上变化,系统要能动态切换
语义鸿沟每层转发都会累积误差,要携带意图
A2A vs MCPA2A 是嘴,MCP 是手,各司其职
安全鉴权双向验证,防伪装,防越权
环形死锁超时退出,预防检测,外部介入
Human-in-the-loop让人在最值得介入的地方介入
系统哲学接受误差,设计反馈,动态中守住不变的原则

写在最后#

学完这一章,停下来想了一下。

A2A 协议里有一个细节让我印象很深—— 任务状态里有一个 input-required,不是失败,不是继续,是主动暂停,等待补充

Agent 知道自己不知道,所以停下来问。

这让我想到《大学》里的一句话,“知止而后有定”。 知道在哪里停下来,才能稳定。

系统设计里的超时退出、Human-in-the-loop、置信度阈值—— 本质上都是在回答同一个问题:什么时候该停,什么时候该问人。

不是越自动越好,而是知道自己的边界在哪里。 Agent 如此,人也如此。


昇哥 · 2026年3月 学 AI Agent 系列途中,把想清楚的事写下来

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

「JS全栈AI Agent学习」八、A2A 协议完全指南:从零理解 Agent 协作体系
https://blog.fridolph.top/posts/2026-03-17__learn-ai-8/
作者
Fridolph
发布于
2026-03-17
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录