使用 Toon 格式节省 Token 消耗
本文介绍了如何使用 Toon 格式来减少与大模型交互时的 Token 消耗。
完整的代码在 GitHub JavaAIDev/toon-spring-ai.
在 AI 应用的开发中,大模型的输入和输出 token 数量一直是需要关注的问题。现在的模型服务基本上都按照输入和输出 token 的数量来收费,应用使用的 token 数量直接决定了运营的成本。虽然现在模型服务的 token 价格在快速下降中,但是只要应用还在运行中,token 所带来的成本就一直存在。如果输入的 token 数量过大,不仅可能遇到模型的上下文窗口的上限,还可能由于提示中的内容过多,反而影响大模型的输出质量。基于上述的原因,节省输入和输出的 token 就成为了一个现实的问题。
结构化数据的格式
如果要在输入和输出中包含结构化数据,通常有几种格式可以选择,JSON,CSV 和 XML。
JSON 是目前使用最广的一种格式,这一方面得益于 JSON 格式的流行,有大量的工具和第三方库可供使用。另外一方面是由于 JSON schema 的大量使用,也促进了 JSON 的流行。MCP 协议中工具的输入格式,使用的也是 JSON schema。当然了,在不同的地方,所支持的JSON schema 的语法不尽相同。
JSON 的语法存在一定的冗余,尤其是在表示数组时。数组中包含的元素的类型通常是相同的,具有相似的结构。每个元素在表示时,它们的字段都会被重复。
对于表格型的数据,CSV 是最有效的格式,所增加的额外开销最少,因为只需要在第一行添加字段的名称即可。
JSON 格式对于表示嵌套层次复杂的数据,有其独特的优势。这是CSV无法表达的。
在现实的开发中,实际的数据介于完全的嵌套结构,与表格型之间。数据中内嵌的表格越多,使用 JSON 格式所产生的额外开销就越大,因为重复的字段名称越多。
Toon
Toon,作为一种新的数据格式,尝试解决 JSON 格式开销过大的问题。Toon 是 Token-Oriented Object Notion 的缩写,含义是面向 token 的对象表示。Toon 是 JSON 数据的一种编码方式。Toon 格式的出发点是在与大模型进行交互时,优化 token 的消耗。应用发送请求给大模型时,把数据使用 Toon 编码。要求大模型使用 Toon 格式进行输出,再把 Toon 转换成对象或 JSON 格式。
Toon 在格式上,可以看成是 YAML 和 CSV 的组合。使用类似 YAML 的格式表示嵌套的对象结构。对于数组,使用类似 CSV 的格式,字段的名称只会出现一次。还会包含数组的元素个数,方便解析。
Toon 使用与 JSON 相同的数据模型,包括基本类型,对象和数组。基本类型包括字符串,数字,布尔类型和 null,对象是名值对,数组是值的序列。简单的对象使用 key: value 的格式。内嵌的对象使用缩进来表示内嵌的层次。对于数组,数组的长度,以 [] 的方式添加到字段的名称之后。如果包含的是基本类型,这些值编码之后作为单个值。如果包含的是结构相同的表格型数据,则以 {} 包含字段的名称,数据以 CSV 的格式编码。如果包含的是结构不同的数据,则以类似 YAML 的格式,使用 - 来分隔多个元素。
下面是 Toon 格式的示例,从中可以看到基本类型,对象和数组的表示方式。
context:
task: Our favorite hikes together
location: Boulder
season: spring_2025
friends[3]: ana,luis,sam
hikes[3]{id,name,distanceKm,elevationGain,companion,wasSunny}:
1,Blue Lake Trail,7.5,320,ana,true
2,Ridge Overlook,9.2,540,luis,false
3,Wildflower Loop,5.1,180,sam,true
与大模型交互时使用 Toon
在与大模型交互时,当需要在请求中包含结构化数据时,使用 Toon 编码之后,包含在提示中。
如果希望大模型产生结构化的输出,可以在提示中描述 Toon 的格式,并要求大模型给出 Toon 格式的输出,再使用类库解析即可。
不过,不建议使用 Toon 作为大模型的输出格式,这主要是因为大模型并不了解 Toon 格式,需要在提示中进行描述。这个描述可能是不够准确的。大模型对 JSON 格式的支持很好,不需要在提示中描述。大模型对 JSON schema 的支持,也方便了生成满足 schema 要求的数据。从结构化输出的角度出发,使用 JSON 是最好的选择,很多模型服务已经原生提供了对输出的 schema 的支持,并不需要在提示中包含。这就进一步提升了结构化输出的质量。
Spring AI 集成
在应用中,使用 Toon 提供的类库就可以提供 Toon 格式的编码和解码。Java 也有同样的类库。下面通过一个示例来说明。示例使用的是 Spring AI 与模型交互。
使用 Toon 时添加相关的 Maven 依赖。使用的入口类是 JToon,encode 和 decode 方法分别进行编码和解码。
<dependency>
<groupId>dev.toonformat</groupId>
<artifactId>jtoon</artifactId>
<version>1.0.6</version>
</dependency>
为了展示 Toon 的格式,这里创建了一个 User 对象,User 类的声明如下所示,其中包含了 Address 这个内嵌的对象。
package com.javaaidev.toon.model;
import java.util.List;
public record User(String id,
String name,
String email,
String mobilePhone,
List<Address> addresses) {
public enum AddressType {
HOME,
OFFICE,
OTHER,
}
public record Address(
String id,
AddressType addressType,
String countryOrRegion,
String provinceOrState,
String city,
String addressLine,
String zipCode) {
}
}
代码中生成了 3个 User 对象。包含了这3个对象的数组,分别使用 JSON 和 Toon 进行编码。
package com.javaaidev.toon;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.javaaidev.toon.model.Users;
import dev.toonformat.jtoon.JToon;
public class ToonUtils {
private static final ObjectMapper objectMapper = new ObjectMapper().enable(
SerializationFeature.INDENT_OUTPUT);
public static void main(String[] args) throws JsonProcessingException {
var json = objectMapper.writeValueAsString(Users.USERS);
var toon = JToon.encode(Users.USERS);
System.out.println("""
JSON:
%s
TOON:
%s
""".formatted(json, toon));
}
}
从两者的结果可以看到,Toon 比 JSON 节省了 45% 的 token。

在使用 Spring AI 与大模型交互时,可以使用下面这样的提示模板。对 Toon 格式进行了简单的描述。这就足够了,大模型可以自行对内容进行解析。
package com.javaaidev.toon.controller;
import com.javaaidev.toon.model.Users;
import dev.toonformat.jtoon.JToon;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ToonController {
private final ChatClient client;
public ToonController(ChatClient.Builder builder) {
this.client = builder.build();
}
@PostMapping("/request")
public ChatOutput request(@RequestBody ChatInput input) {
var output = client.prompt().user("""
%s
Data is in TOON format (2-space indent, arrays show length and fields).
```toon
%s
```
""".formatted(input.input(), JToon.encode(Users.USERS)))
.call()
.content();
return new ChatOutput(output);
}
public record ChatInput(String input) {
}
public record ChatOutput(String output) {
}
}
在大模型的实际处理中,把查询和数据发送给模型之后,模型可以正确的完成解析。

总结一下,使用 Toon 格式对发送给大模型的数据进行编码,可以减少 token 的消耗。