实战教程讲解如何组织和格式化 JSON 数据供 LLM 处理,直接提升 token 效率和模型输出质量。
如果你正在构建 AI 驱动的功能,很可能会向语言模型发送大量 JSON。而如果发送的 JSON 很多,token 的消耗速度可能也远超你的预期。
在 Kollabe,我们使用 AI 为回顾会议和站会生成摘要、行动项和建议。当数十名团队成员每天都在提交工作进展时,JSON payload 很快就会变得非常庞大。我们需要在不丢失信息的前提下缩减数据量。
目前已经出现了一些较新的正式解决方案,比如 TOON(Token-Oriented Object Notation),它可以将面向 LLM 的 JSON 压缩多达 40%。它有规范的标准、基准测试和 SDK。如果你希望采用一种标准化方案,值得了解一下。
但有时,你可能希望一切都掌握在自己手中:不想再增加一个依赖,希望准确了解发送给模型的内容,并根据自己的具体用例进行调整。下面这些简单技巧,正是我们在不增加复杂度的情况下减少 token 用量的方法。
UUID 无处不在。它们非常适合数据库,但在 token 效率方面表现糟糕。
// This UUID is 4-5 tokens
"550e8400-e29b-41d4-a716-446655440000"
// This is 1 token
"u-1"
当你需要在数百条站会记录中反复引用同一个用户时,这些额外的 token 很快就会积少成多。
解决办法是:在处理数据时建立一个简单的映射。遇到的第一个用户映射为 u-1,第二个映射为 u-2,以此类推。如果之后再次遇到同一个 UUID,就复用之前分配的短 ID。
// Before: UUIDs everywhere
{
odUserId: "550e8400-e29b-41d4-a716-446655440000",
odQuestionId: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
odAnswerId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
// After: short, prefixed IDs
{
uid: "u-1",
qid: "q-1",
aid: "a-1"
}
这里的关键在于,同一个 UUID 始终映射到同一个短 ID。因此,当 LLM 在不同回答中多次看到 u-1 时,它会理解这些记录属于同一个人。不同类型的实体要使用不同的前缀,这样模型才能区分用户 ID 和问题 ID。
JSON.stringify 有第二个和第三个参数,但大多数人都会忘记它们。第三个参数用于添加缩进:
// Pretty printed (wasteful)
JSON.stringify(data, null, 2);
// Minified (efficient)
JSON.stringify(data);
两者的差别如下:
// Pretty: ~80 characters
{
"name": "Alice",
"role": "Engineer",
"team": "Platform"
}
// Minified: ~45 characters
{"name":"Alice","role":"Engineer","team":"Platform"}
对于小对象来说,这点差别无所谓。但如果是数千条站会记录呢?这些空白字符会迅速累积。无论如何,LLM 并不在意格式是否美观。
想明白之后,这一点其实很直观。对比下面两种写法:
// Verbose
type StandupEntry = {
odUserId: string;
userName: string;
yesterdayUpdate: string;
todayPlan: string;
blockerDescription: string;
};
// Concise
type StandupEntry = {
odUid: string;
name: string;
yesterday: string;
today: string;
blocker: string;
};
当你有数百条记录时,更短的键名确实能够节省 token。只需要保证键名仍有足够的可读性,让 LLM 能够理解上下文。
我们遵循以下几条规则:
userId 可以直接写成 iddesc 代替 descriptiony 表示 yesterday 太难理解,但 yest 就没问题不要发送根本不存在的数据:
function removeEmpty<T extends object>(obj: T): Partial<T> {
return Object.fromEntries(
Object.entries(obj).filter(([_, v]) => {
if (v === null || v === undefined) return false;
if (v === "") return false;
if (Array.isArray(v) && v.length === 0) return false;
return true;
})
) as Partial<T>;
}
// Before
{
"name": "Alice",
"blocker": null,
"tags": [],
"notes": ""
}
// After
{
"name": "Alice"
}
如果某个人没有报告阻塞项,又何必专门告诉 LLM 呢?
有些时候,嵌套结构只会带来组织层面的额外开销:
// Before
{
"user": {
"profile": {
"name": "Alice",
"team": "Platform"
}
},
"update": "Finished feature"
}
// After
{
"name": "Alice",
"team": "Platform",
"update": "Finished feature"
}
第二种写法使用更少的结构性 token,表达了相同的信息。当然,如果层级关系本身承载了含义,就不要将其扁平化,但很多时候它并没有。
如果有一组结构相似的数据,可以考虑是否真的需要为每一项都保留完整的对象结构:
// Before: 3 objects with repeated keys
{
"entries": [
{ "name": "Alice", "status": "done" },
{ "name": "Bob", "status": "blocked" },
{ "name": "Carol", "status": "done" }
]
}
// After: header row + data rows
{
"cols": ["name", "status"],
"rows": [
["Alice", "done"],
["Bob", "blocked"],
["Carol", "done"]
]
}
这种方式牺牲了一部分可读性,换取更高的效率。对于大型数据集来说,这么做是值得的。
时间戳、审计字段和内部 ID 通常不是 AI 处理所必需的:
// Before: full database record
{
odAnswerId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
odUserId: "550e8400-e29b-41d4-a716-446655440000",
text: "Great sprint!",
createdAt: "2024-01-15T10:30:00.000Z",
updatedAt: "2024-01-15T10:30:00.000Z",
isDeleted: false,
version: 1
}
// After: just what the LLM needs
{
uid: "u-1",
text: "Great sprint!"
}
问问自己:模型真的需要这个字段,才能生成有用的回答吗?如果不需要,就删掉它。
对于布尔标记,可以考虑当它为 false 时,是否还有必要保留这个字段:
// Before
{ "name": "Alice", "isAdmin": false, "isActive": true, "isVerified": false }
// After: only include truthy flags
{ "name": "Alice", "active": true }
// Or use a flags array for multiple true values
{ "name": "Alice", "flags": ["active", "verified"] }
如果大多数用户都不是管理员,就不要在每条记录中都加入 isAdmin: false。
下面是 Kollabe 实际生成的一份回顾会议摘要在优化前后的对比:
{
"retrospectiveData": {
"questions": [
{
"odQuestionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"questionText": "What went well this sprint?",
"questionType": "positive"
},
{
"odQuestionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"questionText": "What could be improved?",
"questionType": "negative"
}
],
"answers": [
{
"odAnswerId": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"odQuestionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"odUserId": "550e8400-e29b-41d4-a716-446655440000",
"userName": "Alice Chen",
"answerText": "Team collaboration was excellent during the release",
"createdAt": "2024-01-15T10:30:00.000Z",
"voteCount": 3
},
{
"odAnswerId": "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b",
"odQuestionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"odUserId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"userName": "Bob Smith",
"answerText": "CI/CD pipeline improvements saved us hours",
"createdAt": "2024-01-15T10:32:00.000Z",
"voteCount": 5
},
{
"odAnswerId": "3f333df6-90a4-4fda-8dd3-9485d27cee36",
"odQuestionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"odUserId": "550e8400-e29b-41d4-a716-446655440000",
"userName": "Alice Chen",
"answerText": "Documentation was often outdated",
"createdAt": "2024-01-15T10:35:00.000Z",
"voteCount": null
}
]
}
}
{"qs":[{"id":"q-1","text":"What went well this sprint?","type":"positive"},{"id":"q-2","text":"What could be improved?","type":"negative"}],"ans":[{"id":"a-1","qid":"q-1","uid":"u-1","name":"Alice Chen","text":"Team collaboration was excellent during the release","votes":3},{"id":"a-2","qid":"q-1","uid":"u-2","name":"Bob Smith","text":"CI/CD pipeline improvements saved us hours","votes":5},{"id":"a-3","qid":"q-2","uid":"u-1","name":"Alice Chen","text":"Documentation was often outdated"}]}
q-1、a-1、u-1)odQuestionId 改为 qid,将 answerText 改为 text)retrospectiveData)voteCount: null)优化后的版本大约缩小了 50%。当你需要处理一个 50 人团队的回顾会议数据,其中包含数百条回答时,这会显著降低 token 成本,同时缩短推理时间。
并非所有 JSON payload 都需要进行这样的处理。如果只是发送一个小型配置对象或一条用户查询,那么优化所带来的额外工作并不值得。
但当你构建的功能需要处理大量结构化数据时——就像我们在 Kollabe 中处理回顾会议和站会摘要一样——这些技巧会带来切实的效果。它们实现简单,不需要外部依赖,而且能够立即见效。
让数据管道始终处于自己的掌控之中,同样很有价值。当你亲自编写优化层时,就能准确理解其中发生的一切。你可以调整短 ID 的前缀,决定删除哪些字段,并随着数据的演变不断调整策略。不需要面对任何黑箱。
最棒的是,LLM 完全可以正常处理优化后的 JSON。它们不需要美观的格式,也不需要冗长的键名,就能理解你的数据。它们真正需要的只是信息本身。
对了,最后再厚着脸皮宣传一下:如果你在敏捷开发团队中工作,可以试试我开发的免费 Planning Poker 和回顾会议工具 Kollabe。我们的 AI 摘要正是依靠上面的所有技巧实现的。
部分评论可能仅对已登录的访客可见。登录后可查看全部评论。
如果需要采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。