
为什么说JSON不一定是LLM结构化输出的最佳选择? 原创
编者按: 在使用大语言模型时,如何在保证输出质量的同时降低成本?在众多数据输出格式中,究竟应该如何选择?
我们今天为大家带来的文章中,作者通过实际测试给出建议:在某些场景下,相比广泛使用的 JSON 格式,不妨考虑一下其他数据格式,做一些测试,挑选出既能控制成本又能保证稳定性和速度的最佳选项。
文章通过对比 TSV、CSV、Columnar JSON、YAML、TOML 和 JSON 六种格式,从 token 使用量、响应时间和实用性三个维度进行了深入分析。作者指出,没有一种格式能在所有场景下都表现最佳。文章详细分析了每种格式的优劣势,并提供了一个实用的投资回报率计算方法,帮助读者评估是否值得将现有系统从 JSON 转换为其他格式。
作者 | David Gilbertson
编译 | 岳扬
当要求大语言模型(LLM)输出结构化数据时,所采用的格式会对结果产生比较大的影响。本文对比了六种不同的格式,评估考察了它们的处理速度、tokens 消耗以及各自的限制。
01 简要说明
JSON 虽然是多数人的首选,但它对 tokens 的消耗极大。处理相同数据时,它可能需要其他格式两倍的 tokens。
需要注意的是,没有一种格式能在所有情况下都表现最佳,以下是一个决策指南:
(如果你好奇为何没有提及 XML,那是因为我有个个人目标:50 年不碰 XML —— 只剩下 4 年就能达成了!)
我将在下文中详细解释这些格式选择,并探讨每种格式的局限性。但在此之前,先让我们对比一下它们的 token 使用情况和速度。
02 token 使用情况
探究 JSON 之外的其他选项,主要目的是为了减少所需的 tokens 数量,这样做可以降低运营成本并缩短响应时间。
为了对这些格式进行有效比较,我们将基于它们表示特定数据集所需的 token 数量来进行评估。
2.1 比较框架
本次比较我将使用一段文本作为输入,该文本包含了关于欧盟每个国家的一段信息。
我将要求 LLM 将这段普通文本转换成结构化数据,其中每个国家都是一条记录,每条记录包含国家名称、领导人姓名、领导人出生日期、领导人性别、人口数量和领土面积等键/值对。
我将针对每种结构化输出格式执行这一操作,并检查六种格式的输出结果是否相同。
感兴趣的朋友,可以在这个 gist[1] 中查看完整的代码。对于不太感兴趣的朋友,这里展示了我如何为每种格式定义 name、可选的 hint 以及 parser(这里将所有数据解析成 Pandas DataFrame):
LLM(本次测试使用的是 gpt-4o-mini)能够以不同格式准确返回相同的数据。当然,如果数据更复杂,或者使用的 LLM 不够强大,结果可能就不会这么精确了。
2.2 比较结果
下表展示了使用不同格式表示数据所需的 tokens 数量。
JSON 所需的 tokens 数量是 TSV 的两倍。这个差异不容小觑。设想一下,如果你在某个 API 的价格页面上看到,选择 JSON 格式的数据需要支付 1 美元,而 TSV 格式只需 0.5 美元,YAML 格式则是 0.8 美元。
当然,这些结果仅针对我们的示例数据。展示本图表的目的并非要让你认为 JSON 在所有情况下都会大量消耗 tokens,而是让你相信值得用其他格式测试自己的数据。
接下来,我们来看看这些格式的响应时间。
尽管 JSON “只”需要两倍于 TSV 的 tokens ,但其响应时间通常比 TSV 慢四倍。我原本以为 token 数量与响应时间之间的关系是近似线性的 —— 接近O(n),因此如此夸张的响应时间出乎我的意料,我建议我们可以将这种现象的时间复杂度设为O(my)。
将数据结构化输出的响应时间还是蛮重要的,所以赶紧测试吧。
03 局限性与考虑因素
如果这些格式都同样可靠和灵活,那么结论就会很简单:使用 TSV。但事实并非如此,所以让我们对每一种格式进行更深入的了解。
3.1 TSV
在表示表格数据时,TSV 和 CSV 格式颇为相似,区别在于 TSV 使用制表符分隔每一行的数据,而 CSV 则采用逗号。如果数据中本身就包含逗号或制表符,那么这些值就需要用双引号括起来,这时两种格式的 tokens 使用差异才会显现。
由于制表符在数据中出现的频率低于逗号,因此 TSV 在大多数情况下使用的分隔符数量会少于 CSV。
在解析数据时,TSV 与 CSV 相比 JSON,在纯 Python 环境下解析起来略显复杂。虽然可以利用 Python 内置的 csv 模块进行解析,但使用 Pandas 库会更加便捷。在其他编程语言中,解析这两种格式要么需要编写更多代码,要么得依赖第三方库。
如果数据中不含换行符,TSV 可以轻松地逐行解析。因此,若想从 LLM 流式传输响应数据并实时处理每一行数据,TSV(以及 CSV)都是不错的选择。虽然 TOML、YAML 和 JSON 也能实现类似功能,但处理起来会更加繁琐。 另外,本文尚未测试的 NDJSON 也是一个值得考虑的选项。
3.2 CSV
如前文所述,CSV 格式的挑战在于逗号在数据中较为常见,这可能会导致两种情况:要么是需要更多的 tokens 来处理这些逗号,要么是 LLM 在处理时未能正确进行转义,从而产生错误的数据。因此,如果你的数据可能包含逗号,最好避免使用 CSV,或者设计一个详尽的提示词,并实施有效的评估流程,以便准确衡量其可靠性。
对于 TSV 和 CSV 两种格式,你需要用那些可能包含特殊字符(如逗号、制表符、换行符和双引号)的数据来测试你的系统配置。这样,你才能确保系统能够正确处理这些特殊情况。
3.3 Columnar JSON
Columnar JSON 并不是一个常见的技术术语;我之所以将它纳入这次比较,是因为我很好奇它的 tokens 使用效率如何。
可能有些人还不清楚 Columnar JSON 是什么样的,下面就是前文提到的国家数据所对应的 Columnar JSON 格式:
在所有格式中,Columnar JSON 的直观性最差。但是,由于其结构特点,每个字段名只会出现一次,而不是每条记录都重复,这样就能节省 tokens。
我注意到,有时 LLM 能够理解“Columnar JSON”的含义,但有时候需要一些额外的提示词,例如:“应以列名作为键名,对应的列内容以列表的形式组织呈现”。
要解析 columnar JSON,你可以这样将其传递给 Pandas 处理:
与 CSV 和 TSV 不同,columnar JSON 支持嵌套的数据结构,因此它非常适合表示那些某些字段具有复杂结构的记录列表。
这三种格式——TSV、CSV、columnar JSON——仅适用于表示表格数据,即以记录列表为核心的结构。它们都不适合用来表示像配置文件这样的单一 top-level object(译者注:指在结构化数据格式(如 JSON/YAML/TOML)中,最外层定义的单一根对象,通常作为整个数据结构的入口点。)。而接下来的三种格式(YAML、TOML、JSON)则更为灵活多变。
3.4 YAML
YAML 能够返回一个 top-level list(译者注:指在结构化数据格式中,最外层直接定义为列表结构而非对象。),但我注意到,某些 LLM 更倾向于生成一个 top-level object。因此,在给出提示词时,我们需要明确指出,以确保 LLM 按照统一的格式返回数据。
我还遇到了一个问题,即 LLM 在返回字符串值时,格式可能会不一致。在某些情况下,这可能无关紧要,但 YAML 有五种不同的方式来表示字符串,而其中只有一种能够正确解析转义序列(例如\t, \u03B1)。因此,如果你的数据中包含转义序列,那么最好明确要求 LLM 使用双引号来定义字符串。
YAML 相较于 JSON,存在更多的“陷阱”和注意事项。建议你深入了解这些潜在的问题,而不是盲目地期待 LLM 能够自动正确地格式化 YAML。
为了解析 YAML,你需要安装一个第三方库。我个人使用的是pyyaml,这是一个无依赖的库。
3.5 TOML
TOML 是在此场景中唯一不支持 top-level list 的格式,因为它的设计初衷是作为一种配置文件格式。因此,若想用 TOML 来表示记录列表,就必须将这些记录包含在一个 top-level object 内,并告诉 LLM 你想在这个对象中调用什么键。
TOML 在使用上通常会比 YAML 需要更多的 token,因为 TOML 要求所有的字符串值都必须用引号括起来。
在解析方面,如果你的 Python 版本是 3.11 或以上,那么内置的 TOML 解析器[2]就可以直接使用。如果不是,那就需要安装 tomlkit 或类似的库来处理。
TOML 的普及度不及 YAML,你可能会担心 LLM 在处理 TOML 格式时是否会遇到难题。但在我所使用的顶级 LLM 中,并没有发现明显的格式处理问题。我认为,TOML 相较于 YAML 的简洁性在一定程度上弥补了这一普及度差距。而且,YAML 有多种方式可以表达相同的数据,这可能会降低 LLM 的确定性,使得两种格式的可靠性相差无几。
根据我的个人经验,TOML 和 YAML 都可能出现错误,但这些错误通常可以通过更精确的提示词来解决。
关于 YAML 中的字符串和转义序列的问题,TOML 也同样存在。
总体而言,TOML 和 YAML 非常相似,TOML 需要更多的 token,不支持 top-level lists,但对于使用 Python 3.11 或以上版本的用户来说,不需要额外的解析库。
3.6 JSON
关于 JSON,其实没什么特别需要强调的。它之所以能成为默认格式,是因为它用途广泛、易于解析,而且出错率低。只是它包含了大量的引号、逗号、冒号和换行符,这些都增加了 token 的数量,这一点稍显遗憾。
值得注意的是,如果你想要使用 LLM 服务商提供的“结构化数据模式”,或者使用像 Guardrails[3]、Outlines[4] 这样的结构化工具,JSON 往往是唯一的选择。但我认为这种情况会随着时间的推移而有所改变。随着越来越多的基于 LLM 的应用投入实际使用,开发者会开始关注如何减少 token 使用等优化措施,LLM 服务商也会通过支持更多结构化数据格式来满足这一需求。
理想的情况是,LLM 服务商能够调整模型,使其能够可靠地处理多种格式的数据,并在结构化数据模式中提供这些格式作为可选项。
这里有一个注意事项:对于有关 LLM 在输出特定格式时的可靠性方面的旧建议,我们应该保持谨慎。正如 OpenAI 在 2024 年 8 月的一篇博客文章中所提到的[5],GPT-4 的早期版本在处理复杂的 JSON 测试时的正确率仅为 35%,而较新版本的 GPT-4 正确率则高达 85%。这在 GPT-4 系列中是一个巨大的飞跃。
这一点对于使用某些特殊功能或软件包的人来说尤为重要,这些功能或软件包可能会基于一年或更久之前的假设或证据来强制输出结构化数据。你可能并不需要这些功能或软件包,它们可能会迫使你使用 JSON,而实际上你可以选择更经济、更快捷的格式。
04 实际应用
在理论层面这些格式各有优势,但假设你已经有了一套使用 JSON 的结构化数据处理系统,并且运行得很顺畅。你知道 TSV 格式也能适用于你的数据,那么是否有必要进行格式转换呢?
如果你关注的是速度——因为人们需要等待 LLM 生成 token,那么你需要评估等待时间的价值,这部分在这里不展开讨论。
但如果你只是在后台运行一个进程,这个问题就简单多了。举例来说,我们可以设定以下假设条件:
- 你的时间成本是每天 1000 美元
- 将现有系统从 JSON 转换为 TSV 需要半天时间
- 输出 token 的费用是每百万 0.60 美元
- 使用 TSV 可以减少 50% 的 token 使用量
- 你希望一年内收回投资
我们可以用一个小 Python 脚本来计算这些数值:
根据计算,如果你现在每天生成大约 4,566,210 个 JSON token,一年后就能实现收支平衡。
当然,你应该根据自己的实际情况来调整这些数值,但以下基准数据可供你参考。如果你每天只生成几千个结构化数据的 token(并且不介意速度),那么盲目调整数据格式的性价比极低。但如果你每天需要处理数千万个 token,那么探索其他格式绝对是一个划算的决定。
05 总结
选择默认的 JSON 格式确实很有吸引力,因为它灵活、稳定且解析起来简单。但相对而言,它的处理速度较慢,成本也更高。因此,不妨考虑一下其他数据格式,做一些测试,挑选出既能控制成本又能保证稳定性和速度的最佳选项。
Thanks for reading!
Hope you have enjoyed and learned new things from this blog!
About the author
David Gilbertson
I like machine learning stuff.
END
本期互动内容 🍻
❓你在实际项目中最常用哪种数据格式?遇到过哪些意想不到的问题?欢迎分享经验👇
🔗文中链接🔗
[1]https://gist.github.com/davidgilbertson/fcabb55478b4a4e1537a706f808b8b09
[2]https://docs.python.org/3/library/tomllib.html
[3]https://github.com/guardrails-ai/guardrails
[4]https://github.com/dottxt-ai/outlines
[5]https://openai.com/index/introducing-structured-outputs-in-the-api/
原文链接:
https://david-gilbertson.medium.com/llm-output-formats-why-json-costs-more-than-tsv-ebaf590bd541
