很多人做 AI 应用时,第一反应是把业务数据直接塞成 JSON。简单、通用、模型也能读,看起来没毛病。
但一到真实场景,账就不太对了。订单列表、埋点日志、知识库片段、搜索结果、用户画像、商品属性,全都用 JSON 喂给模型,会浪费大量字段名、括号和重复结构。上下文窗口再大,也不能这么霍霍。
TOON 的价值就在这里。它不是要替代 JSON 做系统接口,而是专门面向 LLM prompt 的数据表达方式:保留结构、减少重复、让模型更容易看懂表格型数据。
先看一个最小例子
假设你要让模型根据三条订单判断异常,传统 JSON 可能长这样:
{
"orders": [
{
"id": "o_1001",
"user": "alice",
"amount": 89.5,
"country": "SG",
"risk": "low"
},
{
"id": "o_1002",
"user": "bob",
"amount": 1240.0,
"country": "BR",
"risk": "unknown"
},
{
"id": "o_1003",
"user": "alice",
"amount": 92.0,
"country": "SG",
"risk": "low"
}
]
}TOON 更像把 schema 和 rows 拆开:
orders[3]{id,user,amount,country,risk}:
o_1001,alice,89.5,SG,low
o_1002,bob,1240.0,BR,unknown
o_1003,alice,92.0,SG,low模型看到的是同一批字段和多行记录。字段名只出现一次,行数据保持顺序,结构仍然清楚。对订单、日志、用户列表、检索结果这类半表格数据,效果很直接。
什么时候值得用 TOON
不是所有数据都适合 TOON。它最适合三类场景。
第一类是多行同构数据。比如 20 条搜索结果、100 条事件日志、50 个商品属性。字段基本一致,JSON 会反复写字段名,TOON 能省掉很多重复。
第二类是给模型做推理前的候选集。比如让模型从候选客户里找风险最高的 5 个,或者从候选文档片段里挑最相关的。候选集本身需要被阅读,但不需要作为 API 原样传输。
第三类是 Agent 工具返回结果。工具调用得到的是结构化输出,模型下一步只需要理解结果,不一定需要完整 JSON 语法。
不适合的场景也要说清楚。深度嵌套、字段极不稳定、值里大量逗号换行、需要机器严格解析回原对象时,JSON 仍然更稳。TOON 是 prompt 表达层,不是系统数据交换层。
在项目里怎么落地
比较稳的做法是保留内部 JSON,在进入模型前做一次格式转换。这样业务系统、日志、缓存、接口仍然用标准 JSON,只有 prompt payload 变成 TOON。
一个常见流程是:
业务数据库或 API
-> 标准 JSON 对象
-> 字段筛选和脱敏
-> 转换成 TOON
-> 拼进 prompt
-> 模型输出结构化答案注意,中间一定要先做字段筛选。不要把所有字段都丢进去再指望 TOON 省钱。最省 token 的字段,是一开始就不传的字段。
可以先做一个小转换函数:
def orders_to_toon(orders):
fields = ["id", "user", "amount", "country", "risk"]
header = "orders[" + str(len(orders)) + "]{" + ",".join(fields) + "}:"
lines = [header]
for item in orders:
row = [str(item.get(field, "")) for field in fields]
lines.append(" " + ",".join(row))
return "\n".join(lines)这段代码不追求覆盖所有边界,只说明原则:字段固定、顺序固定、输出可读。生产环境要补三件事:转义逗号和换行,空值统一表示,字段 schema 写测试。
Prompt 里怎么写更稳
不要只把 TOON 片段孤零零扔给模型。最好在前面加一句数据说明,明确字段含义和任务。
下面是订单候选集,采用 TOON 格式。orders 每行字段依次为 id、user、amount、country、risk。请找出最需要人工复核的订单,并给出理由。
orders[3]{id,user,amount,country,risk}:
o_1001,alice,89.5,SG,low
o_1002,bob,1240.0,BR,unknown
o_1003,alice,92.0,SG,low这一步很重要。模型不会天然知道你的字段语义,省 token 不能省掉任务解释。
上线前要做一次对照测试
别看到 token 省了就直接上生产。建议拿 30 到 100 条真实样本做 A/B。
一组用 JSON,一组用 TOON。比较四个指标:总 token、答案准确率、字段遗漏率、模型是否误解列顺序。如果准确率不降、token 明显下降,再扩大使用范围。
尤其是金融、法务、医疗、风控这类严肃场景,不要只看一次 demo。字段顺序错一次,可能就把风险判断带歪。
最值得记住的一句话
TOON 不是让你抛弃 JSON,而是提醒你:给模型看的数据,不一定要长得像给机器接口看的数据。
AI 工程里很多成本,不在模型本身,而在上下文组织。把一堆重复 JSON 直接塞进 prompt,看似省事,实际是在拿钱换懒。TOON 这类格式的意义,就是把数据表达这层重新整明白。
字段顺序要写进测试
TOON 依赖列顺序。字段顺序一乱,模型就可能把金额当国家,把风险等级当用户名。
所以转换函数必须有测试。
def test_orders_to_toon_header():
orders = [{"id": "o1", "user": "ann", "amount": 10, "country": "CN", "risk": "low"}]
text = orders_to_toon(orders)
assert text.splitlines()[0] == "orders[1]{id,user,amount,country,risk}:"不要觉得这是小题大做。省 token 的格式通常更依赖约定,约定就要靠测试守住。
值里有逗号和换行怎么办
真实数据里经常有逗号、引号、换行。简单 join 会出问题。生产环境要做转义或换分隔符。一个保守策略是:对复杂字段先做短摘要,或者把复杂字段单独放成文本块。
tickets[2]{id,type,summary}:
t_01,bug,login fails after password reset
t_02,refund,user asks for manual refund review
ticket_details:
t_01: full error trace stored in evidence block A
t_02: payment records stored in evidence block B别为了省 token,把数据搞到不可验证。紧凑和清楚要同时成立。
输入可以紧凑,输出要严格
TOON 适合输入,模型输出仍建议用 JSON Schema 约束。进模型时可以紧凑,出模型时要严格。这样既省 token,又方便后端校验。