结构化输出:约束解码保证语法,保证不了语义

T1-2模块 1 · Prompt 与上下文工程面试权重 更新于 2026-08
前置T0-3
关联题目Q1-10

这篇学完你能回答什么

  1. 约束解码和「在提示词里要求返回 JSON」有什么本质区别?
  2. 有了结构化输出,还需要写校验吗?为什么?
  3. schema 能表达什么、不能表达什么?表达不了的那部分该放哪儿?

从一个真实故障讲起

一个订单改期与退款的 Agent,模型返回 JSON 驱动下游 API。它的事故史刚好分成三个阶段,正好是这一篇的主线。

第一阶段:模型偶尔在 JSON 前面加一句「好的,这是你要的结果:」,json.loads 直接抛异常。团队上了约束解码,这类问题清零。所有人都松了口气。

第二阶段:两周后出现更棘手的——JSON 完全合法、字段齐全、类型正确,但 new_date"2026-02-30"。语法无懈可击,日期不存在。

第三阶段refund_amount: 99999。schema 里没写上限,模型「合理地」填了一个数,下游照单执行,钱打出去了。

第一类问题约束解码解决了;后两类,它在结构上不可能解决。搞不清这条边界的团队,会在上了结构化输出之后放松警惕,然后在更贵的地方摔一跤。

第一类清零了,后两类更贵
订单改期与退款 Agent 的三阶段事故史:parse 报错 → 日期不存在 → 钱打出去了

  1. JSON 前多了句寒暄

    「好的,这是你要的结果:」,json.loads 抛异常

  2. 上约束解码

    这类语法问题清零,所有人松了口气

  3. 两周后:日期不存在断点

    JSON 合法、字段齐、类型对,2026-02-30

  4. 金额填了 99999

    schema 里没写上限,下游照单执行,钱打出去了

第一类 · 语法偶尔加寒暄、json.loads 抛异常约束解码之后清零
第二类 · 值不真语法无懈可击2026-02-30 这一天不存在
第三类 · 越权99999 是合法整数钱照单打了出去
后两类的性质以为约束解码会管它在结构上不可能解决
约束解码从来没打算管日期真不真、金额合不合规 —— 是团队误以为它管。搞不清这条边界的人,会在上了结构化输出之后放松警惕,然后在更贵的地方摔一跤

核心概念:先打比方,再给定义

Structured outputs(结构化输出)。让模型的输出严格符合你给定的 JSON Schema。

Constrained decoding(约束解码)。它的实现机制:把你的 schema 编译成一份语法,在采样阶段就把所有会违反结构的候选 token 屏蔽掉。

比喻:不是叮嘱司机「尽量别开出这条路」,而是把所有岔路口都用护栏封死。模型不是「更听话了」,是物理上吐不出违反结构的 token

两个不同的东西,别混

名称 约束对象 用在哪
JSON 输出 模型的回复本身 你要一个结构化的答案
严格工具调用(strict tool use) 模型生成的工具调用参数 你要它调用函数时参数一定合规

JSON mode(旧的、弱的那一档)。只保证输出是合法 JSON,不保证符合你的 schema——字段可以缺、类型可以错。看到有人把这两个混为一谈时要能分辨。

不是叮嘱司机,是把岔路口封死
约束对象也分两种:一种管模型的回复本身,一种管它生成的工具调用参数

叮嘱司机 · 尽量别拐出去

路线大概率没错,几乎零成本

全靠他自觉 —— 偶尔还是会拐出去,你事后才知道

对应
提示词请求 / JSON mode

「请只返回 JSON,不要有其他文字」

模型「尽量」照做;JSON mode 保证能 parse,{"随便什么": 1} 也算合法

偶尔加寒暄字段可缺类型会飘
护栏 · 岔路口全封死

物理上开不出去,不靠自觉

护栏只管路面,管不了你开到的那个地方对不对

对应
Constrained decoding

schema 编译成语法,采样阶段封死

违反结构的候选 token 概率直接置零 —— 模型不是更听话了,是吐不出违规的 token

JSON 输出strict 工具调用语法 grammar
JSON mode 是旧的、弱的那一档:只保证输出是合法 JSON,不保证符合你的 schema —— 字段可以缺、类型可以错。看到有人把它和 schema 约束混为一谈时,要能当场分辨。

原理拆解

语法层管形状,值和权限归代码
schema 编译成语法,采样时封死违规 token —— 表达不了的那部分只能活在代码里

