YAML vs JSON:LLM 场景下的真实 token 实测

实测 YAML 比 JSON 省约三成 token,TOON 在表格数据上再省一半,但 YAML 1.1 的 Norway problem 会静默污染解析。
文章用 OpenAI 的 cl100k_base tokenizer 实测了 JSON、YAML、TOON 三种序列化格式的 token 开销:在嵌套配置上 YAML 比 JSON 省 33.3%,在 10 行表格数据上省 31.6%,而专为表格设计的 TOON 比 JSON 省 70%、比 YAML 省 56.1%。格式选择取决于数据形状——嵌套非规整数据用 YAML,规整对象数组用 TOON,API 与数据库边界仍应保持 JSON,转换只发生在与 LLM 交互的边界。文章同时警示 YAML 1.1 的 Norway problem 会把裸写的 no/on/yes 解析为布尔值,已导致 GitHub Actions 的 on: 键在非专用解析器中被误读为 true 的真实 bug。对每轮重发上下文的 agent,这类开销会随调用次数成平方级累积。
在大模型应用里,数据序列化格式的选择常常被当作风格问题一笔带过,但实际上它直接决定了每一次请求要付多少 token 费用,以及上下文窗口里能塞进多少内容。DevToolHub 作者用真实的 tokenizer 对 JSON 和 YAML 做了对照测试,而不是转发网上流传的"大约省 20%"这类模糊说法。结果比多数人预期的差距更大,同时还暴露出一个会静默污染 LLM 输出的解析 bug。

