前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
返回 AI 情报前线
All News · 全部资讯8614
  • 微软Project Perception: AI驱动的闭环自动安全防御系统
  • AI生成WordPress代码的安全性实验验证
  • AI Agent相互委派:多智能体编排的新范式
  • AMD 开源 16B MoE 模型及完整工程资源
  • AI编码Agent成本优化:Context智能压缩方案
  • 生产AI管道成本削减25%:模型参数调优实战
  • MCP生态稳定性警示:工具契约频繁变更的风险
  • AI代码生成的转义漏洞:上下文决定安全
  • OpenClaw 连接失败?先排查 prompt 和 context 预算
  • Agent 权限蔓延:如何在增量需求中悄悄发生
  • Agent系统架构选择:单体 vs 多体
  • Transformer训练加速:GPU融合与精度优化
  • Python + FastAPI 构建生产级 AI Agent 完整指南
  • 用 AI Agent 构建多租户 SaaS:架构与成本控制
  • AI Agent 金融操作必须人工审批:架构模式
  • PDF 结构提取:AI 为什么读不懂 PDF
  • 开源编码模型对比:性能与自托管权衡
  • Agent 时代的身份认证困局:MCP 安全重思
  • Grok Build 开源后的 Agent 安全审查清单
  • Semantic Release 自动化版本管理和变更日志
  • LLM JSON 输出:Prompt 不如解码约束可靠
  • 编程任务的 LLM 模型选型指南
  • 真实代码库PR任务上的AI编程Agent对标测试
  • LLM函数调用跨提供商差异的可重复测试框架
  • 2026 Prompt管理工具市场洗牌与迁移指南
  • 2026 LLM网关对比:成本、可靠性与市场格局
  • RAG 2026:开源框架与托管平台的架构权衡
  • AI漏洞检测2026:从RCA到自动修复的Agent演进
  • 代码重构显著降低 AI 编程的 Token 消耗
  • AI时代文档成为代码的运行手册
  • AI从检测Bug转向自动修复范式
  • OpenAI Astra 数学成果:从模型输出到可验证代码
  • Agent 系统失败根源:协调瓶颈而非模型智能
  • 开源工具:减少前端 AI Agent 的 token 浪费
  • AI 模型突破数学难题,数学家警示职业风险
  • Agent 持久化记忆与秘密防泄:从入口单次清洗
  • LLM推荐系统成本陷阱与三层优化策略
  • Cursor Windows任意代码执行漏洞,沙箱打开前需谨慎
  • MCP:AI 工具集成的统一标准
  • 同一模型不同运行时效果差异大
  • AI/ML 工程师实战学习路线图与里程碑
  • 本地 LLM 驱动智能合约模糊测试
  • Herdr:Agent 并行管理工具
  • 按不确定性选模型:DeepSeek Flash 用法指南
  • Prompt 模板失效的根本原因与修复方法
  • Ollama 驱动的自主 Agent 实战:定时执行与安全网关
  • MCP 中的工具形状:Agent 安全治理的新边界
  • Agent 内存系统的取舍:什么值得记住,什么该遗忘
  • 三个易混淆概念澄清:内存 vs 上下文窗口 vs RAG
  • Spring AI 集成 Gemini:配置即用,无需重写
  • AI Agent的四层记忆系统:工作/事件/语义/程序记忆
  • 已加载 51 / 8614
8.0
热点
AI SCORE
技术实践2026-08-02 01:33

LLM JSON 输出:Prompt 不如解码约束可靠

dev.to · AI#LLM提示工程#JSON解析#实验验证
Editor brief · 编辑速览

通过实验对比 Prompt 和 Constrained Decoding 对 JSON 输出的影响,揭示模型知道有效 JSON 格式但未必生成符合解析器期望的输出。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

一个模型即使知道合法 JSON 长什么样,仍然可能生成一段会被解析器拒绝的响应。

例如,下面每个响应都包含一个看起来合法的 JSON 对象。

Here is the result:

