AI 开发

AI Agent 为什么开始需要 JSON Schema?从 Tool Calling 到结构化 Agent 工作流

阅读约 15 分钟

2024 年以前,多数「Agent」是一段 prompt 加两三个函数。2026 年不行了:Function Calling 要描述参数,MCP 要注册工具,Structured Outputs 要约束返回值,A2A 要协商能力,Agents SDK 要用 handoff 对象做路由。这些层各自独立演进,却都落回同一份契约语言——JSON Schema。本文讲清它为什么从「文档里的校验语法」变成了 Agent 的运行时,以及从单次 Tool Calling 走到多步工作流时,该把 Schema 写在哪里。

阅读前速览

阶段 没有 Schema 时 有 Schema 时
单次 Tool Calling 参数是字符串,类型靠运气,多一个字段也能跑 parameters / inputSchema 在调用前拦截
多步 Agent 下游靠解析散文,handoff 靠「提到了 billing」 handoff 对象带 output_type,路由可测试
跨协议复用 Function Calling、MCP、A2A 各写一份,越改越偏 Git 里一份 Schema,导出为三种适配器

结论先说:JSON Schema 不是「给模型看的文档」,而是 Agent 主机在每一步执行前、交接时、评测时都要跑的校验器。

为什么是「开始」需要,而不是一直需要?

早期 Tool Calling 能活下来,是因为链路短:模型吐一个函数名和一段参数,宿主执行,结果塞回对话。错了就重试,用户能看见。Schema 最多写在 README,属于「最好有」。

2025–2026 年链路变长了。一个工单 Agent 可能先分类,再查订单,再决定退款或转人工,再把对象交给另一个 Agent。中间任何一跳如果只是「看起来像 JSON」,后面的步骤会把错误当成事实。这时 Schema 从文档变成了运行时门禁:过不了就不往下走。

另一条压力来自协议层。我们在 MCP Tool Schema vs Function Calling 里写过:厂商 API 用 parameters,MCP 用 inputSchema。A2A Extensions 又把能力写进 Agent Card。三套协议不共享运行时,但都认 JSON Schema 子集。你不在一开始选定 Schema,就会在三个地方各维护一份「几乎一样」的定义,然后在第三周发现 order_id 有的是 string、有的是 number。

DevDay 2026 预测 把同一件事说得更直:Structured Outputs、Function Calling、Agent 运行时正在收成同一根 JSON 管。Schema 是这根管的口径。口径不定,后面接什么协议都漏。

Tool Calling 看起来已经有「结构」,缺口在哪?

Function Calling 的 tools[] 里本来就有一份类似 Schema 的 parameters。很多团队因此觉得「我们已经结构化了」。生产里真正出事的,往往是下面四类。

第一,arguments 仍是字符串。模型返回的是:

{
  "tool_calls": [{
    "id": "call_8f2a",
    "type": "function",
    "function": {
      "name": "create_refund",
      "arguments": "{\"order_id\":\"A-1042\",\"amount\":\"99.00\",\"reason\":\"late\"}"
    }
  }]
}

宿主必须再 JSON.parse 一次。amount 是字符串还是数字,reason 是否在枚举里,多出来的 note 能不能进账务系统——parameters 若不在主机侧强制执行,这些都靠「模型今天心情好」。这不是 Schema 写了就自动生效;strict: true 只约束模型侧,宿主仍要自己校验。

第二,工具结果没有 Schema。role: tool 大多是自由字符串。下一步模型会把这段文字再「理解」一遍。查询成功、库存为 0、权限不足,三种情况如果外形不同,多步工作流会在第三跳分叉到错误分支。

第三,类型宽到失去筛选力。只写 type: string 的 priority,模型可以输出 "urgent!!!"。没有 enum、required、additionalProperties: false 的 Schema,只是一份注释。

第四,单次成功掩盖了工作流失败。第一步 lookup_order 多返回一个 status: "ok",第二步退款 Agent 把 status 当成订单状态。本地试一条快乐路径看不出来,回归集里才会炸。Schema 的价值在这里:它让「这一跳能交给下一跳的对象」变成可断言的测试,而不是对话记录。

