返回归档
🚪LLM 与 Agent

Agent 协议分层:Function Calling、MCP、Skill 与 A2A

Function Calling、MCP、Skill、A2A 不在同一层。各自的协议不变量,决定一次生成如何发现、隔离、执行并写回外部世界。

文章目录

大模型只能基于当前上下文生成下一个 token。它看不见数据库,点不了按钮,也调不动另一个团队的 Agent。所谓 Agent,就是给这次生成接上外部世界。

接法有四种,常被并列成「技术栈」。它们不在同一层:

  • Function Calling 是模型原生的调用语言。解决「如何开口要一个动作」
  • MCP 是标准化的手。解决动作如何被发现、隔离、执行、写回上下文
  • Skill 是按需手册和本地工装。解决「怎么做」何时进入上下文,以及哪些步骤不该交给模型猜
  • A2A 是外包给同事。解决对端会规划时,一件任务如何活下去

类比可以先记在这:FC 像系统调用,MCP 像 USB-C 加一层 LSP 式驱动,Skill 像按需打开的手册,A2A 像把活分给有自己大脑的承包商。

flowchart TB
  U[用户目标] --> R[Agent Runtime]
  R --> P[Planning]
  R --> M[Memory / Context]
  P --> FC[Function Calling<br/>进程内一次调用]
  P --> MCP[MCP<br/>外部系统]
  P --> SK[Skill<br/>按需手册 + 本地脚本]
  P --> A2A[A2A<br/>另一个 Agent]
  FC --> M
  MCP --> M
  SK --> M
  A2A --> M

Agent Runtime

运行时仍是三层:推理层决定做什么,编排层决定调用谁,能力层真正执行。协议出现在后两层。Memory 是粘合剂:每只手用完,都必须改写模型此刻看见的世界,Planning 才接得上下一步。没有这个回写,协议再标准也只是在外面空转。

Function Calling:模型提议,宿主处置

这是最早、也最薄的一层,而且不会被替代。应用把函数 schema 塞进请求;模型用约束解码、tool choice 或 JSON mode,吐出符合 schema 的调用;宿主持执行,再把 observation 以 tool 消息写回。多轮 tool loop 是运行时自己转的,协议只定义单次形状。

核心不变量是 model proposes, host disposes。模型只能提议,真正碰世界的是宿主。非法 JSON 被拒;工具失败则把错误写回上下文,让模型决定重试还是换路。

它解决的是「从自由文本到可执行意图」。发现、鉴权、跨进程、版本协商、凭证隔离,它故意不管。这些空白不是缺陷,是留给上层的。

因此所有更高级的接口,最后都会被翻译回这门语言:MCP 的 tools/list 会变成模型眼前的 schema;Skill 脚本的结果、A2A 的 Artifact,也往往经同等通道进入下一轮。它是底噪,不是被淘汰的旧物。

权衡很干脆:延迟最低、实现最简单、对模型最直接;同时零生态、零隔离,应用必须自己处理一切。

MCP:M×N 问题,加上一条信任边界

MCP 管 Agent ↔ 外部能力。设计灵感是 Language Server Protocol:多个编辑器 × 多种语言,变成多个 Host × 多种工具。消息层用 JSON-RPC 2.0,传输层可换。

JSON-RPC 在这里不是口味。请求带 id,响应用同一个 id 配对;通知没有 id。同一条通道上可以交错 tools/list、tools/call、进度通知,而不必为每个动作新开 REST 资源。方法名是动词,适合动态发现。

角色拆开,才是协议真正硬的地方:

  • Server 只提供聚焦能力。声明 schema、执行、返回结果或标准错误。它不聊天,也不规划下一步。它必须好写、可组合,并且 看不见完整对话,也看不见其他 Server。
  • Client 守会话,把发现到的工具交给模型,按模型决策调用,再把结果写回上下文。
  • Host 是安全经纪。用户同意、能力协商、凭证隔离、取消,都在这一层。工具描述默认不可信,除非 Server 本身可信。

