跳到主要内容

Markdown 优于 JSON:你正在支付却未察觉的输出格式税

· 阅读需 12 分钟
Tian Pan
Software Engineer

大多数团队在上线当天开启 JSON 模式,却从未衡量这背后的代价。这种假设合情合理:结构化输出能保证正确性,为什么不选它呢?答案是,严格的 JSON 模式约束解码通常会使数学、符号和多步分析任务的推理准确度降低 5–15%,而由于评估是在开启格式标志之前运行的,或者评估衡量的是可解析性而非质量,因此没人注意到这一点。

输出格式是一种解码时的约束,正如所有约束一样,它会扭曲模型的概率分布。当你查看日志时,这种扭曲是不可见的:JSON 有效,Schema 匹配,字段类型也对得上。你在日志中看不到的是模型本可以用散文形式产出的推理过程,但由于无法塞进你给定的语法中而消失了。“格式税”是真实存在的,在文献中已有详尽记录,但在生产环境中几乎普遍未被衡量。

这篇文章将探讨何时该支付这笔费用,如何在不必支付时及时止损,以及对于既想要结构化输出又想要准确性的工程师来说,格式选择的决策树究竟是什么样的。

机制:为什么约束解码会损害质量

在每个解码步骤中,语言模型都会在其词表上生成一个概率分布。约束解码应用了一个 Logit 掩码:任何违反目标语法的 Token 其概率都会被清零,剩余的 Token 会进行重新归一化。从结构上讲,这很整洁——输出保证可以解析。从统计学上讲,这是模型对自己分布撒的一个谎,而且这个谎言会产生复利效应。

实践中出现了两种特定的失效模式。第一种是 Token 切分接缝(tokenization seam)。模型是针对常见字符串的特定 Token 切分方式进行训练的——“because”一词在散文中有一种切分方式,在 JSON 左引号后则是另一种,模型在训练期间学到的概率质量集中在第一种切分方式上,而非第二种。当语法强制执行第二种切分路径时,模型是在处理一个预训练期间极少见的序列。输出质量会微妙地下降——语法正确,但语义变薄了。

第二种是字段顺序陷阱。像 {"answer": string, "reasoning": string} 这样的 JSON Schema 看起来无害,实则是灾难性的:模型被迫在生成任何推理 Token 之前先输出答案 Token。模型原本用于检查工作的思维链(Chain-of-thought)现在要么缺失了(如果模型将答案字段视为终止),要么是事后为了匹配已做出的决定而伪造出来的。《Let Me Speak Freely?》论文 (arxiv 2408.02442) 记录了在更严格的格式约束下,GSM8K、Last Letter 和 Shuffled Objects 任务的准确率大幅下降,原因正在于此。

不同格式间的经验差距

格式选择在不同任务中并不是中性的。在选择默认格式之前,有几个 2024–2026 年间的衡量结果值得深入理解:

  • 推理任务在严格 JSON 模式下表现下降。 《Let Me Speak Freely?》研究表明,从自然语言提示转向格式受限的 JSON 指令会导致推理基准测试的准确度显著下降。采用“自然语言转格式”(NL-to-Format)的二阶段方案——即先生成散文,再进行转换——可以将性能恢复到接近无限制的水平。
  • 分类和提取任务在 JSON 模式下可能会提升。 同一篇论文发现,在 DDXPlus 和类似的分类数据集上,JSON 模式表现出色,有时甚至更好,因为约束以任务所需的方式压缩了输出空间。
  • 格式偏好取决于模型。 针对提示格式的研究 (arxiv 2411.10541) 发现,GPT-3.5-turbo 在不同模板的代码翻译任务中表现差异高达 40%,且偏好 JSON;而 GPT-4 更偏好 Markdown,且对格式变化的鲁棒性更强。大型模型能更好地吸收“格式税”;小型模型则会将其弊端暴露无遗。
  • Token 成本并非旗鼓相当。 对于相同的嵌套内容,Markdown 使用的 Token 比 JSON 少约 34–38%,而 XML 需要的 Token 比 Markdown 多约 80%。在输出端,你会在每次请求中支付两次这笔税——一次是金钱成本,一次是延迟。
  • 约束解码并非百害而无一利。 JSONSchemaBench 显示,在像 GSM8K 这样结构极简的任务中,约束解码实际上可以通过让模型保持在轨道上,将下游性能提升高达 4%,并使生成速度提高约 50%。情况非常微妙:损害特定于那些语法阻断了模型原本会采取的推理路径的任务。