调试这类嵌套 JSON 时,先在本地 格式化并做语法检查,再和上周能跑的定义做 Diff。不要把带用户数据或密钥的 tool 响应贴到公开 JSON 网站。

JSON Schema 在 Agent 里其实有四层角色

把 Schema 只当成「工具参数说明书」,会漏掉 2026 年真正在长的三层。完整图是:

  1. 工具入参。 Function Calling 的 parameters、MCP 的 inputSchema。决定模型「能请求什么」。
  2. 工具出参。 查询、写入、外部 API 的返回对象。决定下一跳「能相信什么」。这一层现在最常缺。
  3. Agent 交接(handoff)。 分类结果、意图、置信度、要交给谁。Agents SDK 里这是路由依据,不是聊天摘要。
  4. 工作流状态与评测。 检查点、trace、eval fixture。长任务要能从 JSON 恢复,而不是从对话记录里猜进度。

入参 Schema 示例(退款工具):

{
  "$id": "https://example.com/schemas/tools/create_refund.json",
  "type": "object",
  "properties": {
    "order_id": { "type": "string", "pattern": "^A-[0-9]{4,}$" },
    "amount": { "type": "number", "exclusiveMinimum": 0 },
    "reason": { "type": "string", "enum": ["late", "damaged", "duplicate", "other"] }
  },
  "required": ["order_id", "amount", "reason"],
  "additionalProperties": false
}

handoff Schema 示例(分诊 → 账务):

{
  "$id": "https://example.com/schemas/handoffs/triage_to_billing.json",
  "type": "object",
  "properties": {
    "intent": { "type": "string", "enum": ["refund", "exchange", "human"] },
    "order_id": { "type": "string" },
    "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
    "from": { "const": "triage" },
    "to": { "const": "billing_agent" }
  },
  "required": ["intent", "order_id", "confidence", "from", "to"],
  "additionalProperties": false
}

两份文件的共同点比差异更重要:都是对象、都有枚举、都禁止多余字段、都能进 Git、都能被 ajv(或等价库)在 CI 里跑。模型换快照、协议换 MCP 还是 Function Calling,校验器不用重写。关键字怎么写,见 JSON Schema 教程。

从单次调用到结构化工作流,Schema 卡在哪几道门

单次 Tool Calling 只有一扇门:参数合不合法。结构化 Agent 工作流至少有四扇。

门 1:选择工具之前。 目录超过十个工具后,把整份 Schema 塞进一次请求又贵又容易选错。先检索再调用时,检索结果本身也应是带 Schema 的短名单,而不是一段模型生成的工具介绍。

门 2:执行之前。 parse → validate → execute。validate 失败就返回结构化错误(缺字段、类型错、枚举外),不要把原始字符串丢给下一跳。这一步同时挡住 prompt 注入式的额外键。

门 3:交接之前。 triage 必须交出符合 handoff Schema 的对象,billing 才启动。路由条件写成 intent === "refund" && confidence >= 0.8,而不是「回复里出现了退款两个字」。

门 4:恢复与评测。 长任务把检查点写成合法 JSON:

{
  "run_id": "run_20260903_014",
  "step": "await_refund_ack",
  "state": {
    "order_id": "A-1042",
    "intent": "refund",
    "amount": 99.0
  },
  "schema": "https://example.com/schemas/state/refund_flow.json"
}

评测集也是同一类对象:给定输入,断言输出通过 Schema,并且关键字段相等。Agent「变笨了」时,先 Diff 本周与上周的 Schema 和 fixture,再怀疑模型。很多事故是多了一个 required,或少了一个 enum 值。

一份 Schema,如何喂给 Function Calling、MCP 和 A2A

协议层不要各写各的。推荐的方向是一份规范文件,三种投影:

协议 Schema 落点 从同一文件怎么导出
Function Calling function.parameters 拷贝 type/properties/required,打开 strict
MCP inputSchema 同一对象作为 tools/list 的一部分
Structured Outputs text.format.json_schema handoff / 最终答复用同一套对象定义
A2A Agent Card / Extension metadata 对外能力声明引用同一 $id