实测数据:YAML 到底省多少 token
作者构建了两个测试用例,一个是嵌套的配置对象,另一个是 10 行、每行 4 个字段的规整表格数据,并统一用 OpenAI 的 cl100k_base tokenizer 编码。需要说明的是,换成 Claude 或 Gemini 自家的 tokenizer,绝对数值会略有差异,但两种格式之间的相对排名不会改变。
在嵌套配置对象(单个对象内嵌子对象,而非列表)这一组中,JSON 编码为 99 个 token、322 个字符,YAML 为 66 个 token、223 个字符。也就是说,仅仅去掉大括号、逗号和键名的引号,YAML 就把同样配置的 token 数压低了 33.3%。
到了 10 行、4 字段的规整表格数据上,画面发生了变化。JSON 是 323 个 token、963 个字符,YAML 是 221 个 token、590 个字符,YAML 依然领先 31.6%。但同一份数据用专为压缩设计的 TOON 格式编码,只有 97 个 token、235 个字符,比 JSON 少 70%,比 YAML 少 56.1%。
差距来自结构而非表面修饰。YAML 的 - id: 1\n name: Alice 写法会在每一行重复所有字段名;TOON 只在开头声明一次字段列表(users[10]{id,name,role,active}:),之后只流式输出裸值。对一个一次性的配置文件,这种重复几乎无所谓;但对要进入 agent 上下文窗口、而且每轮都要重发一次的工具返回值来说,重复的代价会迅速放大。
cl100k_base 是 OpenAI 为 GPT-3.5/GPT-4 系列模型提供的 BPE(Byte Pair Encoding)分词器,也是 tiktoken 库默认使用的编码方案之一。它的词表规模约 10 万,训练语料以英文和代码为主,因此对 JSON 这类带有大量标点符号的文本有特定的切分习惯:花括号、方括号、逗号和引号往往各自占据一个 token,甚至键名后的 ": " 组合也会被拆成多个 token。这意味着 JSON 的"符号开销"在 token 层面被显著放大,而 YAML 通过缩进替代括号、省略冗余引号,正好避开了这些高频但低信息量的符号 token。不同厂商的 tokenizer 对符号的合并策略不同,所以绝对省幅会浮动,但"符号越多、token 越贵"这一底层逻辑是通用的。
三种格式各自适用的场景
选择哪种格式,核心取决于数据的形状而非个人偏好。
嵌套、非规整数据(应用配置、单对象带子对象)方面,YAML 和 TOON 的体量几乎一致。TOON 的规范本身就借用了 YAML 的缩进语法来处理非表格结构,所以这类场景切换格式没有实际收益。选 YAML 即可,毕竟支持它的工具链更多。
规整的对象数组(用户列表、日志行、查询结果、工具输出)是 TOON 的主场。这正是它被设计出来压缩的数据形态,优势明显。
任何要落到 API、数据库或既有软件里的数据,继续用 JSON。它通用、严格,所有库都默认支持。不要在这些环节换成 YAML 或 TOON,转换应该只发生在与模型交互的边界上。
一个实用的工程模式是:系统内部实际流转的数据保持 JSON,只把交给模型的文本转成 YAML 或 TOON,然后要求模型用 JSON 格式返回任何需要回到代码里的内容。
没人提醒你的 YAML 解析陷阱
YAML 的紧凑来自比 JSON 更宽松的语法,而宽松有具体的失败模式,其中最有名的是 Norway problem(挪威问题)。在 YAML 1.1 中——很多库仍在实现这一版本,包括较老的 PyYAML——裸写的 no、yes、on、off 会被解析成布尔值而非字符串。
一段看似正常的国家代码列表:
geoblock_regions:
- dk
- fi
- is
- no
- se
会被静默解析成 ["dk", "fi", "is", false, "se"],挪威(no)直接变成了 False。版本号字符串也有同样的问题:10.23 会被解析为浮点数而非字符串 "10.23",破坏那些按文本比较版本的逻辑。
这对开发者群体并非理论风险。GitHub Actions 工作流文件的顶层键就是 on:(例如 on: push)。根据 Go YAML 库的一则真实 bug 报告,通用的 YAML 1.1 解析器会把该键读成布尔值 true,而不是字符串 "on",于是任何用非 GitHub 专用库解析工作流 YAML 的工具,都可能静默丢弃或错误处理触发配置。GitHub 自己的 runner 能正确处理 on:,因为它是 YAML 1.2 感知的;但如果你在写自己的工作流 linter、生成器,或者一个读取 .github/workflows/*.yml 的 LLM 工具,这正是那种能通过代码审查、却在上线后崩掉的 bug。
修复方式有两种:给键加引号("on": push),或改用符合 YAML 1.2 的解析器。YAML 1.2.2 是 2021 年 10 月修订的现行规范,正是为了修掉这类 bug 才移除了裸 on/off/yes/no 的布尔简写。如果你把 YAML 喂给 LLM,并期望它稳定推理或重新生成这段 YAML,这个坑的影响会加倍:模型训练时也很可能带着 YAML 1.1 式的解析假设。
YAML 1.1 与 1.2 的关键差异在于类型推断规则。YAML 1.1(2005 年发布)沿用了大量源自 Perl 和 RFC 的隐式类型约定,把 y/n、yes/no、on/off、true/false 等裸词统统视为布尔值,还支持 010 这样的八进制、1_000 这样的下划线千分位、以及 12:30 这样的六十进制时间。YAML 1.2(2009 年)大幅精简了这些规则,只保留 true/false 作为布尔字面量,on/off/yes/no 回归普通字符串。问题在于,大量主流库诞生于 1.1 时代或为兼容旧数据保留 1.1 行为:Python 的 PyYAML 长期默认 1.1,Go 的 go-yaml v2 也是 1.1,而 v3 才切到 1.2 语义。这就是为什么同一份 YAML 在不同语言、不同版本的解析器下可能得到完全不同类型的结果——它不是一个"写法对不对"的问题,而是"你在哪个规范的语义下读它"的问题。
为什么 token 数对 agent 的影响远大于单次对话
在单条聊天消息里,YAML 比 JSON 省 60 个 token,几乎无关紧要。它真正会累积出代价的地方很具体:那些每轮都要重发累积上下文的 AI agent——对话历史、工具输出、检索文档统统重发。如果一个 agent 在一次会话里调用工具 20 次,而每次工具返回的都是 JSON 行数组,那你就把 JSON 的额外开销付了 20 遍,而不是一遍。
这和 prompt 压缩的核心经济学一致:真正值得调整的是每次请求都会拉动的那个杠杆,而不是只拉一次的杠杆。对 agent 架构而言,序列化格式恰好就是这样一个杠杆。
AI agent 的上下文重发机制是理解这一节经济学的关键。与单轮问答不同,agent 通常采用"累积上下文"循环:每一轮它都把系统提示、历史对话、历次工具调用及其返回值、检索到的文档全部重新拼成一次请求发给模型。上下文窗口是有限资源(如 128K token),而每轮都要为已有的全部内容重新付费。假设一次会话中工具被调用 N 次,那么第 k 次调用时,前 k-1 次工具返回的内容仍在上下文里,序列化格式的额外开销就被重复计入了 N、N-1、N-2……次。总开销按 O(N²) 量级累积,而不是线性的 N 倍,这正是为什么格式选择在 agent 场景下从"风格问题"升级为"成本杠杆"。
常见问题
YAML 对 LLM 来说是真的解析更快,还是只是 token 更少? 本文只测了 token 数,也就是成本和上下文窗口这两个杠杆。解析速度是另一个问题,取决于运行时环境,而与模型本身无关。
TOON 会取代 YAML 吗? 不会。两者只在规整表格数据上重叠。对嵌套、不规则结构(大多数真实配置文件),TOON 自己的规范也采用 YAML 式缩进,切换格式并无效率收益。
为什么 YAML 里的 "no" 会变成 false?
YAML 1.1 会把一组裸词(y、n、yes、no、on、off、true、false)在未加引号时当作布尔字面量。YAML 1.2.2 已从规范中移除该行为,但许多解析器(较老的 PyYAML、Go 的 go-yaml v2)出于兼容性仍在实现 1.1 规则。
是不是该给每个 YAML 值都加引号?
只给那些可能被误读为其他类型的值加引号即可——国家代码、版本号字符串,以及任何长得像 yes/no/on/off 的内容。对 GitHub Actions 的 on: 这类键,给键本身加引号:"on": push。
核心结论
用真实 tokenizer 测下来,YAML 在嵌套配置和表格数据上都能比 JSON 省下约 30%–33% 的 token。在规整表格数据上,TOON 更进一步,比 JSON 少 70%、比 YAML 少 56%。对嵌套、非规整数据,YAML 和 TOON 体量相当,出于兼容性优先选 YAML。Norway problem(no → false)是真实存在且已经破坏过实际工具的问题,GitHub Actions 的 on: 键在非 GitHub 专用解析器里就是一个典型受害者。JSON 依旧在 API 和数据库边界上无可替代——格式转换只应发生在与 LLM 交互的边界。
在自建流水线里权衡 YAML 与 JSON 时,诚实的答案是"取决于数据的形状",而不是"YAML 永远赢"。在正式决定之前,用 tokenizer 量一遍自己的真实载荷,并留意那些看起来像布尔的未加引号值。
相关推荐

特斯拉Roadster为何要会飞?深度拆解马斯克的疯狂演示
特斯拉Roadster发布会演示了爬墙、倒立与飞跃特技,但The Vergecast拆解后指出:演示车或拆除电池、Rocket版为邀请制且不合法上路,官网条款甚至不保证交付。跳票九年后,这台车为何仍需存在?

AI不是替代者:从工厂根因分析看人机协作
从工厂根因分析场景出发,解析AI如何加速证据链构建而非替代人类专家,探讨「AI支持人而非取代人」的务实落地逻辑与商业价值。

AI Agent决策审计:受监管流程中的证据链怎么建
从Reddit讨论出发,分析受监管流程中AI agent决策审计的三个核心问题:审计记录该包含哪些字段、错误决策的反馈延迟如何应对、以及LLM评审员与人类判断的一致性如何持续校准。