AI Agent 为什么离不开 JSON?从 Tool Calling、Function Calling 到 MCP 的完整数据流解析

拆解一次 Agent 调用里每一跳的 JSON:工具定义 Schema、Function Calling / Tool Calling 消息、MCP JSON-RPC,以及校验失败时数据如何回流。

上一篇《AI Agent 为什么开始使用 JSON Schema、Function Calling 与 MCP》讲的是为什么会出现这三层。本文换一个视角:盯住一次真实调用里流动的字节——几乎全是 JSON。

用户看到的是自然语言;Agent 真正「办事」靠的是:把意图编码成 JSON 参数、把工具结果编码成 JSON 消息、把跨进程协议也编码成 JSON-RPC。JSON 不是点缀,而是模型、宿主程序、MCP Server 之间唯一能互相校验的公共语言。

三个名字,同一份 JSON

文档里常把三个词混用,数据流上它们处在不同层,但载荷形状高度同构:

名称发生在哪两端JSON 扮演的角色
Function Calling模型 API ↔ 宿主tools 定义 + tool_calls.arguments
Tool Calling同上(更通用的叫法)同一套 messages / tools JSON
MCP宿主 ↔ 工具进程JSON-RPC 方法 + inputSchema

可以记成一句话:模型侧用 JSON 选工具、填参数;MCP 侧用 JSON 发现工具、执行工具。 宿主是翻译器:把 MCP 的 tools/list 映射成模型的 tools 数组,把模型的 tool_calls 映射成 tools/call。

为什么必须是 JSON

Agent 要同时满足三方:

  • 模型:训练语料里 JSON 极多,生成合法对象比生成 protobuf 二进制容易得多
  • 程序:有成熟的解析、Schema 校验、Diff 与 JSONPath 生态
  • 协议:OpenAPI、JSON-RPC、MCP inputSchema 已经绑在同一套类型描述上

纯自然语言无法 fail-fast:括号、引号、多语言混写都会让正则解析崩溃。YAML 对缩进敏感,模型更容易写坏。二进制协议对人类与 LLM 都不友好。于是 JSON 成为「可审计、可校验、可版本化」的默认导线格式——这也是本站工具全部围绕 JSON 的原因:你调试的就是这条导线。

第一跳:工具定义里的 Schema

数据流从「告诉模型有哪些工具」开始。无论走 OpenAI 风格的 tools,还是 MCP 的 tools/list,核心都是一份 JSON Schema(或其子集):

{
  "name": "get_weather",
  "description": "查询指定城市当前天气,只读",
  "parameters": {
    "type": "object",
    "properties": {
      "city": { "type": "string", "description": "城市名,如上海" },
      "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
    },
    "required": ["city"]
  }
}

MCP 里同一份约束写在 inputSchema 字段。Schema 同时进两路:校验器拦截非法参数;模型上下文靠 description 决定何时调用。字段说明写得越像业务文档,误调用越少。

第二跳:Function Calling / Tool Calling

宿主把工具列表随 messages 发给模型后,模型不执行代码,只返回结构化调用。典型形态(各厂商字段名略有差异):

{
  "role": "assistant",
  "tool_calls": [
    {
      "id": "call_01",
      "type": "function",
      "function": {
        "name": "get_weather",
        "arguments": "{\"city\":\"上海\",\"unit\":\"celsius\"}"
      }
    }
  ]
}

注意 arguments 常常是字符串化的 JSON:要先 JSON.parse,再按 Schema 校验,最后才执行。执行结果再以 tool 角色消息回流:

{
  "role": "tool",
  "tool_call_id": "call_01",
  "content": "{\"city\":\"上海\",\"temp_c\":31,\"condition\":\"晴\"}"
}

这一跳解决的是「模型如何伸手」。并行多工具时,数组里会出现多个 tool_calls,宿主可并发执行,再按 id 把结果对号入座。

第三跳:MCP JSON-RPC

若工具不在宿主进程内,而是独立 MCP Server(文件系统、GitHub、内部订单服务),宿主与 Server 之间走 JSON-RPC 2.0。一次只读查询大致三步:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"host","version":"1.0"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"上海"}}}

Server 的成功响应同样是 JSON:content 数组里常见 type: "text",文本内容又是一段 JSON 字符串。于是出现「JSON 套 JSON」——外层是协议信封,内层是业务载荷。调试 MCP 时先分清这两层,再用 Schema 校验内层。

