1. 账单失控的真相:Token 到底被谁吃掉了
很多人第一次用 DeepSeek Harness 跑工作流,看到后台账单的第一反应都是“是不是计费出错了”。我身边至少有三个朋友跟我吐槽过同一件事:明明只是让它读几个文件、改几行代码,怎么一轮下来 Token 消耗量比预期高出一个数量级。这个问题不是个例,而是绝大多数人从“聊天式对话”切换到“Agent 式工作流”时必然撞上的墙。
要搞清楚 Token 去哪了,得先理解 Harness 这类工作流插件和普通对话的根本区别。普通对话里,你发一句它回一句,上下文是线性增长的,一轮对话的 Token 消耗基本等于“你的输入 + 它的输出”。但 Harness 不一样,它是一个自主循环的 Agent 框架:它会自己决定读哪些文件、执行哪些命令、调用哪些工具、根据结果再决定下一步。每一次“决定”都要把当前的完整上下文重新喂给模型一次。也就是说,你看到的“一轮任务”,在模型侧可能是十几甚至几十次 API 调用,每次调用都带着一份不断膨胀的上下文。
这里有个很反直觉的点:Token 消耗的大头往往不是模型生成的代码,而是被反复重发的上下文。我实测过一个中等规模的仓库重构任务,最终生成的代码大概 3000 Token,但整个任务跑下来消耗了将近 40 万 Token。差距在哪?在于 Harness 每读一个文件、每执行一次命令,都会把“系统提示词 + 历史对话 + 工具返回结果 + 当前文件内容”这一整坨重新塞进请求里。文件读得越多,历史越长,单次请求的输入 Token 就越大,而且是平方级增长——因为每一轮都要带上前面所有轮次的累积。
所以当你觉得“Token 消耗太快”,本质上是在为三样东西付费:重复发送的历史上下文、被无差别读入的大文件、以及不必要的工具调用往返。理解了这一点,后面所有的优化开关才有意义——它们本质上都是在做同一件事:砍掉那些模型其实不需要看到的 Token。
提示:在动手调任何开关之前,先养成一个习惯——去后台看单次任务的 Token 明细,区分“输入 Token”和“输出 Token”。如果输入远大于输出(通常是 10:1 甚至更高),那问题一定出在上下文管理上,而不是模型话太多。
2. 官方开关一:上下文窗口裁剪,别让历史无限膨胀
2.1 这个开关解决的是什么问题
Harness 默认的行为是“尽可能保留完整历史”,这在短任务里没问题,但一旦任务超过十几轮,历史上下文就会变成一头吞金兽。上下文窗口裁剪(Context Trimming)这个开关的核心逻辑是:当累积上下文接近模型窗口上限时,自动丢弃最早的、已经不再影响当前决策的轮次。
为什么可以丢?因为 Agent 工作流里,早期的很多轮次是“探索性”的——比如它一开始读了 A 文件发现不相关,又去读 B 文件。等到任务进行到后半段,A 文件的内容对当前决策已经毫无价值,但默认配置下它依然躺在上下文里,每一轮都被重新发送。裁剪就是把这些“死重”扔掉。
2.2 怎么配、配多少才合理
这个开关通常不是一个简单的开/关,而是带一个阈值参数,比如“保留最近 N 轮”或“上下文占用超过 X% 时触发裁剪”。我的经验值是:
| 任务类型 | 建议保留轮次 | 触发阈值 | 说明 |
|---|---|---|---|
| 单文件小修改 | 8-10 轮 | 70% | 任务短,几乎不触发 |
| 多文件重构 | 12-15 轮 | 60% | 平衡连贯性与成本 |
| 大型仓库探索 | 6-8 轮 | 50% | 探索类任务历史价值低,激进裁剪 |
这里有个坑要提醒:裁剪阈值设得太激进,会导致 Agent“失忆”。我试过把阈值压到 40%,结果它改到一半忘了自己前面已经改过哪个文件,又回头重改一遍,反而更费 Token。所以裁剪不是越狠越好,而是要找到“刚好够它记住当前任务状态”的平衡点。判断标准很简单:如果 Agent 开始重复已经做过的工作,说明裁过头了。
2.3 一个容易被忽略的细节
裁剪策略通常有两种:按轮次裁和按 Token 数裁。按轮次裁简单粗暴,但问题是不同轮次的 Token 量差异巨大——读一个大文件的轮次可能顶得上十个闲聊轮次。所以我更推荐按 Token 数裁,设定一个“保留最近 X 千 Token 历史”的硬上限。这样无论每轮内容多少,上下文总量都是可控的。
3. 官方开关二:文件读取白名单,从源头掐断无效输入
3.1 为什么“读文件”是最大的隐形开销
前面说了上下文膨胀是主因,那上下文是怎么膨胀起来的?八成是因为文件读取。Harness 在探索阶段会主动读文件来理解项目结构,但它的默认策略往往是“广撒网”——把目录下能读的文件都读一遍。一个稍微像样的项目,node_modules、dist、.git 这些目录加起来轻松几十万 Token,而其中 99% 的内容对任务毫无帮助。
我见过最夸张的案例:有人让 Harness 改一个 React 组件的样式,结果它把整个 node_modules 扫了一遍,单次任务烧掉 80 万 Token。这不是模型笨,是配置没告诉它“哪些地方不用看”。
3.2 白名单和黑名单,该用哪个
文件读取控制一般提供两种模式:白名单(只读指定路径)和黑名单(排除指定路径)。我的建议是优先用黑名单做兜底,再用白名单做精确控制。
黑名单是必须的,至少要排除这几类:
node_modules/、vendor/、venv/等依赖目录dist/、build/、out/等构建产物.git/、.svn/等版本控制元数据*.min.js、*.map、*.lock等压缩或锁文件- 图片、字体、二进制文件
白名单则用于进一步收窄。比如你明确知道这次任务只涉及src/components/下的文件,那就把读取范围锁死在这个目录。这样 Agent 连“探索”的机会都没有,直接从源头省掉大量无效读取。
3.3 配置示例与实测效果
以常见的配置文件为例,大致长这样:
file_access: mode: whitelist include: - "src/**/*.ts" - "src/**/*.tsx" - "package.json" exclude: - "**/node_modules/**" - "**/dist/**" - "**/*.min.js" - "**/*.lock" max_file_size: 100KB注意最后那个max_file_size,这是个非常实用的保护。有些项目里藏着巨大的日志文件或数据文件,一旦被读进去就是灾难。设一个 100KB 的上限,超过的文件直接跳过,能挡掉很多意外开销。
实测下来,光是把 node_modules 和 dist 排除掉,同一个任务的 Token 消耗就能降 60% 以上。如果再加上白名单精确锁定,降 80% 都不夸张。
注意:白名单设得太窄也有风险。如果 Agent 需要读的文件不在白名单里,它可能会反复尝试、报错、重试,反而浪费 Token。所以白名单要基于你对任务的判断来设,宁可稍微宽一点,也别让它“够不着”必要的文件。
4. 官方开关三:工具调用结果截断,别让命令输出撑爆上下文
4.1 命令输出为什么是个无底洞
Harness 执行命令(比如跑测试、查 git log、列目录)后,会把命令的输出结果塞进上下文。问题在于,很多命令的输出是又长又没用的。比如你跑一次npm install,输出几百行依赖安装日志;跑一次测试,输出上千行通过用例。这些内容对 Agent 的下一步决策几乎没有任何价值,但它们实实在在地占着 Token。
更麻烦的是,这些输出会被反复重发。因为一旦进入上下文,后续每一轮请求都会带上它。一个 5000 Token 的命令输出,如果在后续 20 轮里都被重发,那就是 10 万 Token 的纯浪费。
4.2 截断策略怎么设
工具调用结果截断(Tool Output Truncation)这个开关,就是给命令输出设一个长度上限,超过部分直接砍掉,只保留头尾关键信息。配置上通常有几个维度:
- 按行数截断:比如最多保留 50 行,超出部分用
... (省略 N 行)代替 - 按字符/Token 数截断:比如最多 2000 Token
- 保留头尾:头部保留命令开始的关键信息,尾部保留结果状态(成功/失败、错误信息)
我个人的配置习惯是:普通命令保留头 30 行 + 尾 20 行,测试类命令只保留失败用例和汇总行。因为对于 Agent 来说,它真正需要知道的只是“命令成功还是失败”“失败在哪”,中间那些成功用例的细节毫无意义。
4.3 一个实战中的取舍
这里有个需要权衡的地方:截断太狠可能丢失关键错误信息。我有一次把截断设得太激进,结果测试失败的具体报错被砍掉了,Agent 只看到“测试失败”却不知道原因,于是反复重跑测试,Token 反而烧得更多。
所以截断策略要优先保留错误和异常信息。好的截断逻辑应该是:如果命令输出里包含 error、fail、exception 等关键词,这些行必须保留;如果全是成功信息,那就大胆砍。有些 Harness 版本支持基于正则的保留规则,一定要用起来。
5. 官方开关四:系统提示词精简,别让“人设”吃掉预算
5.1 系统提示词的隐藏成本
系统提示词(System Prompt)是每一轮请求都必须携带的固定内容,它定义了 Agent 的角色、行为规范、工具使用说明等。很多人不知道的是,一个臃肿的系统提示词,会在每一轮请求里被重复计费。
假设你的系统提示词有 3000 Token,任务跑了 30 轮,那就是 9 万 Token 的纯系统提示词开销。如果这个提示词里塞了大量“你是一个专业的、严谨的、富有创造力的、注重细节的……”这类对实际行为影响甚微的修饰,那这些 Token 就是白烧的。
5.2 精简的原则
精简系统提示词的核心原则是:只保留影响行为决策的内容,删掉所有“气氛组”。具体来说:
- 角色描述能短则短,“你是一个代码助手”就够了,不需要三段话渲染
- 工具说明如果框架已经内置,就别在提示词里重复
- 行为规范只保留硬性约束(比如“不要修改测试文件”),删掉软性建议
- 示例(few-shot)能删就删,或者压缩到最小
我做过一个对比测试:把一个 2800 Token 的系统提示词精简到 900 Token,任务完成质量几乎没有变化,但整体 Token 消耗降了约 15%。对于高频使用的场景,这个降幅相当可观。
5.3 别踩的坑
精简不等于乱删。有几类内容是不能动的:安全约束、输出格式要求、关键工具的使用规则。这些一旦删掉,Agent 可能会做出危险操作(比如误删文件)或者输出无法解析的格式,导致任务失败重来,反而更费钱。精简的对象是“锦上添花”的描述,不是“保命”的约束。
6. 官方开关五:任务步数上限,给失控的 Agent 踩刹车
6.1 为什么需要步数上限
Agent 工作流最可怕的地方在于它可能陷入死循环。比如它改了一个文件,跑测试失败,又改回去,再跑还是失败,再改……如果没有步数限制,它能这样耗到你账户见底。我遇到过最离谱的一次,一个简单的类型错误,Agent 来回改了 40 多轮都没搞定,Token 消耗直接飙到六位数。
任务步数上限(Max Steps / Max Iterations)就是给这种情况踩刹车:设定一个最大轮次,超过就强制停止。这不是为了省钱而牺牲质量,而是为了防止小概率的失控演变成灾难性账单。
6.2 上限设多少合适
这个没有标准答案,取决于任务复杂度。我的经验参考:
| 任务复杂度 | 建议步数上限 | 说明 |
|---|---|---|
| 简单问答/单点修改 | 10-15 | 超过基本就是出问题了 |
| 常规功能开发 | 25-40 | 留足探索和调试空间 |
| 复杂重构/多模块 | 50-80 | 但要做好中途干预准备 |
关键是要配合监控和中断机制。步数上限是最后一道防线,但更好的做法是在任务跑到一半时看一眼进度,如果发现它在原地打转,直接手动停掉,别等它撞上限。
6.3 配合重试策略一起用
单纯设步数上限还不够,最好再配一个失败重试上限。比如“同一个文件连续修改 3 次仍未通过测试,就停止并报告”。这样能更早地识别出“卡住了”的状态,而不是傻等到总步数耗尽。这两个开关配合使用,能把失控风险压到最低。
7. 五个开关的组合拳:一套可复制的配置模板
7.1 为什么单开一个没用
这五个开关不是孤立的,它们作用在不同的环节:文件白名单管“输入源头”,工具截断管“中间产物”,上下文裁剪管“历史累积”,系统提示词管“固定开销”,步数上限管“失控兜底”。只开一个,效果有限;组合起来,才能形成完整的成本控制闭环。
我实测过:单独开文件白名单,Token 降 60%;单独开上下文裁剪,降 30%;但五个全开并调好参数,同一个任务从 40 万 Token 降到 6 万左右,降幅 85%。这就是组合拳的威力。
7.2 一套可以直接抄的配置
下面是我目前常用的一套配置,适用于大多数中小型代码任务:
context: trimming: enabled: true strategy: token_based max_history_tokens: 30000 trigger_threshold: 0.6 file_access: mode: whitelist include: - "src/**" - "*.json" - "*.md" exclude: - "**/node_modules/**" - "**/dist/**" - "**/.git/**" - "**/*.lock" max_file_size: 100KB tool_output: truncation: enabled: true max_lines: 50 keep_head: 30 keep_tail: 20 preserve_on_error: true system_prompt: mode: minimal max_tokens: 1000 execution: max_steps: 40 max_retries_per_file: 3这套配置的核心思路是:能砍的砍,能锁的锁,能兜的兜。你可以根据自己的项目特点微调参数,但整体框架可以直接用。
7.3 调参的顺序建议
如果你刚开始优化,别一次性全改,容易出问题也不知道是哪个参数导致的。建议按这个顺序来:
- 先开文件白名单,这是收益最大、风险最低的
- 再开工具输出截断,注意保留错误信息
- 然后调上下文裁剪,从保守阈值开始慢慢收紧
- 接着精简系统提示词
- 最后设步数上限做兜底
每改一项,跑一个标准任务对比 Token 消耗,确认有效再改下一项。这样既能定位问题,也能积累出适合自己项目的参数经验。
8. 几个我踩过的坑和反常识经验
8.1 缓存不一定省钱
有些 Harness 版本支持上下文缓存(把重复的上下文缓存起来,下次请求复用)。听起来很美,但实测下来,如果上下文变化频繁,缓存命中率会很低,反而增加了缓存管理的开销。我的建议是:只有当你的系统提示词特别长、且任务轮次特别多时,缓存才划算。否则别折腾,老老实实裁剪更实在。
8.2 小模型干粗活,大模型干细活
这是个容易被忽略的策略:不是所有步骤都需要用最强的模型。文件探索、目录扫描这类“粗活”,完全可以用便宜的小模型来做;只有真正需要推理和写代码的步骤,才切换到强模型。有些 Harness 支持按步骤配置模型,用好了能再省一大笔。我试过把探索阶段换成小模型,整体成本又降了 20% 左右,质量几乎没影响。
8.3 别迷信“全自动”
很多人追求“一句话丢进去,全自动跑完”。但实测下来,全自动任务的 Token 消耗往往远高于半自动。因为全自动意味着 Agent 要自己探索、自己试错,而半自动(你先告诉它改哪个文件、大概怎么改)能省掉大量探索开销。如果你的目标是控制成本,那“人给方向、Agent 执行”的模式,比“Agent 全包”划算得多。
8.4 定期看账单明细
最后一条,也是最实在的一条:养成定期看 Token 明细的习惯。不要等到月底账单出来才傻眼。每次跑完一个稍大的任务,去后台看看输入/输出比例、哪些步骤消耗最多。看多了你自然就有感觉,知道什么样的任务大概该花多少 Token,一旦某次异常偏高,立刻就能定位到是哪个开关没生效或者哪个文件被误读了。
这套东西说到底就一个核心逻辑:Agent 工作流的成本,90% 花在“模型不需要看却被迫看了”的内容上。五个开关做的都是同一件事——把那些内容挡在上下文之外。理解了这个本质,你甚至不需要死记参数,自己就能根据项目情况判断该怎么配。