语法层 · 约束解码每生成一步,把不符合语法的 token 概率置零,只在合法候选里采样保证:能 parse、字段齐、类型对。编译产物缓存 24 小时,首次调用有额外延迟
值域层 · 代码校验枚举是否越界、数值范围、日期是否真实存在、格式正则"2026-02-30" 死在这一层 —— minimum / maximum 严格 schema 根本不支持
业务层 · 代码校验是否在该用户权限内、是否与来源一致、是否超过审批阈值refund_amount: 99999 死在这一层;高危动作的判断权必须收回代码

语法表达不了的,就一定管不住。截至 2026-08 官方明确列出的不支持项:递归 schema、外部 $refminimum / maximumminLength / maxLength、其他数组长度约束;additionalProperties 只能是 false

复杂度也有上限:所有严格 schema 合起来算,超了直接报编译错误。官方给的修法顺序是 —— 只给关键工具加 strict → 把可选参数改成必填 → 拍平嵌套 → 拆到多个请求。每个可选参数都是状态空间大户。

结构化输出不降低幻觉率,只是把幻觉变成格式合法的幻觉。副作用很隐蔽:parse 不再报错,你反而丢了一道天然告警 —— 第 2、3 层因此更重要,不是更不重要。
原文示意
路线一:提示词请求
  "请只返回 JSON,不要有其他文字"
      → 模型「尽量」照做 → 大概率对,偶尔加寒暄 / 缺字段 / 类型飘

路线二:JSON mode
  → 保证能 parse,但 {"随便什么": 1} 也算合法

路线三:约束解码(当前默认选择)
  你的 JSON Schema
      │ 编译
      ▼
   语法(grammar)
      │
      ▼
  每生成一步:把不符合语法的 token 概率置零 → 只在合法候选里采样
      → 结构上不可能违规
# 约束解码在做什么(示意)
for step in range(max_tokens):
    logits = model.next_token_logits(state)
    allowed = grammar.allowed_tokens(parse_state)   # 语法告诉你此刻哪些 token 合法
    logits[~allowed] = -inf                          # 其余全部封死
    tok = sample(logits)
    parse_state = grammar.advance(parse_state, tok)

关键限制(这是本篇最值钱的一段)

既然 schema 要被编译成语法,那语法表达不了的约束,就一定管不住。截至 2026-08,官方文档明确列出的不支持项包括:递归 schema、外部 $refminimum / maximumminLength / maxLength、其他数组长度约束;additionalProperties 只能是 false。此外还有整体复杂度上限——所有严格 schema 合起来算,超了会直接报编译错误,官方给的修法顺序是:只给关键工具加 strict → 把可选参数改成必填 → 拍平嵌套 → 拆到多个请求。(每个可选参数都会显著放大语法的状态空间,是复杂度大户。)编译产物会缓存 24 小时,所以首次调用有额外延迟,高频调用可以忽略。

把这些串起来就是本篇的结论:

数值范围、字符串长度、日期是否真实存在、金额是否在权限内、引用是否与来源一致——这些全部属于语义,语法层根本表达不了。它们必须活在你的代码里。

回头看开头的故障:"2026-02-30" 是合法字符串;99999 是合法整数。约束解码从来没打算管这两件事,是团队误以为它管。

工程实践(截至 2026-08)

表 1:四条路线选型