投影时注意子集差异:厂商 strict 模式往往要求所有 property 都在 required 里,且禁用部分 oneOf / $ref。规范文件可以更完整,导出器按目标裁剪,并在 CI 里对「规范 vs 投影」做一次结构 Diff,避免手改某一侧。

MCP 负责工具供给,Function Calling 负责模型如何表达调用意图,A2A 负责Agent 之间如何发现和协商。Schema 不替代这三层,它是三层共用的字段字典。这一点和前两篇的结论一致,只是把「字典」从概念落成仓库里的文件。

落地:把 JSON Schema 当成单一事实来源

可以立刻做、也不依赖任何一次发布会的几件事:

  • 在仓库建 schemas/tools/、schemas/handoffs/、schemas/state/。每个文件有稳定 $id 和版本。
  • 用脚本导出 Function Calling / MCP / Structured Outputs 三种适配,禁止在适配文件里手改字段。
  • CI 用 ajv(或等价库)校验:示例 payload、历史 fixture、线上抽样(脱敏后)必须通过当前 Schema。
  • 新工具默认 additionalProperties: false 和显式 enum。需要开放扩展时,单独开 metadata 对象,不要把根对象放宽。
  • 工具超过十个,加路由或检索,不要把目录整包塞进一次请求。
  • 宿主同时接受 arguments 为字符串或对象,避免厂商把类型收紧后全站告警。
  • 含密钥或用户数据的 payload 只在浏览器本地看:格式化、Diff,零上传。

目录可以很薄。重要的是先有文件,再写 prompt。prompt 里的「请返回 JSON」无法在 CI 里失败;Schema 可以。

常见误区

  • 把 Schema 写进系统提示就算完成。 模型侧约束和主机侧校验是两件事。没有主机校验,strict 关掉或换模型快照后,契约就消失。
  • 只校验入参,不校验出参和 handoff。 多步 Agent 的事故更多发生在「上一步返回了意料外的形状」。
  • 为每个协议手写一份「差不多」的 Schema。 第三周一定分叉。导出,不要复制。
  • 用超宽 type: object 逃避设计。 开放对象等于没有门。真正要扩展,用版本号或显式 metadata。
  • 把 Schema 当成模型训练语料。 它首先是给校验器和测试跑的。描述写清楚有助于选工具,但不能代替 enum 和 required。
  • 在可视化编排里改字段,却不回写 Git。 画布上的节点不是事实来源。先导出 Schema,再让画布读文件。

FAQ

没有 Schema,Tool Calling 能不能上线?

原型可以。工具固定、步骤少于三、失败可由人看见时,字符串参数往往够用。一旦出现分支、自动重试或第二个 Agent,没有 Schema 的错误会静默传播。

JSON Schema 会不会被 Structured Outputs 或 MCP 取代?

不会。它们是消费 Schema 的层,不是另一套类型系统。2026 年的趋势是更多协议引用同一份 Schema,而不是发明新的字段语言。

该用 draft-07 还是 2020-12?

跟你的校验库和厂商子集走。Agent 场景真正用到的是 type、properties、required、enum、additionalProperties。选一个能在 CI 和模型 API 之间对齐的子集,比追最新 draft 更重要。

私有内部 API,还要 MCP 和 A2A 吗?

不一定。内部闭环用 Function Calling + Schema 即可。预留 MCP / A2A 的价值在于:当你把同一份 Schema 写进仓库后,以后要插协议只是加导出器,不用重新发明字段。

怎么调试失败的 tool / handoff JSON?

在浏览器本地格式化、对照 Schema 看缺了哪个 required,再和上一版 Diff。JSONSort 不上传数据。不要把含密钥的响应贴到公开站点。

结论

AI Agent「开始」需要 JSON Schema,不是因为模型突然读不懂自然语言,而是因为工作流变长、协议变多、错误必须在主机侧被拦住。Tool Calling 给出调用意图;Structured Outputs 给出最终对象;MCP 和 A2A 给出供给与协商。把这几层粘在一起的,是仓库里那一组可以校验、可以 Diff、可以导出的 Schema 文件。

从今天起与其先写一段更长的 prompt,不如先写 schemas/tools/create_refund.json。模型会换,协议会改,口径应先稳定。

相关文章

更新日志: 初稿发布