2187 字
约 7 分钟
14
S21-X09 · A2A:Agent 之间怎么通信

MCP 解决的是"Agent 调工具",A2A 解决的是"Agent 找另一个 Agent 干活"。两者的边界经常被混为一谈:把另一个 Agent 包成一个 MCP 工具也能跑,但那样你就丢掉了任务生命周期、长时任务的进度回传、以及"这个 Agent 能干什么"的机器可读声明。

下面按一次真实交互的顺序走:发现 → 声明 → 建任务 → 推进状态 → 取产物。


1. 场景:为什么不能只靠工具调用

对比项 MCP(Agent ↔ 工具) A2A(Agent ↔ Agent)
对端是什么 无状态函数集合 有自己计划的自主实体
调用时长 秒级 分钟到小时,可能中途要问人
返回什么 一次结果 一个可持续查询的 Task
能力描述 inputSchema Agent Card(技能、输入输出模态、鉴权方式)
谁做决策 调用方 被调用方自己做

关键差异是控制权:工具不会中途反问,Agent 会。所以 A2A 的核心不是"请求-响应",是"任务状态机"。


2. 核心对象

对象 作用 关键字段
Agent Card 机器可读的能力声明 name、description、skills[]、capabilities、securitySchemes
Task 一次委派的工作单元 id、status.state、artifacts[]、history[]
Message 任务内的一条消息 role(user/agent)、parts[]
Part 消息或产物的内容块 text / file / data
Artifact 任务产出物 name、parts[]

parts[] 是 A2A 里最重要的设计:一条消息不是纯文本,而是若干内容块拼起来的,可以同时带一句说明、一个 PDF、一段结构化 JSON。这比"只传字符串"的表达力高一个量级。


3. 发现:一个固定路径的 JSON

{
  "name": "report-agent",
  "description": "把原始数据整理成可发布的周报",
  "url": "https://agents.example.com/report",
  "version": "1.2.0",
  "capabilities": {"streaming": true, "pushNotifications": true},
  "defaultInputModes": ["text/plain", "application/json"],
  "defaultOutputModes": ["text/markdown", "application/pdf"],
  "skills": [{
    "id": "weekly-report",
    "name": "周报生成",
    "description": "输入指标 JSON,输出 Markdown 周报并附图表",
    "tags": ["report", "markdown"],
    "examples": ["把上周的 DAU 和留存整理成周报"]
  }],
  "securitySchemes": {"bearer": {"type": "http", "scheme": "bearer"}}
}

约定位置是 https://<host>/.well-known/agent-card.json(早期草案里叫 agent.json,读到 404 时两个都试)。发现机制是 A2A 最实用的部分:调用方不用读文档,拉一次卡片就知道对方能干什么、要什么鉴权、支不支持流式。


4. 建任务:message/send

{"jsonrpc":"2.0","id":1,"method":"message/send","params":{
  "message":{"role":"user","parts":[{"kind":"text","text":"生成 2025-W44 周报"}],
             "messageId":"m-001"},
  "configuration":{"acceptedOutputModes":["text/markdown"]}}}

典型响应(示意,非实测输出):

{"jsonrpc":"2.0","id":1,"result":{
  "id":"task-7f21",
  "status":{"state":"working","timestamp":"2025-11-03T09:00:00Z"},
  "artifacts":[],
  "history":[{"role":"user","parts":[{"kind":"text","text":"生成 2025-W44 周报"}]}]}}

返回的是 Task 而不是结果。这是 A2A 和普通 HTTP API 最大的手感差异:你拿到的是一个 id,之后靠 tasks/get 轮询或靠流式订阅。

方法 用途 备注
message/send 建任务或追加消息 同步等结果时它也可能直接返回 completed
message/stream 同上,但用 SSE 推事件 需要卡片里 streaming: true
tasks/get 查任务状态与产物 长任务的主要读取方式
tasks/cancel 请求取消 是"请求",对端可以拒绝
tasks/pushNotificationConfig/set 注册回调地址 需要 pushNotifications: true

5. 状态机

状态 含义 谁会走到这里
submitted 已接收,未开始 排队中
working 处理中 最常见
input-required 需要调用方补充信息 被调用 Agent 反问
completed 完成,产物在 artifacts 终态
failed 失败,status.message 带原因 终态
canceled 已取消 终态
rejected 拒绝接单 终态
auth-required 需要额外授权 半终态,补凭证后可继续
unknown 对端自己也说不清 兜底

