你已经能让模型返回 JSON 了,但一到上线就出问题:字段偶尔多一个、嵌套层数不对、多步工具调用时参数和文本混在一起。这篇不重复原理,只讲落地:两种结构化输出方式各自在什么条件下必须用、什么条件下会坑你,以及超时、重试、降级、日志和评测该怎么排。
你已经能让模型吐出 JSON 了,但一上线就出问题:字段偶尔多一个、嵌套层数对不上、多步工具调用时参数和说明文字混在一起。这篇不重复原理,只讲落地:函数调用和 JSON Schema 严格模式各自在什么条件下必须用、什么条件下会反咬你一口,以及超时、重试、降级、日志和评测该怎么排。
先给一个粗糙但好用的直觉:函数调用解决「模型该动手做什么」,严格模式解决「模型交回来的东西长得对不对」。你要的是结构正确还是取值正确,决定了主选哪一个、另一个当兜底。下面逐步拆开。
先把两条路分清:它们到底约束了什么
函数调用本质是让模型挑一个函数并填参数,schema 是提示而不是硬约束,模型理论上可以填错类型、少字段、多塞一个自造字段。严格模式是把 schema 编译进解码过程,输出在结构层面必须合法,凑不出合法结果时才报错或被截断。
| 对比项 | 函数调用 | JSON Schema 严格模式 |
|---|---|---|
| 约束对象 | 选哪个工具、传什么参数 | 返回体的字段与类型 |
| 字段可否缺失 | 可能缺,尤其参数多时 | 未标记必需的字段可不给,标了必给就必须给 |
| 类型漂移 | 常见,数字变字符串 | 基本锁死,除非 schema 本身写宽 |
| 多余字段 | 可能自造 | 通常被禁止 |
| 适合的活 | 多步工具编排 | 单轮抽取、固定表单 |
判断口径先立住:确认你要的是结构正确还是取值正确。前者能靠 schema 兜住,后者只能靠业务校验加重试,schema 再严也拦不住模型填一个合法但不合理的 ID。另外别默认所有模型的严格模式都真严格,有相当一部分是软严格,靠后处理和重试兜底,这点必须自己拿固定样例实测。
兼容性:先看你的调用链能不能吃下它
严格模式对 schema 子集有限制,复杂嵌套、联合类型、正则、默认值这些常常不被支持。写 schema 之前先按最小可用子集设计,把校验逻辑放到应用层,而不是塞进 schema 里赌它能编译。
- 先用最小可用子集写 schema,能扁平就扁平,别一上来玩五层嵌套
- 接入前拿一份固定样例跑对比:同一份 schema 走不同协议入口,看是否被降级成普通 JSON 提示
- 多模型混用时按模型分别记录:支不支持严格模式、失败时返回什么、错误长什么样
- 下游只做文本展示、不解析字段的场景,严格模式收益很低,别为它牺牲模型选择面
- schema 每次改动都重跑一遍固定样例,行为可能跟你上次测的不一样
多步工具编排与失败处理:这才是真正分叉的地方
需要模型连续调多个工具、每步结果影响下一步时,优先函数调用,它天然带「选谁、传什么、拿回什么」的往返结构。严格模式更适合一次输入一次输出、结构稳定的场景,不需要模型自己决定调什么。
- 分类失败把失败拆成三类:结构不合法、取值越界、工具执行失败。三类处理方式完全不同,混在一起排就永远排不清。
- 分别处理结构不合法可自动重试;取值越界必须过业务校验并让人看到;工具执行失败要把错误信息回灌给模型,让它调整参数,而不是原样重试。
- 设上限和退避重试要有次数上限和退避间隔,连续失败就降级到宽松解析加人工兜底,别让它无限循环烧额度。
- 留痕每次重试都记下请求标识和失败类型,否则线上出问题时你只能看到一堆失败请求,分不清是哪一类。
这一节的分野很干脆:模型需要自己决定下一步做什么,用函数调用;模型只需要稳定地产出结构化结果,用严格模式。两者混着上,先想清楚谁是主干谁是兜底。
上线前的落地清单与排查顺序
上生产之前,先把三张表写下来:schema 字段最小集、每个字段的合法取值范围、失败时的动作。这三张表没有,后面的日志和重试都是瞎撞。日志至少记请求标识、命中方式、是否重试、重试次数、最终是否通过校验、耗时,缺一项线上就少一条线索。排查顺序固定下来:先看原始返回是不是合法 JSON,再看字段是否齐,再看取值是否符合业务,最后才怀疑模型和提示词。多数人一上来就改提示词,往往在修一个根本不在提示词层的问题。
| 排查顺序 | 看什么 | 常见结论 |
|---|---|---|
| 一 | 原始返回是否合法 JSON | 不合法先查严格模式是否被降级 |
| 二 | 字段是否齐全 | 缺字段查 schema 必需标记 |
| 三 | 取值是否符合业务 | 合法但不合理,属校验缺失 |
| 四 | 模型与提示词 | 前面都过了才轮到改这里 |
评测别只看一个通过率,把首次通过率和加两次重试后的通过率分开记,两者的成本差很大。要不要换模型、值不值得开严格模式,模型口径和价格口径到 /models 和 /official-api 核对当时的数字再决定。
这两种方式都不适用的时候
有些场景强行上结构化输出是负收益。识别出来比选型更重要,因为一旦上错,改回来的代价比重写提示词大得多。
- 输出是大段自由文本再从中抽取:先让它自由写,再用规则或二次抽取,强行套 schema 会损害内容质量
- schema 一天改几次:先把字段稳定下来再上严格模式,否则你在维护一份永远追不上的约束
- 对延迟极敏感的在线链路:严格模式加校验加重试会拉长尾延迟,先用 /tools/speed-test 量出基线再决定
- 需要跨厂商可移植:函数调用的事实标准更通用,严格模式各家实现差异更大,可移植性差
- schema 本身还在讨论阶段:别拿严格模式当需求澄清工具,它只会放大没想清楚的部分
结构正确能靠 schema 兜住,取值正确只能靠校验和重试兜住,别把两件事混成一个开关。下一步拿你现有的一个接口,按排查顺序跑一遍,把首次通过率和加两次重试后的通过率分开记;再对照中转与协议入口的差异,看你的严格模式有没有被悄悄降级。→
常见问题
严格模式能保证模型一定按我的 schema 输出吗?
不能保证「一定」,只是把结构错误的概率压得很低。它约束的是格式合法性,不约束取值是否符合业务含义。字段值对不对、枚举有没有语义错,仍然要在你这边做校验,并且把校验失败当成正常分支来设计,而不是异常。
多步工具调用一定要用函数调用吗?
如果每一步由模型自己决定调哪个工具、传什么参数,用函数调用最省事。反过来,如果你的流程是固定的几步、每步该调什么已经写在代码里,那模型只负责填参数,严格模式或普通 JSON 输出都够用,还更便宜。
两者能不能一起用?
可以,常见做法是函数调用负责编排,工具返回后再用严格模式抽字段。但要注意两套约束叠加会放大兼容性问题,尤其走中转时。建议先在同一模型上分别跑通,再组合,并保留关掉严格模式的开关。