如果你只能记住一个校准点,那就是:模型越小,推理任务越重,且 Schema 中答案早于推理时,格式税就越沉重。而在提取任务中、在大型前沿模型上,以及当 Schema 与任务契合时,格式税最轻。

三种格式:按其实际用途排名

Markdown:适用于推理和散文

当模型需要思考时,Markdown 是目前摩擦力最低的输出格式。它不强加任何语法,没有 logit 掩码,也没有模型在预训练中没见过千万次的 token 分词边界。列表符号、标题和代码块都是训练分布中原生存在的。对于任何以推理的正确性或完整性为主要质量指标的任务——如 Agent 规划、多步调试、以及需要权衡上下文的客户支持——Markdown 应该作为默认选择。

Markdown 的弱点在于机器解析。将 Markdown 解析为下游的类型化字段是非常脆弱的。如果你的工作流中下一步是人类阅读输出,Markdown 胜出;如果下一步是需要 {city: string, temperature: number} 的函数,Markdown 则会落败。

XML 标签:用于提取和边界设定

XML 标签占据了一个非常有用的中间地带。它们具有足够的结构来可靠地界定字段——例如 <quote>...</quote><answer>...</answer><reasoning>...</reasoning>——但又足够轻量,不会强制进行受限解码。模型是在编写带有标记的散文,而不是在语法中穿行。

Anthropic 专门训练了 Claude 来识别 XML 标签作为提示词组织机制,这种模式在两个方向上都有效:作为输入结构和作为输出结构。对于提取任务(“提取文档中提到的所有日期”)以及混合了散文和结构化内容(推理块后跟结构化摘要)的任务,XML 标签能为你提供 90% JSON 的可解析性,同时保持接近 Markdown 的生成质量。

其缺点是冗余。在三种格式中,XML 是最消耗 Token 的。如果你是按输出 Token 付费,包含大量 XML 的响应是最昂贵的选择——成本大约是同等 Markdown 内容的两倍。

严格 JSON(强制 Schema):用于机器间的交付

严格 JSON 模式——无论是 OpenAI 的结构化输出、Anthropic 的工具调用,还是像 Outlines 这样基于 FSM 的库——当输出必须在没有人工干预的情况下直接进入代码时,是正确的选择。API 响应、工具参数、数据库写入、服务间消息。下游消费者是一个解析器,一个格式错误字段的代价要高于损失几个百分点的推理质量。

诀窍是不要过早地使用严格 JSON。一个仅写着“以 JSON 响应”并给出一个松散示例的系统提示词,与带有 logit 掩码的严格 Schema 强制解码完全是两回事。松散版本付出了大部分格式税(模型仍倾向于生成 JSON 形状的输出),却几乎没有任何保证。严格版本付出了全部税,并得到了全部保证。中间地带——没有 Schema 的 “JSON 模式”——通常是两头不讨好。

双重传递模式:兼而有之

“自然语言转格式”模式是大多数团队尚未尝试过的、投资回报率(ROI)最高的提示工程技巧。它通过两次调用完成:

  1. 第一步:自由生成。 提示模型以 Markdown 或自由散文的形式生成答案,并附带它想要的任何思维链。不设格式限制。如果任务需要,使用具备推理能力的老牌模型。
  2. 第二步:转换。 将 Markdown 输出输入到第二个更便宜的模型调用中,并使用严格的 JSON Schema。此时的任务是提取,而非推理——而提取正是严格 JSON 所擅长的。

