超越 JSON 模式:在生产环境中获取可靠的 LLM 结构化输出
你部署了一个从支持工单中提取客户意图的流水线。你已经对其进行了广泛测试。它运行良好。发布三天后,一个警报被触发:下游服务因 KeyError: 'category' 而崩溃。模型开始返回 ticket_category 而不是 category —— 提示词(prompt)没有改动,只是你的提供商悄悄推行了一次模型更新。
这就是结构化输出问题。而 JSON 模式并不能解决它。
让 LLM 生成可靠符合特定形状的机器可读输出,是那种看起来微不足道 —— “只需告诉它返回 JSON 即可” —— 直到它在凌晨 3 点于生产环境中崩溃的问题。失败模式很微妙,解决方案是分层的,而不同方法之间的权衡非常重要,具体取决于你是在运行云端 API 还是自托管推理。
为什么“返回 JSON”不是一种策略
想要通过类似 "Respond only with valid JSON in the following format: ..." 的提示词来解决结构化输出问题的直觉是可以理解的。它在测试中运行得足够好,让人觉得问题已经解决。但仅靠提示词的 JSON 提取失败率在 5–20% 之间(取决于 Schema 的复杂程度),而且这些失败往往以最糟糕的方式成簇出现。
最常见的失败情况:
- 前导语污染 (Preamble contamination)。模型输出
"Sure! Here's the JSON you requested: {...}"—— 这对任何 JSON 解析器来说在语法上都是错误的。 - 幻觉键名 (Hallucinated keys)。模型发明了 Schema 中不存在的字段名称。你要求
status,却得到了current_state。两者对模型来说在语义上都有意义;但对你的解析器来说只有一个是正确的。 - 缺失必需字段 (Missing required fields)。模型没有为它不知道的字段返回
null,而是直接忽略了它们。 - 类型漂移 (Type drift)。你在需要整数
42的地方得到了字符串"42"。 - 结构中途截断 (Truncation mid-structure)。在接近 Token 限制的长输出中,模型会突兀地结束,留下未闭合的括号。
JSON 模式(response_format: { type: "json_object" })解决了第一个问题 —— 它防止了明显的 JSON 格式错误 —— 但对其他四个问题无能为力。它保证的是有效的 JSON 语法,而不是 Schema 一致性。
解决方案的四个阶段
该领域已经演变出四种不同的方法,每种方法都解决了问题的不同层面。
第一代:提示工程 (Prompt Engineering)
这是基准方法。适用于低流量、简单 Schema 且对错误宽容的场景。但在大规模运行、复杂 Schema 或模型更新后会失效。正确的心理模型是,基于提示词的方法将 Schema 一致性视为对模型的“软指令”,而不是硬性约束。模型可以而且一定会发生偏离。
第二代:函数调用与工具使用 (Function Calling and Tool Use)
从 2023 年开始,主要的模型提供商开始微调模型,以生成符合 JSON Schema 的函数参数。当你定义一个带有 Schema 的工具或函数时,模型会产生紧贴 Schema 的语法有效输出。这并非数学上的保证 —— 模型是被微调以将 Schema 视为指令 —— 但与仅靠提示词的方法相比,可靠性有了巨大提升。
函数调用目前是大多数云端 API 用户的实际默认选择。它在 OpenAI、Anthropic、Cohere 和 Groq 上都能运行,且行为基本一致,尽管边缘情况有所不同。
