1551 字
约 5 分钟
7
A2A智能体互联
2026-10-13
无标签
系列:ToyAgent 智能体知识库 14/26
A2A:智能体之间的互联协议
一句话核心:A2A 就是"代理之间的 REST"——发现靠名片(Agent Card),调用靠统一信封(task_id + 状态机),发起方永远不猜对方的内部实现。
为什么需要
前一步讲的子代理是"自家分身":主代理直接 new 出来,活在同一个进程里,共享宿主的模型配置和工具集。这种协作方式解决不了另一类问题——网络上还有别人家的代理:翻译代理、天气代理、订票代理……它们有自己的实现、自己的鉴权、自己的主人,你不了解也不需要了解它们的内部结构。
类比很直观:
- 子代理 = 请同事帮忙:坐你旁边,喊一声就干,共享你的工位(进程、模型、工具)。
- A2A 外部代理 = 外包给合作公司:发一封标准格式的合同(任务信封),等对方的正式回执(task_id + 结果),至于对方内部怎么排班、用什么软件,你一概不管。
没有统一协议会发生什么?每对接一家代理就要读一遍对方的 API 文档、适配一遍请求格式、处理一遍错误码。对接 N 家代理的复杂度变成 O(N×M)。A2A 把这个矩阵压缩成"双方都只认一套标准"。
核心机制
A2A 协议有三块基石:
| 基石 | 内容 | 场景对应 |
|---|---|---|
| 名片发现 | Agent Card:name / url / version / skills,生产中托管在 /.well-known/agent.json |
a2a_discover 拉取 translator、weather 两张名片 |
| 标准信封 | task_id + submitted → working → completed/failed 状态机 | a2a_send 发送任务,返回 task_id + 回执 |
| 异步回执 | 发起方拿到 task_id 即可离开,结果异步取回 | 响应带 task_id,状态流转展示在执行轨迹里 |
代码骨架:
// 名片:A2A 的发现单元,生产中就是 https://对方域名/.well-known/agent.json
record AgentCard(String name, String url, String version,
List<String> skills, String description) {}
// 发送:按名片路由 + 状态机流转,发起方只认 task_id
String taskId = "task-" + seq + "-" + uuid4();
// 状态流转:submitted → working → completed
return "{\"task_id\": \"" + taskId + "\", \"status\": \"completed\", \"result\": \"" + reply + "\"}";
主代理的调用流程是一条固定链路:
a2a_discover 拉名片 → 按 skill 路由到目标代理 → a2a_send 发标准信封 → 拿 task_id 回执 → 轮询/订阅取结果
sequenceDiagram
participant U as 用户
participant A as 主代理
participant R as 名片端点
participant T as 翻译代理
U->>A: 把你好智能体翻译成英文
A->>R: a2a_discover 拉取名片
R-->>A: 返回 translator 与 weather 两张名片
A->>A: 按 skill=translate 路由到 translator
A->>T: a2a_send 发送标准任务信封
T-->>A: 回执 task_id 状态 submitted
T->>T: 状态流转 working
T-->>A: 状态 completed 附带结果 Hello Agent
A-->>U: 返回翻译结果与 task_id
调用全程只见名片与信封:发现按 skill 路由、调用拿 task_id 回执、结果异步取回,主代理对翻译代理的内部实现零感知。
子代理 vs A2A 外部代理
| 维度 | 子代理 | A2A 外部代理 |
|---|---|---|
| 运行位置 | 同进程(in-process) | 跨进程、跨网络 |
| 上下文 | 可 spawn 全新 / fork 继承父会话 | 对方独立,只能靠任务描述传递 |
| 信任模型 | 自家代码 | 标准协议 + 名片声明的能力边界 |
举例
模拟一次"中文翻译成英文"的完整 A2A 执行轨迹:
用户:让翻译代理把「你好,智能体」翻译成英文
[Agent 思考] 需要翻译能力 → 先发现可用代理
→ a2a_discover()
← 名片1: { name: "translator", url: "http://a2a.translator.svc", version: "1.2.0",
skills: ["translate"], description: "多语言翻译" }
← 名片2: { name: "weather", url: "http://a2a.weather.svc", version: "0.9.1",
skills: ["forecast"], description: "天气预报" }
[Agent 思考] skill=translate 命中 translator 名片
→ a2a_send(card=translator, task="把「你好,智能体」翻译成英文")
← { task_id: "task-42-9f3a", status: "submitted" }
← { task_id: "task-42-9f3a", status: "working" }
← { task_id: "task-42-9f3a", status: "completed", result: "Hello, Agent." }
Agent:翻译结果——Hello, Agent.(task_id=task-42-9f3a,全链路可追溯)
再举一个生产视角的对比场景,说明"猜内部接口"和"按 skill 路由"的差别:
场景:接入三家翻译供应商(A 家 REST、B 家 gRPC、C 家 webhook)
无 A2A:写三个 Adapter,各处理一种鉴权、一种错误码、一种重试语义。
有 A2A:三家的名片都声明 skill=["translate"],主代理只需按 skill 选名片,
发统一信封。换供应商 = 换一张名片里的 url,主代理代码零改动。
注意 task_id 的角色:发起方拿到它就可以去干别的事,之后靠轮询或订阅拿结果,而不是傻等阻塞——这是异步任务在跨网络协作里必须的解耦。
面试考点
- A2A 和 MCP 有什么区别? MCP 解决"代理 ↔ 工具"的连接(我调用一把锤子),A2A 解决"代理 ↔ 代理"的协作(我委托一家公司干活)。一个讲能力接入,一个讲任务委托。
- 为什么发起方要持有 task_id 而不是同步等结果? 跨网络任务可能跑几秒到几小时;持 task_id 使发起方可以释放线程、支持断线重连后继续查询、支持超时重试与幂等去重。
- 发现机制为什么用
/.well-known/agent.json? 复用 HTTP 生态的惯例路径(类似 OAuth 的 well-known 端点),无需额外注册中心也能零配置发现。
生产化路径
- 名片加认证与签名校验:防止恶意代理冒名顶替,发现结果验签后才可路由。
- 长任务支持轮询 / 回调两种模式,短任务可同步直返。
- 失败重试与幂等:同一 task_id 重复投递不应产生两次副作用。
- 多代理编排:一次请求级联多个外部代理,形成 DAG 式委托链。
评论
0 条
还没有评论,先写一条吧。