返回归档
🚪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/listtools/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:交付物。它不是聊天记录

状态分成三类:

  • 进行中:submittedworking
  • 暂停:input-requiredauth-required
  • 终态:completedfailedcanceledrejected
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 和上下文工程。

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