[控场AI]
· 5 分钟阅读· 2,816 字

约束解码如何让LLM输出严格遵循Schema

约束解码如何让LLM输出严格遵循Schema

约束解码只保证LLM输出格式合法,无法保证字段值的语义正确。

约束解码通过在 token 采样阶段施加语法掩码,把非法 token 概率置零,使输出在语法层面必然合法,格式合规率可接近 100%,对函数调用、结构化抽取等场景价值很大。但文章指出一个常被忽视的边界:语法合法不等于语义正确。解码器能保证工具调用的"形状",却可能让字段值完全错误,例如把北京的天气查询填成上海、单位填反。这类语义错误不会触发解析异常,会安静流入下游系统,比格式错误更隐蔽。因此约束解码应定位为格式保障层而非质量保障层,仍需配合字段级校验、参数范围检查等业务逻辑;同时可通过枚举、数值范围、正则等 Schema 特性把部分语义约束前移,但无法处理依赖上下文的软判断。

How Constrained Decoding Makes LLM Outputs Follow a Schema

大模型调用工具或返回结构化数据时,JSON 格式的稳定性一直是工程落地的痛点。约束解码(Constrained Decoding)被视为解决这个问题的关键手段,但一个被广泛忽视的事实是:它只能保证输出的"形状",无法保证输出的"内容"正确。

约束解码到底在做什么

约束解码的核心机制是在 token 生成阶段对采样过程施加掩码。模型每一步都会输出一个 logits 分布,而解码器根据预先定义的语法(通常是 JSON Schema 或某种形式文法)计算出哪些 token 在当前状态下是合法的,然后将非法 token 的概率直接置零,只在合法候选集中进行采样。

这样一来,无论模型内部的概率分布长什么样,最终输出的 token 序列在语法层面一定是合法的。字段名拼写正确、括号闭合完整、逗号位置无误——这些在裸采样下需要反复重试甚至后处理修复的问题,在约束解码下从生成的第一步就被杜绝了。

从工程角度看,这确实解决了很大一类问题。函数调用(function calling)、结构化抽取、API 参数生成等场景,格式合规率可以从"大部分时候正确"提升到接近 100%。

Logits 与采样:大模型每生成一个 token,都会对词表(通常几万到十几万项)输出一组实数分数,称为 logits。这些分数经 softmax 归一化后成为概率分布,解码器再依据该分布采样出下一个 token。所谓"掩码",就是把非法 token 对应的 logits 设为负无穷(等价于概率置零),使采样只在合法候选集内进行。理解这一点很关键:约束解码并不改变模型对词表其余部分的"想法",只是在最后一步做筛选,因此模型内部关于语义、事实的判断并未被修正。

形式文法与解析器:约束解码依赖的"语法"通常用 JSON Schema 或上下文无关文法(CFG)描述。解码器内部维护一个状态机或增量解析器,每接受一个 token 就推进状态,并据此判断下一步哪些 token 合法。像 Outlines、llama.cpp 的 GBNF、XGrammar 等工具走的都是这条路径。这也解释了它的能力边界:它能表达"括号是否闭合""枚举是否合法"这类结构规则,却无法表达"这个城市名是否与用户问题一致"这类依赖语义的约束。

形状对了,值可能是错的

问题出在一个容易被忽略的层面:语法合法性不等于语义正确性。

举个例子,你要求模型输出一个天气查询的工具调用,Schema 规定包含 city 和 unit 两个字段,unit 只接受 "celsius" 或 "fahrenheit"。约束解码能保证输出的 JSON 一定有这两个字段,unit 一定是这两个值之一。但如果用户问的是"北京今天多少度",模型完全可能输出 {"city": "上海", "unit": "fahrenheit"}——格式无可挑剔,内容南辕北辙。

原文用一句话精准概括了这个局限:"The decoder can guarantee the shape of a tool call while leaving its values completely wrong." 解码器能保证工具调用的形状,却可能让它的值完全错误。

这背后的原因是,约束解码只在 token 选择的边界上工作。它不干预模型对上下文的理解,不检查实体抽取是否准确,也不验证数值推理是否正确。掩码是一个语法过滤器,不是一个事实校验器。

语法合法性 vs 语义正确性:这是两个正交的维度。语法合法性只要求输出符合给定文法的产生式规则,是一个纯形式判定;语义正确性则要求输出与真实世界、用户意图、上下文事实相符。一个 JSON 串可以同时做到"结构完美"和"内容荒谬",正如示例中把北京的天气查询填成上海。因此在评测时,格式合规率(如 JSON 解析成功率)与任务准确率必须分开度量,前者接近 100% 并不意味着后者也高。

Function Calling 的典型失败:在工具调用场景中,这类语义错误尤为常见。模型可能把单位从摄氏度误填为华氏度、把日期解析成相邻的一天、把用户提到的实体替换成训练中更常见的实体,或者在多轮对话中错误地复用上一轮的参数。这些错误都不会触发解析异常,却会让下游 API 返回错误结果或产生副作用,因此往往需要额外的参数合理性校验和关键字段的二次确认。

为什么这个问题在工程中容易被低估

约束解码带来的格式合规率提升非常直观,很容易给人一种"问题已经解决了"的错觉。尤其在快速原型阶段,开发者看到 JSON 不再报错解析失败,就会默认整条链路是可靠的。

但生产环境中的失败模式往往更隐蔽。格式错误会立刻抛出异常,容易被监控捕获;而语义错误——字段值填错、实体混淆、单位搞反——会安静地流向下游系统,可能造成数据污染、错误的 API 调用、甚至业务决策失误。

一个常见的误解是认为约束解码能提升模型的推理质量。实际上它不改变模型的内部表示,只是在输出层做了截断。模型对任务的理解程度、对用户意图的把握精度,完全不受约束解码影响。

实际使用中的应对思路

既然约束解码管不了值的正确性,那它应该被放在工具链的什么位置?

把它当作格式保障层,而非质量保障层。 约束解码的职责是让下游解析器永远不需要处理 malformed JSON,这个价值本身就很大,但它不能替代输出内容的验证逻辑。

在约束解码之上,仍然需要针对具体业务场景设计校验规则。比如对抽取类任务做字段级的交叉验证,对工具调用做参数范围检查,对关键实体做二次确认。这些校验和约束解码是互补关系,不是替代关系。

另一个值得关注的方向是,Schema 本身的设计可以承载部分语义约束。枚举值、数值范围、正则模式这些 JSON Schema 特性在约束解码中同样生效,合理利用它们可以把一部分语义检查前移到解码阶段。但这种方法有明确的边界——它只能表达"值属于某个集合"这类硬约束,无法处理"值是否符合上下文"这类软判断。

理解边界比使用工具更重要

约束解码是一个成熟且实用的技术,它把 LLM 结构化输出从"祈祷式编程"变成了可靠的工程实践。但任何工具都有其作用域,把格式保障误认为内容保障,是当前 LLM 应用开发中一个代价不小的认知偏差。

在构建依赖结构化输出的系统时,清晰地区分"格式正确"和"内容正确"这两层需求,分别设计对应的保障机制,比单纯依赖某一种解码策略要稳健得多。

#约束解码#Constrained Decoding#LLM结构化输出#JSON Schema#函数调用#工具调用#大模型输出格式
分享:

相关推荐