05. 结构化输出(JSON/XML)怎么通过 Prompt 保证稳定性?
整理 JSON/XML 结构化输出的稳定性问题、Prompt 约束、API 约束与容错机制。
简单回答
结构化输出的稳定性是 LLM 工程化落地的常见难点——模型可能漏字段、加多余字段、格式错误、或者在某些输入下输出解析失败。保证稳定性的方法从低到高有四层:Prompt 里明确规定格式(给 schema 或示例)、要求模型先确认再输出、用支持结构化输出的 API(如 OpenAI 的 JSON Mode / Structured Outputs)、以及在应用层做解析容错和重试逻辑。工程上这四层最好都做,而不是只依赖其中一层。
详细解答
为什么结构化输出不稳定
大模型的训练目标是语言建模(预测下一个 token),不是"遵守 JSON 语法"。模型在生成 JSON 时,每个字符都是靠概率预测出来的,没有语法级别的约束。特别是在以下情况下容易出错:
字段值里包含引号(导致 JSON 转义问题);字段值很长,模型在生成中途出现截断或跑偏;输入内容触发了模型的对话倾向(比如输入是一个问题,模型想直接回答而不是填 JSON 字段);少见的字段名或嵌套层数深的结构,模型"没见过多少"。
随着模型能力提升,GPT-4、Claude 3.5 这类强模型的结构化输出可靠性已经大幅提升,但在生产环境里"几乎不出错"和"完全不出错"之间的差距仍然很大,工程上必须处理失败 case。
Prompt 层面的最佳实践
用 Schema 而非自然语言描述格式:与其写"请输出一个包含 name、age 和 city 字段的 JSON",不如直接给出 JSON Schema 或一个完整的示例。示例比描述更精确:
把格式要求放在 Prompt 末尾:模型在生成时对最近的上下文权重更高(Lost in the Middle 效应)。把格式要求放在用户输入之后、临近生成的位置,比放在 System Prompt 开头遵从效果更好。典型结构:[任务描述] + [用户输入] + [格式约束]。
用分隔符隔离指令和内容:用 XML 标签或 --- 这类分隔符把格式指令和要处理的内容清晰隔开,减少内容对格式指令的干扰:
要求"只输出 JSON,不要其他内容":明确禁止模型在 JSON 前后加入解释性文本("当然,以下是分析结果:"……),否则解析时需要先找到 JSON 的边界。
API 层面:JSON Mode 和 Structured Outputs
JSON Mode(OpenAI、许多其他 API 支持):强制模型输出有效的 JSON 字符串,通过在 decoding 阶段做语法约束实现——在每个 token 生成时,过滤掉所有会导致 JSON 语法错误的 token,只保留合法 token。这保证了语法合法性(不会有格式错误),但不保证字段的完整性和内容的正确性。
Structured Outputs(OpenAI 2024 引入):在 JSON Mode 基础上进一步升级,可以指定完整的 JSON Schema(包括必须有的字段、字段类型、枚举值等),模型输出被约束为完全符合 Schema 的 JSON。这是目前工程上最可靠的结构化输出方案。
对于没有原生 Structured Outputs 支持的模型,outlines、guidance、lm-format-enforcer 等开源库提供了类似的 grammar-constrained decoding 能力,可以在本地部署的模型上使用。
应用层的容错处理
即使做了上面所有工作,生产环境里仍然要有应用层的容错机制:
解析失败后重试:捕获 JSON 解析异常,重新请求模型。通常失败后重试一次的成功率就很高(很多失败是模型在某次请求里恰好出了问题)。但重试要有次数限制,避免无限循环。
宽松解析:用比严格 JSON 解析更宽容的方式处理输出——比如用正则表达式提取 JSON 子串(处理前后有多余文本的情况)、尝试修复常见错误(末尾多了一个逗号、引号未转义等)。json_repair 这类库专门做 LLM 输出的 JSON 修复。
Fallback 处理:如果所有重试都失败,有明确的 fallback 逻辑(返回默认值、触发人工审核、或者给用户友好的错误提示),而不是直接让整个系统崩溃。
面试时可以这样答
结构化输出不稳定是很常见的工程问题,工程上应该分四层来保证。
Prompt 层:给 Schema 或示例而不是用自然语言描述格式,更准确;格式要求放在 Prompt 末尾,紧靠生成位置,比放在开头遵从率更高;用 XML 标签或分隔符把指令和内容隔开;明确说"只输出 JSON,不要任何其他内容"。
API 层:OpenAI 的 Structured Outputs 可以指定完整 Schema 做语法级别的约束,是目前最可靠的方案。对于本地模型,outlines、lm-format-enforcer 这类库能实现类似效果。
应用层:解析失败要有重试逻辑(失败重试一次成功率很高);用宽松解析处理常见的小错误(末尾多逗号、多余文本);有明确 fallback,不能让整个系统因为一次解析失败就崩。
实际工程里这几层最好都做,不能只靠 Prompt 层,因为强模型在边界情况下也会偶尔输出格式错误。
常见追问
- Structured Outputs 的 grammar-constrained decoding 是怎么实现的?会不会影响生成质量?
- 如果 JSON 的某个字段值是自由文本(比如"用户的反馈"),该字段怎么防止特殊字符导致格式错误?
- XML 和 JSON 作为结构化输出格式,哪个对 LLM 更友好?