Chapter 5 · 用 Schema 替代格式自由
LLM 输出的最常见痛点不是"它说错了",而是"它说对了但格式飘了"。这一章告诉你如何让模型 100% 输出严格 JSON——并理解这背后的解码机制。
Item 13:别再"求 JSON"——用原生 response_format
在 prompt 里写 "请输出 JSON" 是 2023 年的做法。
核心
OpenAI / Anthropic / Google 都已支持 response_format={"type": "json_schema", ...}。这条路径是在解码层做受限采样(mask 掉不合 schema 的 token),而不是希望模型"自觉"输出合法 JSON。
Things to Remember
- 用原生
response_format而非 prompt 内 JSON 示例。 - Schema 必须包含
additionalProperties: false,否则模型会偷加字段。 - 字段类型必须显式(
integer而非number)。
Item 14:用 Constrained Decoding 锁死字段集
100% 合法 JSON 不是奇迹,是数学。
核心
Outlines(arXiv:2307.09702)的方法是把 Schema / 正则 / CFG 编译成有限状态机,每生成一个 token 都根据 FSM 的当前状态把所有"非法 token"的 logit 设成 -inf。理论上字段越界概率为 0,且性能开销 < 1%(XGrammar,arXiv:2411.15100)。
Sidebar:FSM 受限解码内幕
为什么 Outlines / XGrammar 比 prompt 受控强这么多?因为前者是架构层保证——FSM 在每一步都准确知道"下一个合法 token 集合",模型只能在这个集合里选概率最高的;后者是统计性祈祷——希望模型 99% 的时候说对。当你的产品需要 99.99% 时,前者才是唯一选项。
Things to Remember
- 自部署模型用 Outlines / XGrammar /
llama.cppGBNF。 - 闭源模型用 OpenAI / Anthropic 原生 structured output(底层等价 FSM)。
- Constrained decoding 不是减少错字段,是消灭错字段。
Item 15:让 enum 替代自由文本
"高/中/低" 就比 "请用一句话评估优先级" 更稳。
引子
所有可枚举字段(优先级、情感、类别)都应该用 enum,让模型只能从给定值中选。这不仅消灭拼写差异("高优先级" / "高优" / "重要"),还把下游解析从"模糊匹配"降级为"严格相等",可观测性也随之提升(直接 group-by 即可统计)。
反例 vs 正例
json
// Bad
{"priority": "string"}
// 模型会输出 "高"、"高优先级"、"high"、"重要" 各种变体
// Good
{"priority": {"type": "string", "enum": ["P0", "P1", "P2", "P3"]}}Things to Remember
- 凡是可枚举的概念,一律用 enum——不要为了"灵活"留自由文本。
- 加 enum 后 maybe 再加 description 解释每档含义,对模型选档准确性有显著帮助。
- 后端按 enum 严格相等校验,遇到非 enum 值直接拒绝(fail fast)。