传输可以是 stdio 或 Streamable HTTP,但载荷仍是 JSON 行或 JSON 体。关于 2026 传输与 SDK 是否要改代码,见《MCP 2026 迁移指南》。

一次完整调用的数据流追踪

用户说:「上海今天多少度?」端到端如下:

  1. Host → MCP Server:tools/list 得到带 inputSchema 的工具清单(JSON)
  2. Host → 模型 API:映射为 tools[].parameters(仍是 JSON Schema)
  3. 模型 → Host:tool_calls,arguments 为 {"city":"上海"}
  4. Host 校验:对照 Schema,缺字段或类型错误则拒绝执行,把错误 JSON 回喂模型
  5. Host → MCP:tools/call,params.arguments 为对象(不是字符串)
  6. MCP → Host:天气结果 JSON
  7. Host → 模型:role: tool 的 content 字符串
  8. 模型 → 用户:自然语言;若下游系统只要结构,再用输出 Schema 约束最终 JSON
用户自然语言
    │
    ▼
Host 编排 ──JSON Schema──► LLM Tool Calling
    │                         │
    │                         ▼
    │                    arguments JSON
    │                         │
    ▼                         ▼
MCP JSON-RPC ◄──────── 校验通过才执行
    │
    ▼
结果 JSON ──► tool message ──► 模型最终答复

小型脚本可能跳过 MCP,直接在宿主里调本地函数;企业 Agent 则几乎总是「Tool Calling + MCP」叠在一起。选型与生态见《2026 MCP Server 排名》。

校验失败时数据如何回流

JSON 能成为 Agent 的「类型系统」,是因为失败也可以结构化。建议至少两道闸:

闸门校验对象失败后怎么回流
执行前模型 arguments不调用真实工具;把 Schema 错误写成 tool 结果或系统提示,让模型重填
回写前MCP / 函数返回值截断、脱敏或标错;避免把堆栈原文灌进下一轮上下文

开发阶段把 Schema 与 2~3 组正/反例 payload 存进仓库,用 JSON 工具箱本地校验——这和测 REST 契约同一思路,只是消费者换成了模型。

常见问题 FAQ

Tool Calling 和 Function Calling 是一回事吗?

对开发者几乎是同一套数据流:宿主把工具 Schema 发给模型,模型返回带 JSON 参数的调用,宿主执行后再把 JSON 结果塞回对话。Function Calling 是早期 OpenAI 命名;Tool Calling / Tools API 是后续更通用的叫法。

MCP 报文为什么也是 JSON?

MCP 基于 JSON-RPC 2.0:initialize、tools/list、tools/call 的请求与响应都是 JSON 对象。工具的 inputSchema 本身又是 JSON Schema,因此 Host 可以把 MCP 工具一对一映射成模型 API 的 tools 数组。

模型输出的 arguments 是字符串还是对象?

多数 Chat Completions 风格的 API 把 arguments 做成 JSON 字符串,需要宿主 JSON.parse 后再按 Schema 校验。部分新接口直接给对象。无论哪种,落地前都应用同一份 Schema 校验。

为什么不能用 YAML 或 protobuf 替代 JSON?

可以在工具实现内部用任意格式,但模型上下文与跨厂商协议层已把 JSON 当作事实标准。YAML 缩进易错,protobuf 对模型不友好。常见做法是边界用 JSON,内部再转换。

数据流里哪一层最该做 Schema 校验?

至少两处:模型返回 tool_calls 之后、真正执行工具之前;以及 MCP Server 返回结果之后、写回模型之前。前者防幻觉参数,后者防脏数据污染下一轮推理。

如何本地验证这条链路上的 JSON?

把 inputSchema、样例 arguments、工具返回样例保存成 JSON 文件,用 JSON 工具箱在浏览器本地校验 Schema 与数据是否匹配,数据不上传服务器。

总结

AI Agent 离不开 JSON,是因为每一跳都要机器可读:Schema 描述工具,Tool Calling 传递调用,MCP 用 JSON-RPC 把调用送出进程。自然语言只出现在两端的用户界面;中间全是可校验的对象。

建议你从一次真实工具开始:写出 Schema → 打印模型返回的 arguments 字符串并解析 → 若工具在 MCP Server 上,再抓一条 tools/call。三份 JSON 对得上,这条 Agent 才算真正跑通。演进背景见《技术演进全解析》;Schema 样例可在 JSON 工具箱本地校验后再上线。