用 DeepSeek Harness 做开发辅助,第一个月账单出来的时候,我差点以为 API Key 被人盗刷了。聊了几个小时的天,改了几十个文件,Token 用量却比我预估的高了好几倍。后来我把配置一条一条翻出来看,才发现问题不在模型,而在 Harness 默认的那套“高能耗”设置。这篇文章不聊宏观概念,只讲怎么用 5 个官方开关把 Token 消耗压下来,把账单控制在看得懂的范围内。适合正在用 DeepSeek Harness 做编码、写脚本、跑 Agent 工作流的开发者,也适合准备把 Harness 部署到团队环境、但又担心 API 成本失控的运维同学。
1. 先搞清楚:你的 Token 到底烧在哪了
1.1 Token 不是字数,是颗粒度
许多刚开始用 DeepSeek Harness 的朋友都有同一个错觉:Token 就是汉字数量。我输入的是一段自然语言,模型返回的是中文,那 1000 Token 差不多就对应 1000 个字。实际完全不是这样。Token 是模型内部对文本做的切分结果,英文一个单词可能被切成 1 到 3 个 Token,中文一个汉字大约对应 1 到 2 个 Token,代码、符号、空格、缩进、换行全都要消耗。更麻烦的是,不同模型的切分规则不完全一样,同样一段文字在 DeepSeek 的编码表里和在别的模型里,Token 数可能有明显差异。
我举一个实际例子:一段 200 字的 Python 代码,里面混杂了缩进、括号、中文注释,可能轻松超过 400 Token。你以为你让模型改的是 200 字的文件,实际上背后的计费单位是这个数的两倍还多。理解了这一点,你再看账单就不会太惊讶,也会明白为什么省 Token 的第一步不是少说话,而是搞清楚一次请求到底发出去多少东西。下面这张表可以帮你建立基本感觉:
| 文本片段 | 估算 Token | 说明 |
|---|---|---|
| “hello world” | 约 2 | 英文单词通常 1 个 Token |
| “你好” | 约 2 | 中文单字通常 1 到 2 个 Token |
| 含缩进的 50 行 Python 代码 | 约 400 以上 | 缩进、符号、注释都计入 |
1.2 Harness 的“输入”远比你想的多
为什么同样的对话,在网页版和 Harness 里的 Token 消耗不一样?因为 Harness 不只是聊天窗口,它是一套把 DeepSeek 接进本地命令行、IDE、Agent 工作流的“驱动器”。你让它读文件,它会把文件内容塞进上下文;你让它调用 Skill,它会把 Skill 定义和返回结果塞进上下文;你让它访问工具,工具的返回 JSON 也会被完整塞进去。
云端模型看到的内容是一个巨大的请求包,包里面至少有四部分:
- 系统提示词:固定的一部分,通常几千 Token;
- 工具定义:Harness 注册的所有工具描述,可能几千 Token;
- 会话历史:前面所有轮次的对话,以及每次工具调用的输入输出;
- 当前指令:你真正想让模型做的事情。
这四部分加起来,才是每次请求的真实输入量。所以你每一次点击发送,成本都不只是你最后打的那句话。默认配置下,这个输入量很容易突破几万 Token。你在网页版对话时,输入就是你刚打的问题;在 Harness 里,输入是整包物品。这个区别,决定了 Harness 的费用天然会比普通聊天高。
1.3 消耗占比的观察
我调参前统计过我自己的账单,某次会话总消耗 72K Token,其中历史上下文重复发送占了约 61%,系统提示词和工具定义占了约 17%,模型输出占了约 15%,工具调用返回结果占了约 7%。也就是说,真正的“有效输出”只占很小一块,大头都在上下文重复发送上。
这让我明确了操作方向:不是让模型少回答,而是让 Harness 少把旧内容一遍遍发上去。为了直观,我整理了一张实际记录下来的两种会话形态对比表:
| 会话形态 | 输入 Token(约) | 输出 Token(约) | 总消耗(约) |
|---|---|---|---|
| 长会话 30 轮,窗口 32K,未清理 | 58K | 8K | 66K |
| 短会话 6 轮,窗口 16K,及时清理 | 14K | 5K | 19K |
同样完成一个功能的开发,后者只花了前者的三分之一。差距主要来自历史上下文的重复发送。所以下面的几个开关,大多都在围绕“怎么少发旧内容”做文章。
2. 官方开关一、二、三:把上下文和输出勒紧
2.1 开关一:限制上下文窗口(context_window)
Harness 的默认上下文窗口通常会顶到模型支持的上限,因为工具希望你能体验“超长上下文”的便利。但对于大多数编码任务,超长上下文其实是一种浪费。窗口越大,Harness 能在上下文里塞的内容越多,每次请求携带的输入也就越多。你并不会因为窗口大而得到更聪明的回答,只会得到更贵的账单。
我建议普通编码场景把上下文窗口设在 16K,命令行问答场景甚至可以设到 8K。配置项名称一般是 context_window 或 --context-size。以 JSON 配置为例:
{ "context_window": 16384 }需要说明的是,限制窗口并不是“只保留最后 16K 的内容”,更准确的说法是:Harness 会在请求发出前,把将要发送的上下文控制在窗口内。超出部分可能被丢弃或被截断。设置窗口的真正价值在于,它给整个会话设下了一个明确的成本天花板,防止会话因为被喂入大量文件内容而悄悄膨胀到几十 K。尤其当你用 Harness 做代码仓库级别的 Agent 任务时,Harness 可能会扫描目录、读取多个文件,如果窗口没有限制,一次扫描可能就把几百 K 文本全部塞进请求。
这里的关键操作习惯是及时清理会话。我见过很多人一整天不关 Harness 窗口,让会话累积到几百轮,每次回复前光是把历史重新发送一遍就要烧掉几万 Token。我的做法是:当一个会话解决完一个完整任务,就执行 /clear 或 /compact。/compact 尤其好用,它让模型把之前的讨论压成一段摘要,用摘要替代完整的原始历史,后续请求的输入量能下降一个量级。但注意,摘要本身也占 Token,所以不要每两句话就压缩一次,通常一个长任务压缩一到两次就够了。
2.2 开关二:锁死单次回复的最大 Token(max_tokens)
第二个开关是 max_tokens,限制模型单次输出的上限。这可能是最直观、也最容易被忽略的开关。默认值很大,导致模型有足够空间“说废话”。尤其当你在 Harness 里没有明确要求简洁时,DeepSeek 很容易生成额外解释、补充说明、示例代码和总结段落。
我的建议是日常任务配置 1024,代码生成类任务可以临时提高到 2048。例如:
{ "max_tokens": 1024 }可能有朋友担心,max_tokens 设低了,模型会不会回答到一半被截断?确实存在这个风险。如果模型觉得回答还没写完但已经到达输出上限,它会直接停止,不会通知你,导致回复看起来“半截”。为了减少这个问题,我通常配合 temperature 一起调整。temperature 设到 0.2 左右,模型会更倾向于直接输出结论,而不是长篇大论地铺陈。同时,在指令中明确“只给关键代码,不要解释”,也能让输出更紧凑。真遇到长任务,比如生成一个完整的服务类文件,我会临时把 max_tokens 调到 4096,任务结束再调回来。
不同任务类型建议使用的 max_tokens 可以参考下面这张表:
| 任务类型 | 建议 max_tokens |
|---|---|
| 日常问答、翻译、改写 | 512 |
| 代码修改、报错解释 | 1024 |
| 生成完整代码文件 | 2048 到 4096 |
| 长篇文档撰写 | 4096 |
还有一个认知要纠正:max_tokens 并不是总预算。它只限制本次回复的输出 Token,不影响输入 Token。很多用户以为把 max_tokens 调低就能大幅控制成本,结果发现账单还是很高,原因就是他们的输入侧还堆着大量历史。所以,max_tokens 要和窗口限制、历史轮次限制一起用,单靠哪一个都压不住。
2.3 开关三:瘦身历史对话轮次(history_limit)
第三个开关控制 Harness 在每次请求中携带多少轮历史消息。默认值一般很慷慨,20 轮起步。但看过前面消耗分析你应该明白,20 轮历史如果每一轮都包含工具调用的长文本,累积起来非常吓人。
我推荐把 history_limit 设为 5 到 8 轮。以 6 轮为例:
{ "history_limit": 6 }为什么是 6 而不是 1?因为现代编码工作流通常是一个小闭环:读取文件、分析问题、提出方案、修改代码、运行测试、看报错。这六步各对应一轮或两轮对话。保留 6 轮,模型能理解你刚才在做什么,又不至于背上一个月前讨论的包袱。
在这个开关下,有两个配套选项值得打开:始终保留系统提示词,始终保留当前工具结果。它们确保历史轮次被压缩时,模型不会丢掉最关键的工具上下文。比如刚才读取的一个文件内容,如果被当作普通历史消息清理了,你接下来的指令就会失去参照。这个细节很多人没注意,一压缩历史后模型突然“失忆”,其实不是模型问题,是配置问题。
不同保留轮数的效果差异也很明显:
| 保留轮数 | 效果 |
|---|---|
| 1 到 2 轮 | 最省 Token,但模型容易忘记刚才做了什么 |
| 5 到 8 轮 | 适合编码闭环,推荐日常默认值 |
| 20 轮以上 | 上下文完整,但费用近似线性上涨 |
实操里我的经验是,如果任务真的需要长期记忆,不要依赖多轮历史。把关键信息写入项目目录下的 notes.md,然后让 Harness 读取这个文件。这比让它从 20 轮历史里寻找信息更省 Token,也更可靠。用文件代替聊天记忆,是我在长期使用中觉得最实用的一招。
3. 官方开关四、五:让 Harness 少做无效动作
3.1 开关四:关掉自动补全和自动执行,改成手动确认
Harness 为了让你用起来更顺滑,默认会开启一堆自动行为。你以为它只是在你输入时悄悄补全几行,实际上每一次补全都是一次完整的模型请求。想象一下:你打开一个 1000 行的代码文件,Harness 每当你敲一个字符就尝试补全一次,每次补全都要把整个文件内容作为输入发出去,那一个小时的编码时间,Token 消耗会比正常问答高出好几个数量级。这个数字一点都不夸张,我见过有人在 IDE 里连着用 Harness 写了一个下午,服务端后台的请求记录里全是密密麻麻的补全请求。
我强烈建议把 auto_complete 和 auto_execute 设为 false。不同版本叫法不一样,但思路是一致的:
{ "auto_complete": false, "auto_execute": false }关闭之后,Harness 不会在你写完一行代码时立刻弹出补全建议,也不会在看到日志里出现关键字时自动调用工具。所有动作都需要你按快捷键或输入命令触发。刚开始可能觉得少了点“智能感”,但当你查看服务端请求记录时,会发现请求数量明显减少。
我做过一个实测:同样是调试一个接口异常的任务,自动模式触发了 27 次模型调用,手动模式只触发 9 次。自动模式里有大量调用是在尝试猜测用户意图:它试图自动搜索相关文档、自动读取代码、自动生成修复方案,但其中一半是无效猜测。关闭自动行为,本质上是把猜测权从模型手里收回来,交给你自己。你比模型更清楚你想干什么,省下来的不只是 Token,还有时间。
更有意思的是,有些 Harness 版本在没有人工确认的情况下,会自动调用执行类工具。这种自动执行不仅费 Token,还有安全风险。代码改动一旦自动执行,可能会在项目里留下你没注意到的副作用。手动确认模式同时解决了成本和风险两个问题,属于那种关了才会觉得真香的开关。
3.2 开关五:打开提示词缓存(Prompt Caching)
第五个开关是提示词缓存。DeepSeek API 支持基于前缀的缓存机制。简单来说,服务端会把常见前缀的计算结果暂存下来,当你下一次请求携带相同前缀时,命中的那部分输入可以按更低的缓存价计费。因为 Harness 每次请求都会包含系统提示词、Skill 定义、开头若干轮历史,这些内容恰好构成了一个高度稳定的前缀,非常适合吃缓存红利。
打开方式很简单:
{ "prompt_cache": true }但很多人打开后发现效果不明显,问题通常出在“前缀不稳定”上。缓存命中的前提是前缀完全一致,哪怕你在系统提示词尾部多了一个空格,缓存都可能失效。所以,使用缓存有几个铁律:
- 系统提示词和 Skill 定义设置好后不要频繁改动;
- 用户消息尽量放在固定位置,不要穿插到系统提示词中间;
- 不要在会话途中频繁 /clear,因为每开一个新会话,前缀就要从零开始重新积累。
这里还有一个很容易踩的坑:为了解决历史过长的问题,有些用户喜欢在每次请求前手动修改历史消息,比如删掉中间几轮,或者重新排列顺序。这会让前缀出现断层,缓存命中率断崖式下降。正确的做法是:要么保持完整历史的固定顺序,让它积累成一个稳定前缀;要么干脆开启新会话,让前缀从简短的固定内容开始。
缓存的实际收益在长会话里最明显。你开着一个会话连续工作一小时,系统提示词和前面历史会被发送很多次,如果每次都命中缓存,输入费用会大幅下降。我自己的账单在打开缓存后,长会话场景的输入费用大约下降三到五成。注意,缓存并不是所有请求都能命中,当你发出一条全新的用户指令时,新的用户指令本身不会被缓存,但前面的固定部分可以。这部分固定部分通常占输入量的 70% 以上,所以收益并不小。
3.3 补充两个和开关等效的官方能力
说完 5 个开关,我还想再强调两个不属于“开关”但同样能压账单的官方能力。
第一个是模型选择。DeepSeek Harness 通常允许你在 deepseek-chat 和 deepseek-reasoner 之间切换。reasoner 模型在推理时会产生内部思考过程,这些思考过程同样计入 Token。如果你只是做代码格式化、翻译、写注释,用 reasoner 简直是拿大炮打蚊子。我的配置里默认模型就是 deepseek-chat,只有遇到复杂的架构设计、算法推导、疑难 bug 定位时才手动切换。你可以把这条写进团队规范里,任何人跑 Harness 前先确认模型类型,能省出一笔很可观的费用。
第二个是批量处理任务。比如你手上有 10 个 markdown 文件需要统一改格式,不要开 10 个会话、发 10 次请求。你把 10 个文件名一次性丢给 Harness,让它在一个会话里逐个处理。这样系统提示词和工具定义只需要发送一次,后续处理都能复用同一份上下文。批量处理省掉的是重复的固定开销,10 个独立请求的固定开销是 10 份,1 个批量请求只需要 1 份。
4. Token 与登录态的经典问题排查
前几节讲的是计费 Token 的控制,这一节说一说和 Token 相关的另一类问题:登录 Token 失效、刷新失败。这类问题不算配置问题,但如果你遇到,会直接影响 Harness 能否正常使用,消耗的虽然不是 API 费用,但却是实实在在的时间成本。
4.1 “sign-in could not be completed”和“token exchange failed”
Harness 登录时,本质上是用一个临时授权码向认证服务换取访问 Token。这个过程在日志里体现为 token exchange。如果这一步失败,你会看到 “sign-in could not be completed” 或 “token exchange failed”。
我的排查步骤是:
- 先看完整报错,确认是不是网络层面的瞬时错误。如果是网络抖动导致的,稍等几十秒重试可能就好了。
- 检查账号是否在其它设备上重新登录过。如果是,本地旧的登录会话可能已经被服务端判为失效,这时直接重新登录。
- 清理本地凭证文件。DeepSeek Harness 的凭证一般存在 ~/.deepseek-harness 目录下的 auth 文件里。删除前可以备份,然后重新执行登录命令。
这套流程能解决九成以上的登录失败问题。不要为了保留登录状态去手动改凭证文件,几乎只会把事情搞得更糟。
4.2 “invalid refresh_token”和 access token could not be refreshed
访问 Token 是短期的,过期后 Harness 会尝试用 refresh_token 换新的。当你看到类似 “failed to refresh token: 400 bad request: invalid 'refresh_token'” 的报错,十有八九是本地保存的刷新凭证已经不合法了。
常见诱因有两个。第一,多端登录互相顶替。你在电脑上登录了 Harness,之后又在手机上用同一个账号登录,老设备的刷新凭证会被撤销,下次续签就会失败。第二,本地凭证文件被同步工具弄坏了。比如你把它放在云盘同步目录里,同步时出现冲突,文件内容变成空字符串,那 refresh_token 自然就是 invalid 的。
处理方式也很直接:删除本地凭证文件,重新登录。不要试图通过修改 JWT 内容来续期,JWT 的签名是服务端验证的,本地改任何一个字符都会报错。我见过有人反复尝试在 JSON 里补全 refresh_token 字段,结果每次都是同样的报错,因为服务端根本不认本地拼出来的值。
4.3 JWT 与 Token 续签的几个认知
我经常看到有人把“登录 Token 失效”归结为工具 Bug,这里想替工具说句公道话。JWT 的设计初衷就是无状态、自包含。服务端签发的 Token 里写好了过期时间,到期之前,服务端在没有额外黑名单机制的情况下,无法强制让某个 Token 失效。Harness 在实际登录中采用的通常是 OAuth 流程:短期访问 Token 加长期刷新 Token。访问 Token 丢掉没关系,刷新 Token 还在就能续;如果刷新 Token 也丢了,就只能重新登录。
如果你是自己写脚本调用 Harness 的 API,续签逻辑要注意三点:Token 不要硬编码在脚本或仓库里,尽量用环境变量或系统钥匙串;续签前检查 Token 的过期时间,快过期了才发请求;续签失败不要死循环重试,连续失败基本说明凭证已经废弃,该走人工登录流程了。这套经验放在任何 OAuth 客户端上都适用。
4.4 常见报错速查表
| 报错关键词 | 可能原因 | 处理建议 |
|---|---|---|
| sign-in could not be completed | 登录流程中断、授权码无效 | 清凭证后重新登录 |
| token exchange failed | 换 Token 时网络或服务端异常 | 检查账号授权状态,重试登录 |
| token endpoint returned 403 | 服务端拒绝 Token 交换 | 检查账号权限和授权范围,重新登录 |
| invalid refresh_token: empty string | 本地刷新凭证损坏 | 删除凭证文件,重新登录 |
| access token could not be refreshed | 刷新 Token 过期或账号已退出 | 重新执行登录流程 |
| 403 forbidden | 凭据权限不足 | 检查账号是否有对应服务权限 |
实际排查时建议按顺序:先看完整报错,再检查凭证文件,最后重新登录。不要在同一会话里反复点击登录,那样只会产生更多的失败请求。
5. 直接可抄的配置样板与实测效果
5.1 一份适合普通编码场景的 Harness 配置
把前 5 个开关落成一份配置,我的日常配置长这样。文件位置在不同版本略有差异,但大多是 ~/.deepseek-harness/config.json 或项目根目录的 .harness.yaml。
JSON 版:
{ "model": "deepseek-chat", "context_window": 16384, "max_tokens": 1024, "history_limit": 6, "auto_complete": false, "auto_execute": false, "prompt_cache": true, "temperature": 0.2 }YAML 版:
model: deepseek-chat context_window: 16384 max_tokens: 1024 history_limit: 6 auto_complete: false auto_execute: false prompt_cache: true temperature: 0.2这份配置的核心思想可以归结为一句话:默认用便宜模型、短窗口、短输出、少历史、无自动动作、开缓存。你不需要完全照抄,但可以先复制这份,然后根据自己手头任务的类型微调。如果你是在团队内网服务器上统一部署 Harness,建议由管理员在共享配置里预设这些值,并禁止普通用户覆盖关键的成本项,否则团队里只要有一个人开着自动补全写一天代码,账单就会很难看。
5.2 实测下来能省多少
我在一个中型前端项目上做过调整前后的对比。调整前使用默认配置,完成“新增一个列表页、包含接口联调和空态处理”这个任务,累计消耗大约 68K Token。调整后使用上面的配置,同样任务消耗大约 21K Token。下降幅度接近 70%。其中,关闭自动补全贡献最大,其次是收紧历史轮次和限制上下文窗口。缓存的效果更多体现在连续工作一小时的场景里,单独看单次任务不明显,但叠加下来很可观。
| 配置状态 | 会话总消耗(约) | 模型调用次数(约) | 完成情况 |
|---|---|---|---|
| 默认配置 | 68K | 27 | 完成 |
| 上述配置 | 21K | 9 | 完成,质量无明显损失 |
第三列模型调用次数也很说明问题:默认配置下 Harness 做了大量试探性工作,调了 27 次模型;手动确认模式下只调了 9 次。模型调用次数直接和账单正相关,所以你也可以通过观察统计接口里的请求次数,来判断配置调整的效果。如果你的 Harness 版本自带 usage 或 metrics 面板,优先看它;如果没有,可以临时在代理层记录一下请求体大小,也能估算出个大概。
5.3 三个我踩过的坑
最后聊几个我在调整配置时踩过的坑,给后来者提个醒。
第一个坑是“频繁清理会话,缓存反而失效”。我一开始以为 /clear 是省钱大招,所以每几轮就清一次。结果缓存命中率掉到几乎为零,因为每次新会话都要从头积累前缀。后来我改成:同一个任务尽量保持一个会话,任务结束才清场。清场前如果担心丢失上下文,用 /compact 先压缩一份摘要,而不是直接 /clear。
第二个坑是“多会话并行,账单翻倍”。有一段时间我同时开着三个 Harness 窗口,一个写前端、一个改接口、一个写运维脚本。表面上是三线并发,其实每个窗口都在独立发送系统提示词、工具定义和历史上下文。后来我强制自己单会话串行,或者把同类任务合并到同一个会话,总消耗立刻降下来了。
第三个坑是“上下文窗口调太小,触发反复重试”。有一次我为了省钱把窗口设成 4096,结果 Harness 读取一个比较大的配置文件时内容被截断,工具请求失败后自动重试,重试时又因为窗口太小继续失败,来回折腾了七八轮,费用反而比不设窗口还高。解决方法是:不要在让 Harness 扫描大文件时把窗口压得过低;如果文件太大,先用正则或 grep 把关键片段提取出来,再丢给模型。用小的输入片段配合合理的窗口,才是真正的省。
调 DeepSeek Harness 的参数,本质上是在跟“自己”做对抗。工具默认配置追求的是省事、智能、反应快,而这些目标天然都会增加 Token 消耗。我调了这 5 个开关之后最大的感受是:模型质量并没有下降,回答反而更干脆了,因为我限制了它的发挥空间,它就只能挑重点讲。省 Token 这件事,功夫不在省钱本身,而在帮模型减少无效动作。我用过的最小成本方案,就是上面这份配置加一条习惯:每次开工先想清楚这个会话要解决什么问题,解决完立刻清场。这条习惯比任何参数都管用。