在生产环境中,有三点让这种模式非常具有吸引力:

  • 推理步骤运行在模型最强的解码路径上,找回了在受限解码下损失的准确性。
  • 转换步骤非常便宜。模型小,输出短,任务简单。增加的延迟通常在 200–500ms 之间;增加的成本仅为推理步骤的一小部分。
  • 失败模式变得可观察。如果第一步产生了错误的推理,你可以看到它。如果第二步遗漏了一个字段,你可以针对缓存的散文输出重试,而无需重新运行昂贵的首步调用。

团队有时会以延迟为由提出反对。这种反对值得商榷——第二次跳转确实会增加时间——但这很少是需要优化的真正瓶颈。如果推理质量是你产品的实际制约因素,那么为了节省 300ms 延迟而损失 10% 的质量是一笔划不来的交易。如果延迟是真正的制约因素,第一个问题应该是你是否根本不需要推理,而不是如何从一个推理能力较弱的流水线中挤出几秒钟。

仅靠字段顺序即可解决问题

如果你坚持使用单次生成的结构化输出——对于许多延迟敏感型应用来说,这是正确的决定——最廉价的修复方案就是 Schema 字段顺序。将推理字段放在首位

{
"reasoning": "...",
"final_answer": "..."
}

而不是

{
"final_answer": "...",
"reasoning": "..."
}

这与 OpenAI 在其结构化输出文档中给出的建议一致。其原理在于模型逐个生成 Token 的特性。放在答案之前的推理字段给了模型在结构化输出内部进行思考的空间。放在答案之后的推理字段则成了一种虚设——模型实际上是在为它已经做出的决定编写辩解。

这一处改动就能免费找回大部分损失的推理质量。它不需要第二次调用,不改变你的流水线,且只需对 Schema 进行一行修改。最常忽略这一点的是那些将 Schema 设计成与下游类型完全一致的团队,在这种设计下,答案自然排在首位,而他们从未想过调整顺序。请重新调整它。

你周一就能用的决策树

在选择格式之前,你需要问的问题是:“这个输出的下一个消费者需要什么?”

  • 人类读者,重推理: markdown,单次生成。
  • 人类读者,混合结构化数据 + 散文: XML 标签,单次生成。
  • 机器消费者,轻推理(分类、提取、工具参数): 严格 JSON,单次生成,如果有推理字段则将其置于首位。
  • 机器消费者,重推理(计划、多步分析、输出需被解析的代码生成): 双次生成 —— markdown 推理,JSON 提取。
  • 不确定属于哪一类: 默认使用 markdown + 廉价的转换步骤。缺点很小;优点是能够找回格式化可能损失的任何推理质量。

核心观点是,输出格式并非部署时的琐碎细节。它是一种在解码时进行的干预,旨在用一个维度的质量去换取另一个维度的质量,你应该清楚自己权衡了哪个维度。做得好的团队会在切换开关前对两种格式都运行评估 (eval)。没做好的团队则会在六个月后,当用户反馈模型“变笨了”时,才发现这项税收的存在。

衡量你自己流程中的税收

实际操作的要求并不高。挑选你最重要的五个生产环境提示词。针对每个提示词运行三个变体:自由散文输出、严格 JSON 输出,以及双次生成(先散文再进行 JSON 提取)。衡量你真正关心的指标 —— 任务准确率、用户满意度、下游操作成功率 —— 而不仅仅是格式合规性。然后进行对比。

通常会出现两种情况。在分类和提取任务中,这三种变体的表现差异处于噪声范围内,而 JSON 在成本上胜出。在重推理任务中,双次生成具有决定性优势,而严格 JSON 是三者中表现最差的。现在你有了数据,关于是否开启 JSON 模式的决策就不再仅仅是意识形态之争了。

格式税是一种潜在成本,它在部署时看起来像是一顿免费的午餐,却在每一次你无法解释的评估退化中悄然积累。在不影响效果的任务中刻意承担这种成本是优秀的工程实践。而仅仅因为 JSON 看起来“更专业”就普遍采用它,则是你应该停止做的事情。

References:Let's stay in touch and Follow me for more thoughts and updates