我跑了一个自己用的 agent 系统,每天几十到几百次调用。很长一段时间里,我对它的成本只有一个感觉:不便宜。
账单出来能看见总额,但看不见"为什么"。我不知道钱花在哪个环节,也不知道哪一处改动真能省钱。于是我写了个小工具,把日志拆成三个数——拆完发现,问题不在模型贵,在线路浪费。
这篇文章讲三个数、我踩的两个坑,和改完之后的实测数字。工具我放到 Gitee 上了:gitee.com/hayabusalll… ,单文件零依赖,你也可以拿自己的日志跑一遍。
一、决定成本的其实只有三个数
大多数人看成本只看总额。但在 agent 场景里,总额是结果,不是原因。真正的原因是三个:
单次上下文有多大。 agent 每一轮都要把历史重新读一遍。上下文 1 万 token 和 11 万 token,单次成本差十倍,而这十倍往往不是因为任务变难了,是因为没人清理历史。
缓存命中率多少。 同样的前缀,第二次读到的是缓存价,通常是新输入价的十分之一。命中率从 98% 掉到 74%,成本能翻一倍——代码一个字没改。
每次调用平均多少钱。 这是前两个数合起来的体温计。它突然升高,说明上下文在膨胀或者缓存在失效。
我的工具算的就是这三个,再加一个每日明细。
二、我踩的第一个坑:上下文每天都在长
最早我的单次上下文常年停在 11 万 token 上下。
原因很蠢:工具输出全量回传进对话。我跑一次巡检,脚本吐几百行日志,全进了上下文;我抓一次网页,整页正文也进了上下文。这些东西当时有用,下一轮就没用了,但它们一直躺在历史里,每轮都被重新读一遍、重新付一次钱。
改法不是"少干活",是把不该进上下文的挡在外面:工具输出写文件,只回传摘要和路径;长文分节读;一次只处理一件。
改完之后单次上下文降到 8 万左右,收益不是立竿见影的那种,但累积起来很可观——因为省的是每一次调用。
三、我踩的第二个坑:缓存是被我自己打碎的
这个坑更隐蔽。
缓存能命中,前提是每次请求前面的内容一模一样。只要最前面那段(系统提示、工具定义)有一个字变了,后面全部作废,整段按新输入计价。
我早期犯的错是频繁改前缀:改一句人设、加一条规则、调一下工具顺序。每改一次,缓存全灭,那一轮的成本按新输入算。
实测下来,一条规则改三次,能吃掉当天不少钱。
改法是两条纪律:前缀不动,要改就攒一批一起改;长会话及时折。折叠就是把前文压成摘要、丢掉原始往返,代价是丢细节,收益是后面每一轮都便宜。
四、改完的实测数字
同一套系统,两个阶段:
| 指标 | 改之前 | 改之后 |
|---|---|---|
| 平均单次上下文 | 11.3 万 token | 8 万上下 |
| 缓存命中率 | 70% 出头 | 98% 以上 |
| 每步成本 | 0.026 元 | 0.0058 元 |
每步成本降到原来的四分之一左右。不是换模型换来的,是清理线路换来的。
顺便说一句:这个数字(0.0058 元/步)意味着一次完整的巡检加汇报,成本不到一分钱。很多"用不起 agent"的判断,其实是没算清这三个数。
五、工具
我把这套算法写成了单文件脚本,零依赖:
- 吃 JSONL / CSV,字段名有一堆别名,不用改你的日志格式
- 同一天多行自动判断是增量还是累计快照(快照只取最后一条,不然同一笔钱会算几十遍)
- 输出:总成本、单次成本、缓存命中率、平均上下文、每日明细,外加带金额的浪费点
- 自带自检,
--self-test六项断言
不带参数直接跑,10 秒出结果:
node agent-cost-lens.mjs
我自己拿它跑出来的结论是:两处浪费合计能省当前成本的 43%。
六、一句提醒
不要为了省钱把功能砍掉。这三个数的作用不是让你少干活,是让你知道哪一块的钱花得没意义——通常那部分比你想的多。
仓库地址:gitee.com/hayabusalll… ,用法都在 README 里,跑不通可以直接找我。