这三条官方原则可以收成中文:Server 要极容易实现;Server 要能互相组合;Server 不准偷看对话、不准窥视邻居。微内核就是这个意思——能力在外,策略在内。

业务开始前先 initialize,交换协议版本和能力集。Server 可能不会流式,Client 可能不会处理采样。谈不妥,就不该假装对方什么都会。近期规格还往「请求尽量自包含」走,是为了少绑 sticky session,方便水平扩展。会话可以有,但不该把生命焊在某一根 TCP 上。

三类原语不是随便分的,控制权不同:

原语 谁主导 副作用 结果去哪
Tools 模型决定调不调 通常有 作为观察,进入下一轮
Resources 应用决定读不读 设计上只读 作为资料注入
Prompts 用户或工作流选用 无 变成后续对话骨架

「改 GitHub issue」是 Tool,「仓库 README」是 Resource,「code review 清单」是 Prompt。混用会把只读数据当成动作,或把动作当成可以随便重放的资料。

发现必须先于调用。tools/list 的结果,最终仍被翻译成 Function Calling schema 给模型看。凭证留在 Server,模型看见的是参数和结果,不是 token。一次 content 写回,本质是给下一轮注意力补一组新的 K/V,和 上下文工程 是同一件事。

传输不改消息语义,只改会话怎么活:

  • Stdio:本地子进程,延迟最低,不能跨机器
  • 旧 SSE:长连接绑会话,断了就丢
  • Streamable HTTP:一个 /mcp。短任务直接 200,长任务再升级成流。用会话头串多次请求,而不是焊死一条连接

MCP Streamable HTTP

不变量可以记四句:消息是带 id 的 RPC;先协商再发现再调用;Server 没有对话权;隔离是一等公民。对端如果自己会拆任务、会反问,就不该再塞进 MCP。

Skill:上下文经济学,外加确定性岛屿

Skill 不是 RPC,也没有 task 状态机。它是一份可 Git 的文件夹:SKILL.md 加可选脚本、参考和资源。

它回答 MCP 不管的问题:这一刻该看见哪段方法,以及哪一步不该让模型生成。

三级加载是对注意力的显式管理,用来对抗 system prompt 膨胀和 lost-in-the-middle:

层 装什么 何时进上下文 成本直觉
L1 name + description 启动时一直在 每个 Skill 大约几十到一百 token
L2 SKILL.md 正文 意图命中后再加载 建议控制在几千 token 内
L3 scripts/、references/、assets/ 真正执行时才打开 按需,几乎无上限

五十个 Skill 的目录可以很便宜;真正激活的两份手册才进窗口。这是按需分页,不是把整座图书馆搬进 prompt。

为什么是本地文件夹,而不是远程注册中心:要能私有、能审计、能版本化;脚本要在本机沙箱跑,凭证不出门,也不该因为注册中心挂了就不会做事。

脚本是 确定性岛屿。转 PDF、算表、校验格式有标准答案,走采样就是在赌。Skill 把程序性知识(组织怎么写邮件、合规红线)从模型权重里剥出来,同时把不该幻觉的步骤交给代码。

Agent + Skills + VM

只接 MCP,会得到一堆没有方法的手。只堆 Skill,会做事但出不了沙箱。

A2A:不透明的同事,活的是任务

A2A 管 Agent ↔ Agent。对端有自己的目标理解、工具箱和失败重试。协议因此必须以 Task 为中心,并且必须 不透明:你看不到对方的思考、计划、工具实现和记忆。交换的只是声明过的能力和这次递过去的信息。这是安全,也是知识产权。

五个对象把语义钉死:

  • Agent Card:/.well-known/ 下的名片。端点、鉴权、技能、是否支持流、输入输出模态。同时承担发现、广告和鉴权协商
  • Task:一件有寿命的活。稳定 taskId,自己经历状态
  • Message:过程里的话
  • Part:文本、文件或结构化数据
  • Artifact:交付物。它不是聊天记录

状态分成三类:

  • 进行中:submitted、working
  • 暂停:input-required、auth-required
  • 终态:completed、failed、canceled、rejected
