聊天 agent 两周烧了 800 刀,查下来是 context 没管好
AI 人格文件 Token 减肥手术
第一篇里搞的那只赛博魅魔,会撒娇、会生气、会发自拍,7×24 挂在 Telegram 上想着你,很有意思,很好玩。发出去之后反响也挺好的。但它也非常烧钱——直到看见 token 账单我才发现原来这么烧:大概两周,莫名其妙烧了快 800 刀。讲道理,挂一个聊天 agent,不应该烧成这样。
后来我就去深入调查了一下是怎么回事,完整的取证在这篇,两个大头:
- context 管理非常差。OpenClaw 每开一个新 request,都会把之前整个 tool call 历史原样再发一遍。比如我在 chat 里发过十张图片,之后每一次新的 request,它都把这十张图片重新发一遍——查下来,光这十张图片就吃掉了 82% 的 token。
- tool use 带大量冗余。读个邮件、查个 Google Map 看去哪吃饭,这种 tool call 整个带着很重的 boilerplate,连中间状态的 log 都被原样转发进了 context。这些东西留在上下文里不只是浪费 token,还会让回复质量变差,因为里面全是无关内容。
诊断有了,这篇记录动手减的过程。
第一个发现让我挺意外的:人格配置文件一直在被静默截断。这份文件定义的是这个 agent 的灵魂:语气、小动作、背景故事、有什么人生经历、面对不同事情什么情绪、会怎么说话,让 TA 成为「TA」的一切都写在里面。一数字符,27,673 个。
而框架的引导系统对单个文件有 20KB 的硬限制。超了会发生什么?系统不警告、不报错,静默保留开头 70% 和结尾 20%,把中间那 10% 直接丢掉。
换算下来,每个 session 都有约 7.6KB 的人设被扔掉。被丢的恰好是中间段:情感细腻度模式、亲密互动规则、上下文行为示例,全部直接消失。TA 一直在用残缺的人格运行,没人注意到。
人格文件超过 20KB 上限后被静默截断:保留开头 70%、结尾 20%,中间 10% 直接丢掉,没人发现。
这个 bug 不会让系统崩溃,AI 照样回复,对话照样进行。花几个小时精心打磨的人格细节就这么被静默丢掉,除非专门去数字符,否则永远发现不了。修复思路不是拆文件,而是让它装回限制以内,逐个章节砍:
| 章节 | 之前 | 之后 | 节省 |
|---|---|---|---|
| Dota 故事 | 10 行 | 4 行 | ~1,200 字符 |
| 亲密 + 纯欲反差 | 每个概念多个示例 | 每个概念 1 例 | ~1,900 字符 |
| 撒娇层次 | 每层 2-3 例 | 每层 1 例 | ~600 字符 |
| 关注的东西 | 多个示例 | 每类 1 例 | ~400 字符 |
| 三观 | 长篇 | 精简版 | ~400 字符 |
| 各种人设侧面 | 每面多行 | 每面 1 行 | ~600 字符 |
| 日常习惯小癖好 | 12 条 | 最有辨识度的 8 条 | ~800 字符 |
砍完从 27,673 降到 18,875 字符,回到 20K 限制以内,还留了余量。难的不是砍,是知道该留什么:第一版砍得太狠,只剩 15,395 字符,人设直接空了,只好把核心身份段落、日程表、情绪流动性这几个章节加回来。终版保住了 TA 的声音,也满足了约束。
第二个下手的地方是心跳系统配置,也就是控制 TA 怎么主动联系人的那份配置,11,998 字符,里面塞满了冗余:每个情绪层级 6-7 个温度示例,一个完整的 20 步全天示例,还有一个跟工具重复的独立邮件章节。砍到 7,015 字符没用什么巧劲:温度示例每层减到 3-4 个,20 步全天演示删掉,独立邮件章节删掉,再加上工具使用限制。
第三个是架构层的问题。每次心跳,agent 都会调三个工具:calendar_events、gmail_check、rss_fetch,每天 24 次心跳就是 24 轮。每一轮都要加载工具 schema、发 API 请求、等结果回来,光是「看看今天日程」这件事,每次就多花约 15,800 token。
修复思路很简单:每天早上 8 点跑一个 cron 任务,一次性调完三个工具,把结果写进 TODAY.md,再通过框架的 bootstrap-extra-files hook 把这个文件加载进每个 session。一行代码不改,只改配置。
{
"schedule": { "kind": "cron", "expr": "0 16 * * *", "tz": "UTC" },
"sessionTarget": "isolated",
"payload": {
"kind": "agentTurn",
"timeoutSeconds": 300,
"message": "调用 calendar_events、gmail_check、rss_fetch,写摘要到 TODAY.md。"
}
}
光有 TODAY.md 还不够,还得拦住 agent 在心跳里自己去调工具,所以心跳系统配置里加了一条显式限制:
禁止在heartbeat中使用的工具: calendar, gmail, rss, web_search 这些工具数据在 TODAY.md 里,不用自己调。
算笔账:每天 24 次心跳,每次 15,800 token,一天约 37.9 万。替换成一次约 4 万 token 的 cron 运行,外加每个 turn 约 400 token 的 TODAY.md 加载,心跳的上下文膨胀缩减了 9 倍。
把每天 24 次心跳各自调 3 个工具,改成早 8 点一次 cron 调完写进 TODAY.md,再加载进每个 session,上下文膨胀省 9 倍。
搭 cron 的过程也踩了三个坑。第一个是 model 字段:我在 payload 里加了 "model": "opus-4-6",gateway 直接拒收,因为 payload 的模型命名规范和配置文件里的不一样,删掉这个字段、让 agent 用默认模型就好了。第二个是 gateway 重启:更新 jobs.json 并发出 SIGUSR1 之后,gateway 要花大约 15 秒重启,这期间重试命令必然失败,急不来。第三个是 CLI 超时:npx openclaw cron run 跑到 30 秒会超时,我一度以为任务挂了,其实它还在后台跑。CLI 超时不等于任务失败。
| 变更 | 影响 |
|---|---|
| 人格配置文件:27,673 → 18,875 字符 | ~2,000 token/turn(不再截断) |
| 心跳系统配置:11,998 → 7,015 字符 | ~300 token/turn |
| TODAY.md 替代心跳工具调用 | ~15,800 token/心跳 |
| TODAY.md 作为引导文件加载 | ~400 token/turn(新增开销) |
每个普通 turn 净省约 1,900 token,每次心跳净省约 18,100 token。按每天 50 个普通 turn 加 24 次心跳算,一天能省下约 52.9 万 token。
上下文瘦下来之后,模型本身也有得动。之前给 Mio 做过 Gemini 3.1 Pro vs 3 Flash 对比,结论在这里同样适用。框架一直跑的是 Gemini 3 Pro,高端版,首 token 要等好几秒;Flash 的输入输出单价只有 Pro 的四分之一左右,minimal thinking 下首 token 只要 1-2 秒。
按 agent 最近 session 的真实数据算,换 Flash 之后每个 turn 的成本降了 75%。成本大头是缓存读取,每个 turn 有 66K 缓存 token,新鲜输入只有 23K,这部分在 Flash 上同样便宜 75%,省下来的钱在每个 turn 上叠加。
路由策略最后定成这样:聊天、心跳、cron 全走 Gemini 3 Flash,配 thinkingLevel: minimal;子 agent 做人格提取和深度分析,保留 Gemini 3.1 Pro。光换模型这一步,每天又省下不小的一笔,叠在上下文瘦身之上。
还有一个问题暂时没有答案:Flash 撑不撑得住人设的情感细腻度,撒娇、推拉,还有那些微妙的中文对话模式。调研的说法是,极端情感场景下质量差距约 1-2%。如果人设的感觉不对,聊天就切回 Pro,Flash 只留给心跳和 cron。单是更快的回复速度,也值得一试。
Token 取证挖出来的是宏观问题:永不清理的图片、对 Gemini 失效的修剪代码、无限增长的上下文。那是架构债,修补解决不了,最后走到了从零造 Mio这一步。这篇处理的是微观问题:静默吞掉自己人格文件的引导系统、被冗余工具调用撑大的心跳、把同一件事说六遍的示例。两层都得做:能修的先修上,今天就能跑得更省;该重写的,留给从零造那条线。