```json
{"answer": "72"}

{"answer": "72"}

The calculation is 48 + 24.


{"answer": "72"} {"answer": "72"}


人可以立刻识别出答案。

但一个期望输入恰好为单个 JSON 值的程序却做不到。

这正是我想进一步弄清楚的区别:

Prompt 改变的是哪些输出更有可能出现。约束解码改变的是哪些输出被允许出现。

我围绕 Qwen2.5-0.5B-Instruct 搭建了一个小型本地实验室,从 tokenizer、生成、验证和 logit 处理几个层面观察这种区别。

仓库中包含实验脚本、原始生成结果、观察记录以及 JSONL 格式的评估日志:

github.com/Vaibhav701161/constrained-decoding-lab

我针对三道简单的小学数学题,对比了两种生成条件。

自由形式的 prompt 要求模型展示简短的推理过程,并以一个数字答案结尾。

结构化 prompt 则要求模型只返回一个 JSON 对象:

{ "reasoning": "your reasoning here", "answer": "final numeric answer here" }


生成过程是确定性的,使用了:

do_sample=False


对于每个示例,评估脚本都会记录:

- prompt 和原始输出
- 解析后的表示
- 精确匹配的正确性
- prompt token 数和生成 token 数
- 解析或验证错误

最初的冒烟测试结果是:

这个结果不应被视为基准测试。

三个手工挑选的示例远远不够,而且提取逻辑有意保持得非常简单。

不过,其中暴露出的失败模式仍然很有价值。

使用 JSON prompt 生成的输出包括:

- 看起来合法的 JSON 后面跟着普通文本
- 一个响应中包含多个 JSON 对象
- 一个完整对象生成结束后,模型仍继续生成内容

在一些案例中,答案明明清晰可见。

但完整响应仍然无法通过 JSON 解析。

这正是仅依赖以下指令的实际局限:

Return ONLY valid JSON. No markdown. No extra text.


这些指令可以提高模型生成合规输出的概率。

但它们并不会从模型可能选择的解码路径中移除不合规输出。

## 验证发生在错误之后

JSON 解析器可以在生成完成后告诉我们输出无效。

Schema 验证器可以告诉我们,解析后的对象包含错误的 key 或错误的值类型。

恢复逻辑或许能够从一段更长的响应中提取出第一个完整对象。

这些技术都很有用,但它们解决的是另一个问题。

生成后验证问的是:

模型生成的内容可以接受吗?

约束解码问的是:

模型接下来可以合法生成哪些 token?

前者检测失败。

后者试图防止结构性失败。

这并不意味着约束解码会自动让答案变得正确。

它只意味着生成的序列被强制限制在某种定义好的语言之内。

一个完全合法的 JSON 对象,仍然可能包含错误答案。

结构有效性与语义正确性是两个独立的评估维度。

## 接触解码循环

为了理解其底层机制,我实现了一个小型 Hugging Face `LogitsProcessor`。

这个处理器会扫描 tokenizer 的词表,记录那些单独解码后的文本中包含数字的 token ID:

for token_id in range(len(tokenizer)): piece = tokenizer.decode( [token_id], skip_special_tokens=False, )

if any(character.isdigit() for character in piece):
    self.banned_token_ids.append(token_id)

在每一个生成步骤中,它都会修改这些 token 的分数:

scores[:, self.banned_token_ids] = -float("inf")


经过 softmax 后,这些 token 的概率会变为零。

解码器无法选择它们。

这不是一个受语法约束的解码器。

它只是一个静态 token mask,用来直接观察解码阶段的基本操作。

实验结果并不只是简单的“模型不再生成数字”,而是更有意思。

模型避开了普通的 ASCII 数字,却生成了带圈数字等类似数字的 Unicode 字符。

格式和输出质量也随之下降。

这暴露出了两个重要问题。

## 约束执行的是它的实现,而不是它的意图

“阻止模型输出数字”是一种非形式化的意图。

“将单独解码后的字符串中包含数字的 token,其 logits 设置为负无穷”则是一种具体实现。

二者并不等价。

数字还可以通过以下方式表示:

- 其他 Unicode 数字字符
- 在视觉上类似数字的符号
- 被简单词表扫描遗漏的 token 组合

解码器只能保证实际编码进系统的规则得到执行。

当人们声称约束解码可以保证输出有效时,这一点尤其重要。

这种保证取决于:

- 实际执行的语法
- tokenizer 的表示方式
- byte 和 Unicode 的处理方式
- 对接受状态的定义

一个范围过窄或存在错误的形式化定义,可能会为错误的语言提供非常强的保证。

## 硬约束可能损害输出质量

原始模型的概率分布可能会将大部分概率质量集中在少数几个自然的续写选项上。

当这些续写被 mask 后,解码器只能从剩余选项中选择。

剩下的 token 也许在结构上合法,但可能很别扭、概率很低,或者语义质量很差。

因此,只通过有效率来评估约束解码是不完整的。

一次有价值的对比还应该衡量:

- 生成的 token 数
- 被移除的概率质量
- 在不同 schema 和 prompt 风格下的行为

一个系统可以生成 100% 语法合法的 JSON,但在实际任务上的表现仍然更差。

## 真正的难点:语法作用于 byte,模型输出 token

将选定的 logits 设置为负无穷并不困难。

真正困难的是判断应该 mask 哪些 logits。

语法通常定义在字符或 byte 之上。

语言模型输出的则是 token。

一个 token 可能同时包含:

- 空白和标点
- 一个多 byte 字符的一部分
- 行为取决于前后 token 的片段

假设解析器当前允许一个以 `0` 开头的数字。

合法词表中可能包含表示以下内容的 token:

0 0. 0.4 0.46 0} 0,


每个 token 是否合法,取决于语法和解析器的当前状态。

仅仅判断一个 token 是否等于下一个合法字符是不够的。

解码器必须模拟消费这个 token 的完整 byte 序列。

current parser state ↓ candidate token bytes ↓ simulate every parser transition ↓ keep the token or mask it


只有当一个 token 的完整 byte 序列能够被消费,且不会导致语法失效时,它才应该继续保持可用。

生成一个 token 后,解析器的状态就会发生变化。

因此,必须重新计算合法词表集合。

这让真正的约束具备了状态依赖性。

静态数字禁令会在每个生成步骤中使用相同的 mask。

而 JSON 语法在以下内容之后需要使用不同的 mask:

{


{"answer":


{"answer":"72"


合法的后续内容取决于解析器当前所处的位置。

## 为什么逐个检查所有 token 的朴素做法代价高昂

现代 tokenizer 可能包含数万乃至数十万个 token。

如果在每个生成步骤中都用解析器测试所有 token,就会引入巨大的额外开销。

这正是 token trie 和缓存解析器状态转换等技术可以发挥作用的地方。

token trie 会按照共享的 byte 前缀对词表中的 token 进行分组。

解码器不需要将每个 token 分别送入解析器重放,而是可以只遍历一次共享前缀,并在某个前缀失效时立即剪掉整个分支。

高层循环会变成:

1. 读取解析器的当前状态。
2. 遍历可能的 token byte 前缀。
3. 剪掉违反语法的分支。
4. 收集在可行分支上结束的 token ID。
5. mask 词表中的其余 token。
6. 推进解析器状态。
7. 重复上述过程,直到到达接受状态。

当涉及递归结构、字符串、转义、数字、UTF-8 和存在歧义的解析器状态时,实现会变得更加复杂。

但其核心映射关系始终不变:

解析器状态 → 合法 token ID

## 这个实验不能证明什么

当前实验设置存在一些局限。

三个示例足够用于冒烟测试,但不足以比较不同的解码策略。

### JSON 提取器有意设计得严格且简单

它会截取第一个左花括号与最后一个右花括号之间的内容。

因此,多个合法对象会被合并成一个无效候选结果。

更完善的评估应该分别报告以下指标:

- 完整响应的有效性
- 第一个对象的可恢复性

### 自由形式提取器并不可靠

它会选择输出中的最后一个数字。

如果答案后面又出现了额外的数字文本,就可能产生错误评分。

### 模型没有通过自己的 chat template 接收 prompt

原始 prompt 被直接传给了一个经过 instruction tuning 的模型。

有必要将这种方式与 tokenizer 预期的 chat template 进行比较。

### token mask 并非 byte 级精确

单独解码词表中的每个 token 对观察分析很有用。

生产级实现则应该仔细处理底层 token byte 和 UTF-8 行为。

### 约束是静态的

数字处理器演示的是 logit masking,而不是由解析器驱动的约束解码。

这些局限并不是无关紧要的旁枝末节。

它们决定了我们可以从实验中得出哪些结论,又不能得出哪些结论。

## 下一步实现

下一个有价值的里程碑,是为一种受限输出语言实现一个小型、状态依赖的解码器,例如:

{"answer":"<digits>"}


这个实现应该:

- 将该语言定义为自动机或解析器
- 将 tokenizer ID 映射为 byte 序列
- 跟踪解析器的当前状态
- 模拟候选 token 的 byte
- 只允许能够维持有效性的 token
- 仅在接受状态中停止
- 在每一步记录 mask 大小和延迟

实现之后,就可以对比:

- regex 或有限状态约束
- JSON Schema 约束
- 不同的 token 过滤策略

在对比过程中,应尽可能控制模型、数据集、prompt 内容和语义评分方法保持一致。

## 真正有用的区别

“只返回合法 JSON”仍然是一条有用的指令。

它可以减少失败、提升可读性,并让模型预期的输出形式更加清晰。

但它并不是解码约束。

Prompting 是要求模型遵循某种结构。

验证是检查模型是否做到了。

约束解码则会移除违反该结构的续写选项。

这些方法可以相互配合,但它们提供的保证并不相同。

该实验的代码、原始输出和笔记可以在这里找到:

Constrained Decoding Lab

如果要采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。
Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
Semantic Release 自动化版本管理和变更日志
下一篇
编程任务的 LLM 模型选型指南