stateDiagram-v2
  [*] --> submitted
  submitted --> working
  working --> inputRequired: 缺信息
  working --> authRequired: 缺凭证
  working --> completed
  working --> failed
  working --> canceled
  working --> rejected
  inputRequired --> working: 同一 taskId 补消息
  authRequired --> working: 补授权
  completed --> [*]
  failed --> [*]
  canceled --> [*]
  rejected --> [*]

input-required 在 MCP 里没有对应物。工具缺参数,直接报错;同事缺信息,会停住等你。同一 taskId 再补一条消息,任务从暂停回到 working。它可以停四秒,也可以停四天。终态通常不可原地重启,方便引用和审计。

三种 id 必须拆开:

  • taskId 钉住这一件活
  • contextId 钉住多轮记忆
  • JSON-RPC 的 id 只钉住这一次请求

混用会把「再问一句」做成「再开一件新任务」,或把一次 HTTP 超时当成任务失败。

流的是任务进展,不是某个函数的 stdout。短任务可以阻塞到终态;长任务订阅 working、部分 Artifact、input-required;更长的用轮询或 webhook。

原理对照

Function Calling MCP Skill A2A
像什么 系统调用 USB-C + LSP 驱动 按需手册 + 本地工装 有名片的承包商
发现 无,schema 写死 tools/list / resources/list L1 元数据一直在系统提示里 Agent Card
状态 单次调用 请求尽量自包含,会话可选 加载即用 有寿命的 Task
信任边界 同一进程,无隔离 Server 看不见对话和邻居;Host 做经纪 本地文件夹 + 沙箱 完全不透明
失败 非法 JSON 或工具报错,写回模型 结构化错误回 Host,Server 不规划 指令含糊或脚本失败 追问、改路、部分交付、拒绝
保证 模型能发出可解析意图 能力可组合,且默认隔离 上下文成本可控,确定步骤可重复 委托一件活,而不交出大脑
不保证 生态、鉴权、跨进程 对端会思考 能碰到沙箱外的系统 一次往返就结束

组合,以及怎样接到一件真事上

健康的叠法通常是:Skill 教模型怎么正确使用一组 MCP 工具;主 Agent 用 A2A 把子目标交给专业同事,对方自己再挂 MCP 和 Skill;收尾用本地脚本做校验和导出。

不健康的叠法就那几条:把 Skill 冻成 MCP Server,丢掉渐进披露和本地确定性;把 A2A 当远程 Function Calling,把同事降成 API;只接 MCP 不接 Skill,工具很多但没有方法;只堆 Skill,出不了沙箱。

举一件完整的事:写一份符合公司合规的竞品调研报告。

  1. 用户给出目标。Memory 开始记下任务边界。
  2. Skill L1 命中「合规调研报告」,再加载 L2:章节、红线、语气。渐进披露。
  3. MCP 调内部知识库和 CRM。Server 看不到整段对话,只拿到这次查询需要的参数。隔离。
  4. A2A 把「外部竞品深挖」交给研究 Agent。先读 Card,再提交 Task。对方可能 input-required 问范围,最后回 Artifact。不透明执行。
  5. 主 Agent 综合结果,继续规划。所有观察写回 Memory。
  6. 本地 Skill 脚本做格式校验和 PDF 导出。确定性岛屿。

每一步换的不是「更新的协议」,而是对端的智能和该守的边界。

按对端选接口:动作只在本进程 → Function Calling;动作在外部系统、要发现和隔离 → MCP;缺方法和确定性步骤 → Skill;对端会规划、会反问、交付需要时间 → A2A。

协议字段会改。相对稳的是三件事:划清哪些是一次调用、哪些是按需手册、哪些必须是另一段有寿命的任务;把会话、权限、取消、审计做稳;管住模型此刻看见什么——短记忆、长记忆、Prompt 和上下文工程。

出对话框的方式不会变:先约定这次生成能碰到什么,再让协议把那一次碰到,收成下一轮还能用的事实。