input-required 是整套协议的价值所在:它把"多轮澄清"变成了协议内的一等状态,而不是让调用方去猜"为什么对面不返回"。调用方看到它,应把 status.message 转给人或自己的模型,然后用 message/send 带同一个 taskId 追加消息。

流式下,这些状态变化以事件形式推过来:

event: TaskStatusUpdateEvent
data: {"id":"task-7f21","status":{"state":"working"},"final":false}

event: TaskArtifactUpdateEvent
data: {"id":"task-7f21","artifact":{"name":"weekly.md","parts":[{"kind":"text","text":"# 2025-W44..."}]},"append":true,"lastChunk":false}

event: TaskStatusUpdateEvent
data: {"id":"task-7f21","status":{"state":"completed"},"final":true}

append: true 表示这块内容要接在前一块后面,lastChunk: true 表示该产物传完了。大产物分块传是 A2A 相对普通 JSON 响应的另一个实用点。


6. 推送:长任务不该靠轮询

{"jsonrpc":"2.0","id":2,"method":"tasks/pushNotificationConfig/set","params":{
  "taskId":"task-7f21",
  "pushNotificationConfig":{
    "url":"https://caller.example.com/a2a/callback",
    "token":"callback-verify-token",
    "authentication":{"schemes":["bearer"]}}}}
通道 适合 成本
轮询 tasks/get 任务短、调用方无公网地址 简单,但延迟与请求量挂钩
SSE 流 交互式、需要过程反馈 连接要保活,反代有超时
Webhook 推送 分钟级以上的长任务 调用方必须暴露可回调地址

三者可以叠加:先建任务、拿 id,再按任务时长选轮询或回调。回调地址的 token 必须校验,否则任何人都能伪造"任务完成"通知打你的接口。


7. 与 MCP 的分工

你要做的事 该用
查数据库、跑命令、读文件 MCP
让另一个团队/厂商的 Agent 完成一段有自己判断的工作 A2A
自己的多个内部 Agent 协作 先看编排框架,未必需要协议
对外暴露能力给不特定调用方 A2A(Agent Card + 标准方法)

一句话:MCP 向下(资源与工具),A2A 向上(协作与委派)。 一个 A2A Agent 内部完全可以再用 MCP 挂一堆工具,层次不冲突。


8. 鉴权与企业网关

层次 做法 解决什么
传输 HTTPS + mTLS(东西向) 链路身份
Agent 级 Bearer / OAuth 2.0 谁在调用
任务级 任务所属租户 + 可见性 谁能看这个 Task
网关 统一入口、限流、审计日志 横向治理

企业里常见的形态是"所有 Agent 都挂在同一个网关上",Agent Card 由网关代发,真实地址在内网。这样调用方只需要信任一个域名,被调用方的地址与扩缩容对调用方透明。


9. 排错

现象 原因 处理
拉 .well-known 404 路径写成了 agent.json 或未暴露 两个路径都提供,注意 Content-Type
任务永远 submitted 对端没有 worker 消费队列 用 tasks/get 看 status.timestamp 是否更新
调 message/send 一直不返回 对端把长任务做成同步 改用 message/stream 或先建任务后轮询
input-required 后调用方又建了新任务 没带 taskId 追加消息必须带同一个 task id
流式中途断 反代读超时 心跳 + 客户端重连后 tasks/get 补齐
回调收到伪造请求 没校验 token 校验 token + 来源 IP + 幂等键

10. 边界:现实约束

现实 应对
规范仍在演进,字段会变 客户端按"缺字段就用默认值"写,别硬校验
跨厂商互通尚未普及 先在单厂商内部把协议跑通,再谈跨域
双方对"任务完成"定义不同 在 Agent Card 的 skills.description 里写清验收标准
长任务的对账困难 自己维护 taskId → 内部单号 的映射表
权限边界模糊 委派时只给最小权限凭证,别传长期 token

落地顺序建议:先用自己的两个 Agent 走通 Agent Card → message/send → input-required → artifacts 这条链,再考虑接第三方。 一上来就做跨厂商互通,通常卡在鉴权和语义对齐上,而不是协议本身。


11. 自测

  1. A2A 与 MCP 的分界线用一句话怎么划?
  2. Agent Card 放在哪个路径,它至少要让调用方知道哪三件事?
  3. input-required 状态出现后,调用方正确的下一步动作是什么?
  4. message/send 返回的是结果还是任务?这个设计为什么必要?
  5. 三种推进通道各适合多长的任务?
  6. 为什么说回调地址的 token 必须校验?
S21-X09 · A2A:Agent 之间怎么通信
https://www.clxhxhhr.top/posts/5692/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。