从 LLM 里取出 JSON
礼貌地要 JSON,多数时候能拿到 JSON。「多数时候」对代码来说不够,而有些办法能让它变成保证。
一旦把模型放进程序里,你就需要机器可读的输出。散文对人友好,对解析器毫无用处。
开口要 JSON,多数时候能成。真正要紧的是失败率,因为一万次调用里 2% 的解析失败,就是 200 起事故。
为什么会失败
模型是在预测 token,不是在填模板。在一条 JSON 形状的指令后面,合法 JSON 是概率很高的续写,但机制里没有任何东西强制它如此。
所以那些典型的失败,恰恰就是 token 预测会产出的东西:
- JSON 外面裹了一层散文——对象前面多一句「这是你要的数据:」
- 外面套了 Markdown 代码围栏
- 多一个尾随逗号,或者缺一个右花括号
- 字段名从
user_id飘成了userId - 枚举值差了一点——你指定的是
POSITIVE,它给了positive - JSON 要求双引号的地方用了单引号
- 被截断——对象跑过了 token 上限
四种办法,从最弱到最强
1 · 开口要,并给出示例
在 prompt 里写清 schema,再附一个例子。少样本示例在这里格外有效,因为格式正是示例最擅长传达的东西。
Return only a JSON object matching this shape, with no other text:
{"sentiment": "POSITIVE" | "NEGATIVE" | "NEUTRAL", "confidence": 0.0-1.0}
Example input: "This is fine I guess"
Example output: {"sentiment": "NEUTRAL", "confidence": 0.6}便宜,任何模型都能用,但仍然会时不时失败。
2 · JSON 模式
很多服务商提供一个开关,把输出约束成语法合法的 JSON。这消灭了解析错误——但只管语法。你仍可能拿到字段错误的合法 JSON,所以 schema 还是得在 prompt 里说清楚。
3 · schema 约束生成
目前最强的选项。你提供一份真正的 schema,服务商据此约束生成过程,只有该 schema 允许的 token 才可能被采样出来。
原理是在每一步屏蔽概率分布:如果 schema 规定下一个必须是 "sentiment",那么会违背这一点的 token 会在采样之前被置零。畸形输出从「不太可能」变成了结构上不可能。
叫法很多——结构化输出、引导式解码、语法约束解码——只要服务商提供,它就是默认该选的方案。
4 · 工具定义
如果你已经在用工具调用,工具参数天然就是经过 schema 校验的。纯粹为了接收结构化数据而定义一个「工具」是种正当做法,而且 schema 的强制约束是白送的。
设计 schema
schema 的形状影响的不只是便利,还有可靠性。
扁平胜过嵌套。 深层嵌套的对象失败更频繁。两次扁平调用通常好过一次深度嵌套的调用。
枚举胜过自由文本。 "status": "APPROVED" | "REJECTED" 远比让它自己写一个状态字符串可靠。
字段名要短。 每个字段名都是 token,每次调用都要重复一遍。
给不确定性留个出口。 如果没有 null 选项或 UNKNOWN 枚举值,模型宁可编一个值,也不肯让必填字段空着——这和驱动它在别处编造的「必须给出答案」的压力是同一股力量。
需要推理就先推理后给数据。 把 reasoning 字段放在对象的最前面。答案之后生成的字段影响不了答案,所以 {"answer": ..., "reasoning": ...} 产出的是事后合理化,而 {"reasoning": ..., "answer": ...} 产出的才是真正的分步推理。
在代码里怎么处理
即便用了约束生成,防御性解析也是必要的:
- 解析后仍要按 schema 校验。 语法合法不等于语义正确。
- 解析前先剥掉代码围栏。 便宜、回报高的防御。
- 解析失败就重试一次,并把错误写进重试的 prompt。多数失败是暂时的。
- 留意截断。 对象在生成中途被切断,说明你的输出上限设低了,重试多少次都修不好。
- 用
temperature0。 结构化抽取要的是确定性,不是多样性。
结构的代价
约束输出有一个值得知道的质量代价。把模型逼进一个僵硬的形状,可能损伤形状里的内容,尤其当 schema 与模型组织信息的天性相冲突时。
如果加了约束之后质量明显下降,通常的解法是简化 schema,或者改用两步走:先自由生成,再用一次便宜的调用把它抽取成结构。
要点回顾
- JSON 的各种失败都是 token 预测的副产物:代码围栏、散文、飘移的字段名、截断。
- 升级路径:开口要并给示例 → JSON 模式(只管语法)→ schema 约束生成(结构上保证)→ 工具定义。
- 优先用扁平的 schema、枚举、短字段名,以及一个显式的不确定性出口。
- 推理字段要放在答案字段前面,否则它不起作用。
- 解析之后照样要校验,并用
temperature0。
下一篇: Prompt 不灵了,接下来怎么办?