路线 保证什么 什么时候用
提示词里要求返回 JSON 什么都不保证 模型或端点不支持约束解码时的兜底
assistant 预填 { 曾经的经典技巧 已失效:截至 2026-08,Claude Opus 4.7 及以后返回 400,官方要求改用结构化输出
JSON mode 语法合法,schema 不保证 老模型
约束解码(JSON 输出 / strict 工具) 符合 schema 的结构 默认选择

表 2:三层校验(可复用骨架)

原文示意
第 1 层 · 语法层   约束解码     → 能 parse、字段齐、类型对
第 2 层 · 值域层   代码校验     → 枚举是否越界、数值范围、日期是否真实存在、格式正则
第 3 层 · 业务层   代码校验     → 是否在该用户权限内、是否与来源一致、是否超过审批阈值

一句话:能用 schema 表达的交给 schema,不能表达的必须写代码,绝不写进提示词指望模型自觉。

这条和 Agent 章的判断顺序是同一个逻辑:能代码判的用代码判 → 能变可逆的变可逆 → 剩下的才人工确认(Q3-19)。退款金额是否合理,属于第一类,根本轮不到模型来判断。

避坑清单

  1. 别把 schema 当校验器。 它是格式契约,不是业务规则引擎。
  2. 可选字段要克制。 每个可选参数都在放大语法状态空间;能必填就必填,用一个显式的「未知」枚举值代替「字段缺失」。
  3. 枚举优先于自由文本。 这是唯一一类「语法层能顺便管住语义」的字段——能枚举就绝不让模型自由发挥。
  4. 别设计一个模型填不满的 schema。 强行要求一堆它无从得知的必填字段,会诱导它编造,因为语法不允许它空着。
  5. 结构化输出不降低幻觉率,只是把幻觉变成格式合法的幻觉。副作用是:parse 不再报错,你反而失去了一道天然告警,第 2、3 层校验因此更重要,不是更不重要。
  6. 注意档位交互:过高的 effort 在结构化输出这类任务上可能过度思考,收益递减(T0-5)。
能用 schema 表达的交给它,其余写代码
四条路线只有一条是默认选择;避坑里最贵的一条是「别把 schema 当校验器」

四条路线
路线保证什么什么时候用
提示词里要求返回 JSON什么都不保证模型或端点不支持约束解码时的兜底
assistant 预填 {曾经的经典技巧已失效:截至 2026-08,Claude Opus 4.7 及以后返回 400,官方要求改用结构化输出
JSON mode语法合法,schema 不保证老模型
约束解码(JSON 输出 / strict 工具)默认选择符合 schema 的结构结构上不可能违规 —— 当前的默认选择
三层校验与避坑
三层骨架
语法层交给约束解码;值域层与业务层一律代码校验,绝不写进提示词指望模型自觉
可选字段要克制
每个可选参数都在放大语法状态空间;能必填就必填,用一个显式的「未知」枚举值代替字段缺失
枚举优先
唯一一类「语法层能顺便管住语义」的字段 —— 能枚举就绝不让模型自由发挥
别设计填不满的
强行要求一堆它无从得知的必填字段,会诱导它编造,因为语法不允许它空着
判断顺序
能代码判的用代码判 → 能变可逆的变可逆 → 剩下的才人工确认(Q3-19)
档位交互
过高的 effort 在结构化输出这类任务上可能过度思考,收益递减(T0-5)
schema 是格式契约,不是业务规则引擎 —— 退款金额是否合理,属于「能代码判的」那一类,根本轮不到模型来判断。

面试视角

第二层就见功力:还要不要校验
Q1-10 在工程向一二面几乎必问,平台组尤其爱问;第一层浅,第二层见真章

  1. 先讲机制差异请求 vs 封路,把「约束解码」这个词说准
  2. 明确它保证的是语法不是语义
  3. 主动给不支持清单数值范围、字符串长度、日期真实性
  4. 把高危判断收回代码给三层校验骨架,退款金额不归模型判
只读过:这些回答会暴露你
  • 以为「开了结构化输出就不用校验了」—— 最贵的一条误解
  • maximum 写在 schema 里,就以为管住了金额
  • 分不清 JSON mode 和 schema 约束,把两者当同一件事
  • 还在推荐 assistant 预填 {,不知道它已经返 400
真做过:这些细节骗不了人
  • 知道 schema 只支持子集,说得出至少两条不支持项
  • 把高危动作的判断,彻底移出模型的输出链路
  • 主动指出「结构化输出让幻觉变得格式合法,反而更难发现」
  • schema 复杂到编译报错时,先改可选参数而不是先拆模型
收口那句最省力:约束解码把「模型愿不愿意听话」变成了「模型能不能违规」,但它管的只有形状 —— 值对不对、能不能做,永远是你代码的责任。

面试官怎么问。 Q1-10 在工程向一二面几乎必问,平台组尤其爱问。它的第一层很浅(怎么让它返回 JSON),第二层就见功力:「有了这个还要不要校验」。

答题结构建议

  1. 先讲机制差异(请求 vs 封路),把「约束解码」这个词说准;
  2. 明确它保证的是语法
  3. 主动给出表达不了的清单(数值范围、长度、日期真实性);
  4. 给出三层校验骨架,并把高危字段的判断权收回代码。

分水岭信号

  • 只读过:以为「开了结构化输出就不用校验了」;分不清 JSON mode 和 schema 约束;还在推荐预填 { 这个技巧;用 maximum 写在 schema 里就以为管住了金额。
  • 真做过:知道 schema 只支持子集,说得出至少两条不支持项;能主动指出「结构化输出让幻觉变得格式合法,反而更难发现」;schema 复杂到编译报错时知道该先改可选参数而不是先拆模型;把高危动作的判断彻底移出模型输出链路。

小结与延伸

一句话收口:约束解码把「模型愿不愿意听话」变成了「模型能不能违规」,但它管的只有形状;值对不对、能不能做,永远是你代码的责任。

  • 关联题目:Q1-10
  • 延伸:T1-1(few-shot 与格式约束的分工)、T3-2(工具 schema 设计)、Q3-19(分级自治与高危操作)、Q2-24(引用溯源)

继续深入

本篇归属第 1 章「LLM 与 Prompt 基础」,去做这一章的题