给 Agent 的动态知识大纲加 token 预算:热表完整注入、冷表折叠降级的做法
学习目标:学完这篇,你会给自己的 Agent 加一套"动态知识注入的 token 预算"——知识条目再多,每轮注入的 token 也有封顶;预算内给完整信息,超预算自动降级折叠。知识越多,提示词不再线性变贵。
0. 先说问题
我的 daw 是一个查数 Agent,它维护着一张"已注册表"清单:每张表有状态行、字段释义、关联关系。每轮对话,都会把这份清单的大纲拼进发给模型的提示词——模型得先知道有哪些表,才能决定查哪张。
问题出在"每轮"两个字上。这份大纲每个 LLM 请求都会全量重发一次。31 张表,完整大纲约 900 token。今天 31 张,明年可能是 300 张——提示词成本随知识积累无上限线性膨胀,越用越贵。
翻译成人话:知识大纲是 Agent 的"常用联系人列表",联系人越多,每次打电话前的自我介绍就越长。得给它设个话费封顶。
1. 前置知识:先有一个靠谱的 token 估算器
做预算之前,先得有个估算器。daw 的 usage::estimate_tokens(src-tauri/src/usage.rs)按字符脚本估算:ASCII 0.25 token/字符(4 个字母约 1 token)、CJK 1.3 token/字符、其他 0.5。
有人可能会问,为啥不用 tiktoken 精确算?因为这个估算跑在每轮提示词组装的热路径上,要快、要零依赖。预算本来就是"封顶"用的,不是计费用——估算偏差方向一致就行,封顶逻辑不依赖精确值。
2. 第一步:定预算常量,封顶不封底
/// 注入 preamble 的"已注册表"小节的 token 预算。
/// 大纲随注册表数量无上限膨胀(每张表的字段释义都进 preamble,而 preamble
/// 在每个 LLM 请求上全量重发)。预算内的表保留完整信息,之外的降级为名称
/// 清单,超出 6 张时折叠成一行提示(用 list_tables 按需查看)。
/// 当前 31 张注册表的完整大纲约 900 tokens,预算 1000 留有余量且封顶增长。
const SUMMARY_TABLES_BUDGET: u64 = 1000;
翻译成人话:预算是"天花板"不是"KPI"——表少的时候不硬凑,表多了自动收敛。1000 这个数不是拍脑袋定的,是按当前真实体量(约 900)+ 增长余量定的。定预算前先量一量现状,这是个好习惯。
3. 第二步:按"最近使用"排序,热表优先进预算
render_registered_tables(src-tauri/src/okf/catalog.rs)先把表排序:按 last_explored(上次被查阅的时间)降序,同时间再按表名字母序:
entries.sort_by(|a, b| {
b.last_explored
.unwrap_or(0)
.cmp(&a.last_explored.unwrap_or(0))
.then_with(|| a.local_name.cmp(&b.local_name))
});
为啥按最近使用排?因为用户总在最近查的几张表里打转——刚查过的表,下一轮最可能被问。热表优先进预算,冷表降级,几乎不影响可用性。排序键一定要有业务含义,别用字母序硬排,那样降级的可能是明天就要用的表。
4. 第三步:逐条装配,超预算的只留名称行
遍历排序后的表,每条先算出完整条目(状态行 + 字段释义 + 关联关系)的 token 成本:
for e in entries {
let mut block = entry_status_line(e);
block.push_str(&render_columns(&ts.columns));
block.push_str(&render_relations(&ts.relations));
let cost = crate::usage::estimate_tokens(&block);
if emitted_full && used + cost > budget {
name_only.push(e); // 超预算:只留名称行
} else {
out.push_str(&block); // 预算内:完整条目
used += cost;
emitted_full = true;
}
}
注意 emitted_full 这个细节:第一张表不管超不超预算都完整输出。预算再小,也不能让模型连一张表的完整信息都拿不到——这是底线。
5. 第四步:冷表积压超 6 张,折叠成一行
降级成名称行的表如果超过 6 张,就不逐条列了,折叠成一行提示,只预览前 4 个冷表名:
out.push_str(&format!(
"- (另有 {} 张表:{}…,需要时用 list_tables / list_okf_knowledge 查看)\n",
name_only.len(),
preview.join("、")
));
这行提示是整个设计里最关键的一笔:它给模型留了"按需拉取"的钩子。模型真需要某张冷表,自己调 list_tables 去查,而不是在提示词里常驻。
翻译成人话:折叠不是"扔掉",是"收进抽屉,抽屉上贴了标签"。知识还在,需要时拿得到,只是不占每轮的固定开销。
6. 第五步:同模式复用到"选表经验"小节
daw 还有个"选表经验"小节(哪类问题首选哪张表、交叉验证用哪张),条目随主题积累同样无上限——同套路再来一遍:
const SUMMARY_SELECTIONS_BUDGET: u64 = 400;
超预算降级为名称清单,超 6 条折叠为"另有 N 条选表经验"。同一模式,两处复用。做完你会发现:凡是"随使用无上限增长、又要每轮注入"的动态知识,都值得套这套预算装配。
7. 为什么这样设计
三条理由:
- 局部性原理:用户 80% 的问题集中在最近用的几张表里,热表完整注入覆盖了绝大多数场景,冷表降级几乎无感。
- 降级不断链:折叠行永远带"按需拉取"的工具指引,模型需要时自己取——预算限制的是"常驻成本",不是"可达性"。
- 预算锚定现实:1000 按当前 31 张表约 900 token 的实测定,留余量、封顶增长。预算数字要有出处,不能拍脑袋。
8. 避坑清单
- 预算是封顶不是目标:表少时不硬凑内容填满预算,那样是浪费。
- 第一张表保底完整输出:别让预算把模型饿死,底线要有。
- 徽标必须自带行为指令:大纲里的表状态徽标,我最早用
[pushdown],结果被模型读反成"可直接查",导致 15 次 24–45 秒的全表拉取慢查询。改成[下推表·禁直查]后才消停。徽标是给模型看的 UI,语义自解释比术语精确更重要。 - 排序键选业务含义强的:最近使用 > 字母序。降级顺序决定了超预算时先丢谁。
9. 总结
动态知识注入的预算装配,五步:估算器 → 定预算(按实测量体量)→ 按业务含义排序 → 逐条装配(超预算降级名称行、首条保底)→ 积压折叠(一行提示 + 按需拉取钩子)。核心思想一句话:常驻提示词只付"热知识"的钱,冷知识走按需拉取。
给开发者的建议:如果你也在做 Agent,先去量一下你每轮提示词里动态部分有多大——八成有惊喜。量完再定预算,别拍脑袋。
项目开源在 GitHub:tsingliuwin/daw,觉得有用欢迎点个 star。
本文提出一种面向Agent的动态知识大纲token预算管理方法,通过热表完整注入、冷表折叠降级实现提示词成本可控。核心步骤包括:构建轻量token估算器、设定封顶预算常量、按最近使用频次排序、逐条装配并保底首表完整、冷表超6张时折叠为带按需拉取钩子的提示行。该机制基于局部性原理,保障高频知识可用性,同时将冷知识移出常驻提示词,显著抑制token消耗线性增长。
358

被折叠的 条评论
为什么被折叠?



