结构化输出:从模型拿到可靠的 JSON
当你的代码需要的是数据而非散文时,模型必须返回干净、可解析的结构。本文讲如何拿到可靠的 JSON,而不是只能靠祈祷。
一个能写出漂亮段落的语言模型,对人是有用的。而一个要去喂给另一个程序的语言模型,得做一件更难的事:每一次都返回你的代码能解析的形态的数据,不出意外。当你不再亲自阅读输出,转而把它传给 json.parse 的那一刻,散文就成了负债,结构成了硬需求。本文要谈的,是弥合"模型通常会返回某种像 JSON 的东西"和"我的流水线可以依赖模型的输出"之间的那道鸿沟——因为在生产环境里,"通常"和"定时坏掉"是一回事。
为什么散文不够
当模型回答一个人时,小小的瑕疵会消融在人类的灵活性里。读者不会在意答案是不是以"好的,这就给你:"开头、是不是把数据裹在代码围栏里、字段的措辞是不是和上次略有不同。可解析器在意所有这些。数据前一句多余的话、一个尾随的逗号、一个时而是数字时而是"unknown"这个词的字段——任何一个,都能把一条本来好好的流水线变成一段调用栈报错。
核心问题在于,模型被训练去产出貌似可信的文本,而貌似可信的文本和有效的结构化数据不是一回事。放任不管,模型恰恰会在你需要它死板、机器可读的时候,飘向乐于助人、爱聊天的方向。拿到可靠的结构化输出,就是消除这种飘移的工作。这里有好几根杠杆,而且它们可以叠加。
杠杆一:精确地提要求
最便宜的改进,也是最常被跳过的:告诉模型你想要的确切形态,并把它展示出来。含糊的指令产出含糊的结构。一句"把详情以 JSON 返回"的请求,把字段名、嵌套和类型都留给模型去发明,而它每次调用都会发明得不一样。
正确的做法是具体地指定 schema。给每个字段命名、声明它的类型、说明它是否必需,还有——这是人们会漏掉的部分——展示一个完整的有效响应示例。模型极其擅长照着示例做模式匹配,一个格式良好的样本,在锁定格式上比一整段描述管用得多。也要把要紧的规则明确写出来:输出必须只有 JSON、不带任何周围文字;缺失值应以某种特定方式表示,而不是被省略或被猜测;字段集合是固定的。请求中的精确,是后续一切的根基。跳过它,后面的杠杆就是在给你自己造出来的问题打补丁。
杠杆二:使用模型的结构化输出特性
好言相请有帮助,但单凭指令,仍给模型留了游荡的余地。如今大多数严肃的 LLM 提供方都专门提供了针对结构化输出的特性,用上它们,比单纯靠提示词是一大步飞跃。
这些特性有几种形态。较轻的形式是一种把输出约束为有效 JSON 的模式——模型被阻止吐出任何语法上不规范的东西,这消除了"它加了一句话""它用了代码围栏"这一整类失败。更强的形式让你提供一个输出必须遵从的 schema,于是结果不只是有效的 JSON,而是你所要求的那个确切形态的有效 JSON,字段和类型都对。
哪里有这些特性,就优先用它们,而不是手搓提示词。它们把保证从"模型被要求这么做"移向"系统强制这么做",而这种转移就是全部的关窍所在。查阅你提供方的文档,看看有什么可用、怎么调用,因为具体细节各有不同,但原则恒定:让平台去强制结构,而不是依赖模型的善意。
杠杆三:信任之前先校验
即便有了最好的提示词和最强的结构化输出特性,也要把模型的输出当成不可信的,直到你检查过它。这不是多疑;这和你对待任何外部输入会用的纪律是同一套。校验有两层,两层你都要。
- 结构校验确认输出能解析并匹配 schema:该有的字段都在、类型正确、必需值没缺。这能抓住那些溜过上游一切环节的畸形响应。
- 语义校验确认内容在你的领域里说得通:一个日期是真实的日期、一个类别是你允许值之一、一个数量在合理范围内、一个被引用的标识符确实存在。一份响应可以是完美有效的 JSON,内容却仍是胡说八道,而只有领域检查能抓住这一点。
把校验当成一道关卡来跑,而不是事后补救。没通过这道关的输出,绝不该当作没事一样抵达你系统的其余部分。在关卡处该做什么,是下一根杠杆的主题。
杠杆四:处理你仍会遇到的失败
上述方法的任何组合都不完美,所以要为残留的失败做设计,而不是假装它们不会发生。当校验失败时,你有几个明智的选项,大致按偏好排序。
第一是有界重试。 许多结构化输出的失败是一次性的,只要再问一遍——最好告诉模型上一次尝试错在哪——就能成功。给重试设个上限,免得一个持续性失败永远循环下去。第二,针对那些微小而可预测的问题,是修复:剪掉多余的代码围栏、修正明显的格式、把一个差一点点的结果强转成期望的形态。修复要保持窄而保守,因为激进的自动修复会掩盖真问题,还可能损坏数据。第三,当重试和修复都用尽时,是一次干净、有日志的失败——把这个案例路由到一条兜底路径或人工复核,而不是把坏数据传往下游,并把它记入日志,这样你能看出规律。一个字段总是过不了校验,那是在告诉你去修你的提示词或 schema,而不是去再加一条修复规则。
让 schema 在任务允许的范围内尽量简单
一根更安静、却值得单独一提的杠杆:你所要求之物的复杂度,直接影响你能多可靠地拿到它。深度嵌套的对象、一长串可选字段、繁复的条件结构,对模型来说都比一个扁平、小巧、只含必需字段的形态更难稳定产出。在你伸手去用花哨的提示词来摆弄一个巴洛克式 schema 之前,先问问这 schema 是否真需要那么巴洛克。你常常可以把一次复杂的抽取拆成两次简单的,或者把一个为了整洁而非必要才嵌套的结构压平。最可靠的结构化输出,是你没有过度复杂化的那个结构。
总结
从模型拿到可靠的 JSON,是一摞相互加固的习惯,而非单一的诀窍。精确地提要求,并展示一个示例。使用你提供方的结构化输出特性,让平台去强制形态,而不是模型只是有此意图。校验每一份响应——结构上和语义上——并把它当成不可信的,直到它通过。用有界重试、窄修复和干净兜底,为残留的失败做设计。并让 schema 不超过任务所需的复杂度。把这些一起做到,你就跨过了生产环境里那条要紧的界线:从一个通常返回可解析数据的模型,跨到一条真正可以依赖它的流水线。
