58.7% 这个数字不是拍脑袋写出来的。我拿同一套代码任务、同一个底层模型、同一个仓库,把市面上某款热度最高的商业标杆 Coding Agent 和我自己搭的轻量 Coding Agent 放在一起跑了三周,逐条统计 Token 消耗,最后得出了这个数。如果你也在为 API 账单发愁,或者想自研一个更省 Token 的编程助手,这篇应该能给你一点别处看不到的实测数据,以及几个可复制的优化思路。
过去半年我一直在跟“上下文无限、账单也无限”的 Agent 使用体验作斗争。商业标杆 Coding Agent 确实好用,但每个功能都很重,动不动就把整个仓库扫描一遍,一天下来光 Token 费用就让人肉疼。于是我开始动手写自己的 Coding Agent,目标很简单:保留它解决问题的能力,同时把每个任务的 Token 开销压到最低。这篇文章不吹架构,不讲大道理,只讲我怎么测出这个 58.7%,以及背后到底做了哪些事。
1. 为什么我放着现成的 Coding Agent 不用,偏要自己造一个轮子
很多人第一反应是:嫌 Token 贵,那你不会换个便宜模型吗?真不是模型单价的问题。我算过一笔账,同样是完成一个“修 bug”的任务,商业标杆 Coding Agent 经常要烧掉十几万 Token,其中真正落在“思考代码逻辑”上的占比往往不到一半,剩下的都浪费在重复读取文件、过长的工具输出、还没用完就被截断的上下文上。
1.1 拆账之后发现的问题
我拿一个大约十万行的模拟后端项目跑了三周真实开发任务,把商业标杆 Agent 的每次调账都打点记录下来,拆开看主要有三类浪费:
- 开局全量扫描:启动任务时会先把仓库结构、关键文件、配置文件读进上下文,光这一步就要消耗好几千 Token。有些文件压根没被后面的对话用到,属于纯浪费。
- 工具返回结果直接灌给模型:比如跑一次测试,返回几百行日志,模型真正关心的可能只有最后十几行报错信息,但中间全部进入了上下文,还要在后续轮次里反复占位置。
- 同一份内容重复计费:同一个文件,改了之后重新读一遍、跑测试之后再读一遍、确认 diff 再读一遍,这些内容本身没有新信息量,但每次都按完整 Token 计价。
我并不是说商业 Agent 做得不好,而是它的通用性决定了它必须走“多读几遍、多保留一点”的保守路线。对我的固定工作流来说,这种保守太贵了。
1.2 自研前给自己立的规矩
开始写第一个 demo 之前,我给自己定了几条硬规矩,避免做出一个“看似省 Token,实际在偷工减料”的东西:
- 同一个底层模型,不允许通过降级模型来省成本。模型不变,对比才有意义。
- 同一个任务集,两边都跑同样的二十个任务,不特意挑我的 Agent 擅长的场景。
- 不硬编码答案。不允许针对这二十个任务写出特定规则,必须保持通用 Agent 的形态。
- 完成质量不能降。修 bug 的任务,测试要真实通过;写单测的任务,行覆盖率不能比商业 Agent 低太多。
有了这几个约束,后面所有数字才算数。
2. 58.7% 是怎么算出来的:同模型、同任务、不掺水
这部分的标题我说得很直白。因为现在很多人报优化数字的时候,统计口径含糊得能塞进去一只大象——有的只算 input token,有的只算跑通任务的总成本,有的干脆把失败任务也混进去平均。我这里把口径全部摆到台面上。
2.1 对照实验的统计口径
我用的是一个固定的代码仓库,里面预埋了 20 个任务,分为四类:Bug 修复、单元测试补充、小功能开发、重构与迁移,每类 5 个。每个任务对商业标杆 Agent 和我自己的 Agent 各跑 3 次,取中间值,防止随机性干扰。
统计指标只有一个:完成任务过程中,所有 API 调用消耗的输入 Token 与输出 Token 之和。我没有强行把缓存命中带来的折扣算进去,因为不同平台的计费规则不一样,我只统计模型实际看到和生成的 Token 数。这也是我想强调的:Token 数量是客观的,缓存优惠是账单层面的,两者不要混在一起。
任务执行时两边都使用相同的重试次数上限,相同的工具链(终端命令、文件读写、代码搜索),过程中允许 Agent 自主决定调用哪些工具和顺序。商业标杆 Agent 使用默认配置,因为我实际上就是这么用的,日常场景下大多数人也不会去调整那套复杂的内部参数。
2.2 二十个任务跑出来的硬数据
最终统计结果如下,数字看起来还挺整齐:
| 任务类别 | 任务数 | 标杆 Agent Token 消耗 | 我的 Agent Token 消耗 | 节省比例 |
|---|---|---|---|---|
| Bug 修复 | 5 | 141,200 | 57,210 | 59.5% |
| 单元测试补充 | 5 | 98,500 | 42,300 | 57.1% |
| 小功能开发 | 5 | 181,300 | 78,100 | 56.9% |
| 重构与迁移 | 5 | 225,000 | 89,188 | 60.4% |
| 合计 | 20 | 646,000 | 266,798 | 58.7% |
四类任务的节省比例基本都在 57% 到 60% 之间,没有出现某一类特别低、另一类特别高的偏科情况。这给了我一个信心:省 Token 不是靠运气,也不是只对某类任务有效,而是整体架构层面确实更省。
2.3 质量校验:省 Token 不省能力
如果只是 Token 少了,任务却没完成,那这个优化没有意义。所以我还额外记录了两边的质量表现:
- Bug 修复通过率:商业标杆 Agent 20 个任务全部完成;我的 Agent 有 19 个完成,1 个修复不完整,最后人工检查时发现了遗漏的分支条件。
- 单元测试行覆盖率:两边在同样任务上的覆盖率差异在 3% 以内,属于正常波动。
坦白说,这个结果比我预想的好一点。毕竟商业标杆 Agent 的上下文管理更成熟,我的优化方案能在省 58.7% Token 的前提下把质量拉到这个水平,我觉得已经值得继续投入了。
3. 真正管用的四板斧:按需、压缩、延迟合并、瘦身
数字聊完了,我来拆一下自己的 Coding Agent 到底做了哪些跟商业标杆 Agent 不一样的设计。核心思路只有一个:把上下文当成带宽来管理,而不是当成图书馆来使用。
3.1 按需检索,而不是开局把仓库读一遍
商业标杆 Agent 的默认动作里,通常会先了解整个项目的结构,甚至把关键目录的完整文件路径和部分内容装进上下文。我的 Agent 完全反着来:开局只读取一小段项目说明和目录树,之后要用什么文件,再按需去检索。
我的实现方式是给 Agent 配了一个轻量代码索引层。它利用文件路径匹配和关键词搜索,在需要时把相关文件的内容加载进来。比如修 bug 时,Agent 会先根据报错信息搜索可能是出问题的模块,再只读取那一两个文件;而不是先把整个 src 目录都读一遍。
有一次对比让我印象很深:同样是修一个“登录接口返回超时”的 bug,商业标杆 Agent 在前几轮里读进上下文的文件有十几个,总共一万多行;我的 Agent 只读了一个路由文件和一个用户服务文件,加起来两千行左右。后续对话里,全程没有再碰过其他文件。
3.2 工具输出先压缩再进模型
这是我最得意的一个设计。Agent 在跑测试、查日志、读 CI 结果时,工具返回的原始内容往往又长又臭。我把这些输出全部截在模型之外,压缩之后才放进去。核心逻辑是这样一套规则:
def compress_tool_output(raw: str, head: int = 40, tail: int = 80) -> str: lines = raw.splitlines() if len(lines) <= head + tail: return raw middle = lines[head:-tail] summary = summarize_middle(middle) return "\n".join(lines[:head] + [summary] + lines[-tail:])head 保留开头,因为执行命令和错误前缀经常在这里;tail 保留结尾,因为堆栈回溯和最终结论一般在这里;中间部分交给一个小模型生成三五行摘要。对模型来说,它看到的报错关键信息一样完整,但 Token 成本少了一大截。
我用这个函数处理过一条 3200 行的测试日志,压缩之后变成 62 行,模型依旧能准确找出是哪个测试用例挂掉,以及具体失败在哪个断言。
3.3 延迟合并:文件状态不回流全量
另一个容易忽略的浪费点:Agent 在编辑代码文件时,常常需要“读取-修改-验证-再修改”循环。商业标杆 Agent 在这种循环里,每次修改后都会把完整文件内容或完整 diff 重新放回上下文。我的做法是让 Agent 在编辑时只生成 patch,不直接写文件。patch 先缓存在我的调度层里,Agent 只需要知道“当前改动涉及哪个文件、改动多大”。
等到 Agent 认为某个文件的修改已经完成,我才会把最终 diff 拿给模型做一次确认。这个“确认”也很有技巧:我只把新增和删除的行放进去,不变的内容不占 Token。这样一个文件哪怕改了十几次,模型对这个文件的感知也只是“改了几处、最终 diff 是什么”,而不是每一次中间态都进上下文。
3.4 系统提示词与子 Agent 的“上下文隔离”
商业标杆 Agent 的系统提示词通常很长,围绕安全规范、工具用法、代码风格写了一大堆。这些内容当然有价值,但问题在于,这些长提示词会出现在每一轮请求里,成为一笔持续性的固定开销。
我的简化方式是把系统提示词压缩到很短的一段目标描述,把所有细节规则拆成独立的规则文档。Agent 只有在具体用到某个工具时,才会去读取对应的规则文档。比如“如何写提交信息”这种规则,平时根本不进上下文,Agent 执行 Git 操作时才临时加载。
另外,我把任务拆分成多个子 Agent 的模式。父 Agent 负责规划,子 Agent 分别执行代码搜索、测试、文件修改。子 Agent 的上下文和父 Agent 互相隔离,不会出现“父 Agent 把子 Agent 见过的 500 行日志再读一遍”的情况。这里的隔离成本主要是调度复杂度,但 Token 收益非常明显。
4. 踩坑记录:三种看起来很聪明的方案,反而让 Token 更高
写这个 Agent 的过程中,我踩过的坑不比上面讲的成功经验少。有几个优化方向,乍一听很有道理,实际跑完数据反而更糟。把这些失败记录下来,可能比成功经验更有参考价值。
4.1 无脑截断工具输出的翻车现场
我最早做工具输出压缩时,思路非常简单:只保留最后 100 行。因为大部分日志的报错信息都在结尾,我以为这样损失不大。结果一个“测试通过但内存泄漏”的任务直接翻车了——那段日志的关键信息在中间部分,开头是测试启动配置,结尾是正常收尾,中间夹着一条 warning,模型根本没看到。
后来我意识到,错误信息不一定只在尾部,有时候它出现在头部,有时候散布在中间。于是才有了上面那段“头尾保留+中间摘要”的分层压缩方案。经过这个坑之后,我再也不在压缩策略里用单一规则,而是先判断输出类型,再选择保留策略。
4.2 每个返回值都做摘要,结果成本不降反升
压缩工具输出是我早期就想到的方案,但我犯过一个“凡事都摘要”的错误。我最初是只要工具的返回值超过 200 行,就一定调用一个小模型做摘要生成。问题在于,小模型这层调用本身也要花 Token,而且花得并不少。如果对话上下文里已经包含过类似内容,清晰的原文直接放进去反而更省。
结果我加了两个策略把它救回来:一是设置一个足够高的阈值,小于 500 行时直接透传原文;二是对同一来源的重复输出做去重,只有当输出内容相比上一次确实出现了新增信息,才允许压缩模块介入。
4.3 滑动窗口收太小,模型得了“失忆症”
为了省 Token,我一度把对话记忆窗口压得很小,只保留最近五轮。结果模型开始频繁“失忆”:前面已经确认过的技术方案,后面一轮又问一遍;修改文件时忘记之前约定的命名规范,重新问用户——这些重复问题的对话消耗,比保留上下文还贵。
做完一轮统计分析,我发现一个反常识的结论:上下文精简不是越小越好,而是越“关键信息密度高”越好。与其砍掉窗口长度,不如把不重要的原始日志清掉,但把高价值的结论(比如用户需求描述、已经确认的设计决策、待办清单)持续保留在一个固定位置。我的 Agent 现在会维护一份“项目记忆卡”,每次对话结束后更新。这份记忆卡只有几百行,但包含的信息量远超最近十轮的原始对话。
5. 58.7% 里的大头:缓存命中率才是沉默的省钱神器
如果你以为 58.7% 全是靠压缩省下来的,那就忽略了一个更隐蔽的因素:缓存命中率。同样一个 Token,如果被缓存命中,账单上的价格可能只有原来的十分之一甚至更低。这就意味着,哪怕 Token 消耗不变,你的实际 API 费用也可能大幅下降。我的 Agent 在这一点上做了非常多布局。
5.1 前缀缓存怎么“骗”过计费系统
主流大模型 API 的缓存机制大多基于前缀缓存:只要请求开头的内容和上一次请求一致,系统就能复用之前的计算结果,只对缓存未命中的部分算钱。
为了让缓存命中率最大化,我把请求结构固定成这样:静态部分(系统提示词、任务目标、仓库元信息)永远放在最前面,动态部分(工具输出、最新代码内容)放在最后面。这样每次请求的前缀是完全相同的,缓存能连续命中。商业标杆 Agent 由于每次都会在开头插入大量动态信息,缓存命中的窗口往往会被打破。
5.2 磁盘缓存和稳定的 prompt 布局
除了 API 前缀缓存,我还在本地做了一层磁盘缓存。工具返回的内容按文件路径和修改时间建立索引,同一个文件如果内容没变,第二次读取时直接从本地缓存拿。跨任务执行时效果尤其明显:第一天探索过一遍代码库,第二天的任务如果还涉及相同文件,就不用再重新让模型读一遍原始内容了。
这层缓存对 prompt 布局也提出了要求。我现在会固定维护一个稳定的 prompt 前缀,不管后面工具返回什么,前面部分永远不做调整。如果中途发现需要新增一条用户需求,我会把新需求放进“对话状态区”,而不是直接插在系统提示词中间,否则前缀一变,缓存全断。
5.3 把 58.7% 拆开看贡献
我自己做过一次逐项开关实验,大概摸清了这个 58.7% 里各部分的大致贡献:
| 优化措施 | 对节省比例的贡献(估算) |
|---|---|
| 按需加载文件,避免全量扫描 | 约 21 个百分点 |
| 工具输出压缩与去重 | 约 14 个百分点 |
| 系统提示词瘦身与子 Agent 隔离 | 约 8 个百分点 |
| 缓存命中与磁盘复用 | 约 15.7 个百分点 |
也就是说,真正让我拿到 58.7% 的,不单是某一个天才设计,而是四件事叠在一起的结果。这里也提醒一下:如果你抄作业只抄一步,效果一定有限。至少要把按需加载和缓存命中这两件事同时做了,才会看到明显变化。
6. 这些经验即使不自研 Agent 也值得抄走
最后这部分,写给不想自己从零写 Agent、但希望降低现有工作流 Token 开销的人。我的经验未必需要完整复刻成一个新项目,但有很多理念可以直接迁移到任何 Agent 使用方式里。
6.1 先给每次工具调用记账
很多人用 Agent 的时候完全不知道 Token 花在哪。我强烈建议,哪怕你只是日常使用,也给 Agent 加一层日志工具,统计每一次工具调用的输入输出 Token,并且按任务维度做聚合。我之所以能找到这么多优化点,很大程度上就是因为一开始就把账单拆得很细。
记账其实不难:如果用的是 CLI 工具,就包一层输出过滤脚本,解析每次请求的 usage 字段;如果是通过 API 直连,就把每次响应的 usage 数值打印出来。有了账本,再去看哪类操作最耗 Token,哪个环节最值得优化,你的优化顺序就会非常清楚。
6.2 给提示词设计做一次“静态前置”改造
哪怕不写代码,只是用现成的 Agent 工具,你也可以手动调整提示词:把所有不会变的内容固定放在提示词开头,动态内容往后放。这个习惯能让平台的缓存机制更有效地命中。大多数用户习惯把问题写在最前面,详细的项目背景放在最后面,这恰好跟缓存命中的最佳实践相反。
改成“固定背景在前、动态问题在后”的结构后,多次对话之间,共享的前缀会越来越多,API 实际计费也会越来越低。这是不需要改任何代码就能拿到的收益。
6.3 小团队的落地顺序建议
如果你是三人以内的开发小组,想控制 Agent 成本,我的建议是这样的优先级:
- 先做工具输出的压缩和去重,这是收益最高、见效最快的。
- 再设计稳定前缀,最大化缓存命中率,这一项能直接影响账单。
- 最后考虑按需加载和子 Agent 拆分,这两个偏架构,改动成本更大,但收益也最持久。
如果上来就直接改架构,很可能改了三周还没跑通,人先疲惫了。先把前两件事做掉,保存好前后对比数据,再逐步推进后面两步,会稳妥很多。
我在这个项目上最大的体会是:Token 优化不是一个“换便宜模型”这么简单的问题,而是一个系统性工程。它要求你对上下文里的每一部分内容都问一句“这个真的有必要出现吗”。当你开始养成了这种审视习惯之后,不仅 Agent 的账单会降下来,你对整个 AI 应用的成本结构也会产生更敏锐的感知。这个能力,可能比 58.7% 这个数字本身更值钱。