DeepSeek Harness 上下文压缩异常:过早触发、切小模型卡死与 compact 报错

故障排查发布于 2026-10-17作者: DeepSeek Plugin 插件市场
DeepSeek Harness上下文压缩compactionheadroomTokensCONTEXT_WINDOW_EXCEEDEDcompact缓存命中
DeepSeek Harness 里上下文压缩有一组同源故障:262K 窗口跑到 186K 就自动压缩且很慢、从大窗口模型切到小窗口模型后整个会话卡死、/compact 报 imageRequestPricing is not a function。本文逐个给出判据与配置级处置。

DeepSeek Harness(DSH)的上下文压缩有一套「看起来各自独立、实际同源」的故障:模型窗口明明 262K,跑到 186K 就自动压缩、而且压缩还很慢;从 1M 窗口模型切到 200K 窗口模型后整个会话卡死,每条请求都 400 而 /compact 也失败;上下文真溢出之后会话直接永久不可用;/compact 有时干脆报一句 imageRequestPricing is not a function;还有一条最隐蔽的——压缩每次悄悄多烧二十几万未缓存 token。 这些现象背后是同一套配置与三条实现细节:触发阈值是 min(W×0.8, W−O−headroom) 两个分支取小,而 headroomTokens 默认 65536 是绝对常量;构造摘要请求时不判断目标模型装不装得下、也不带会话的推理档;以及 tokenMeter 的适配器调用用可选链保护错了位置。本文按「什么时候压 → 压得动吗 → 花多少」三段把可改的配置逐项列出来(#9131、#9814、#9635、#9629、#9278)。

DeepSeek Harness 什么时候压:到达 186K 就触发,因为 headroom 是绝对值

一句话结论:触发点不是「窗口的百分之几」,而是 min(窗口×0.8, 窗口−输出预留−headroom) 两分支取小;headroomTokens 默认 65536 且不随窗口缩放,所以在中等窗口模型上先命中的往往是第二支。 报错/现象是「明明还有余量就开始压缩」,日志里会出现 summary is not smaller than the shadowed content 这类提示(#9131)。

据社区核对 README 与源码给出的公式与默认值:

项默认值说明
thresholdRatio0.8第一分支的系数:窗口 × 0.8
headroomTokens65536(绝对)第二分支的减项,不随窗口缩放
输出预留 O约 6K按模型/路由声明
摘要 maxTokens默认 = headroomTokens也就是摘要一次最多可生成 64K(含推理 token)

把 262K 代进去:第一支是 209.6K,第二支是 262K − 6K − 65.5K ≈ 190K——第二支更小,所以实际在约 190K 触发,与用户观察到的 186K 吻合(#9131)。

DeepSeek Harness 「压缩特别费时」的原因也在同一个默认值

maxTokens 默认等于 headroomTokens(65536),意味着摘要调用一次可以生成 64K token(含推理)——这是「压缩很慢」的主要来源,而不是压缩本身复杂(#9131)。

DeepSeek Harness 六项可改配置(都在 compaction 插件层)

  1. 调小 headroomTokens(例如 16384),让第二分支不再过早生效:
yaml
headroomTokens: 16384
  1. 让 thresholdRatio 更靠近 1,但注意必须大于留存比例,否则会出现「刚压完立刻又触发」的抖动(#9131)。
  2. 按 {provider, model} 单独覆盖,避免一套参数套在所有模型上:
yaml
modelPolicies:
  - provider: <你的 provider>
    model: <你的模型>
    headroomTokens: 16384
    thresholdRatio: 0.9
  1. 给摘要调用显式设一个较小的 maxTokens,直接削掉「压缩很慢」的主因(#9131)。
  2. 挂结果剪枝插件:dsh-compaction-tool-result-pruner 走的是「不调模型」的剪枝路径,能减少需要摘要的体量(#9131)。
  3. 或干脆关掉自动压缩:把自动档设成 auto: false,改为手动 /compact,代价是要自己盯上下文(#9131)。

DeepSeek Harness 压得动吗:切到小窗口模型后会卡死

一句话结论:阈值会按目标模型的窗口重算(这一步是对的),但构造摘要请求时没有判断目标模型是否装得下**——它把整段历史原样发给「刚刚被切过去的那个小窗口模型」,于是摘要请求自己就超限、必然失败;而自动压缩路径还会静默吞掉失败,界面上只留一个上游 400。** 症状是从 1M 窗口模型切到 200K 窗口模型后,会话每条请求都失败,/compact 也一样失败(#9814)。

DeepSeek Harness 三步判据 + 两条处置

  1. 看症状时序:问题出现在「切换模型之后」而不是会话一直如此;切换前正常。这是本条最强的指纹(#9814)。
  2. 确认失败的是摘要请求而不是主请求:如果主请求体量本身没超窗口,却在切模型后立刻 400,那基本就是摘要这一路在炸(#9814)。
  3. 处置一:把摘要 provider 固定到大窗口模型。
yaml
summarizationProvider:
  provider: <大窗口 provider>
  model: <大窗口 model>

注意:这只是止血——如果历史本身已经超过所有可用模型的窗口,死锁依旧存在(#9814)。

  1. 处置二:装按声明窗口改投更大路由的社区插件 @argszero/dsh-compaction-target-guard,它在摘要被拒后按 declared window 换一个装得下的路由重试:
bash
npm i @argszero/dsh-compaction-target-guard
  1. 注意声明窗口可能与真实不符:如果 provider 声明的窗口大于服务端实际限制,本条的判断会继续错下去,需要一并核对(#9814)。

DeepSeek Harness 溢出之后:会话为什么「永久」不可用

一句话结论:三层机制叠加——本地压力估算低估真实 prompt、摘要把整段影子区原样重放给刚溢出的同一路由、产品内又没有强制的恢复入口——于是溢出一次就再也压不动。 典型错误串是 CONTEXT_WINDOW_EXCEEDED 与 Compaction could not produce a useful summary(#9635)。

三层拆开看(据报告者给出、社区逐条核对源码确认):

  1. 估算偏差:本地压力启发式会低估真实 prompt 体量(讨论中给出的量级约为 0.67×),所以「看起来还没超」的时候其实已经超了;
  2. 重放自伤:摘要请求会把整个影子区原样重放给刚刚溢出的同一个路由——重放是 summarizer.ts 的有意设计(为了复用 KV cache),代价是摘要请求自身也超限;
  3. 没有手动出口:产品内没有 /compact --force、也没有非 LLM 的强制截断路径,所以一旦进入这个状态,用户没有可点的恢复按钮(#9635)。

DeepSeek Harness 四步自救

  1. 固定一个大窗口模型做摘要(同上一节的 summarizationProvider)——让摘要请求能被接住。
  2. 装溢出恢复类插件 @argszero/dsh-overflow-recovery:它在被拒后丢弃最旧的可丢区段再重试,属于「有损但能续命」的路线(#9635)。
bash
npm i @argszero/dsh-overflow-recovery
  1. 离线手术:直接编辑该会话的 JSONL,把最旧的可丢区段删掉,再重新打开会话。务必先备份,并保持剩下事件的顺序与编号不变(#9635)。
  2. 预防性配置:把 headroomTokens 按比例调小、把 thresholdRatio 调晚、挂工具结果剪枝插件,避免真的走到溢出(回到本文第一节)。

DeepSeek Harness 压得贵吗:摘要请求差一个推理档,缓存命中掉到 1%

一句话结论:摘要请求在构造时没有带上与会话一致的 reasoning_effort,于是它的缓存键与主请求不同,后半段完全没命中缓存——每次压缩额外产生约 25 万未缓存 token。 把摘要请求的推理档改成与会话一致后,命中率从 1.2%~8% 提升到 99.9%(#9629)。

据社区核对源码:compaction-basic 的摘要构造代码里完全没有 reasoningEffort 这个字符串,而主请求会带上会话的 effort,两边缓存键不一致(#9629)。

处置步骤:

  1. 先量化:对比一次压缩前后的用量统计,观察「未缓存输入 token」是否出现一个数量级上的尖峰(讨论中的量级是每次约 25 万)。
  2. 回填推理档:装社区插件 @argszero/cordis-plugin-aux-reasoning,它按 purpose 标记给辅助调用回填与会话一致的推理级别:
bash
npm i @argszero/cordis-plugin-aux-reasoning
  1. 验证:再触发一次压缩,观察未缓存输入 token 是否显著下降、缓存命中率是否上到九成以上。
  2. 注意结论边界:报告者并未主张「上游缓存键一定这么算」,只主张 A/B 现象成立——所以把它当可复现的优化而不是定论(#9629)。

DeepSeek Harness 压不动:/compact 报 imageRequestPricing is not a function

一句话结论:可选链 ?. 只保护「适配器不存在」,不保护「适配器存在但没有实现这个方法」,于是一个鸭子类型注册的第三方适配器就能让 tokenMeter 在图片计价阶段抛 TypeError,压缩整体失败。 报错原文是:

command.execute failed: gateway/internal: this.adapters.get(...)?.adapter.imageRequestPricing is not a function

据报告者分析(社区复核认可):这与具体是哪个插件无关——把视觉类与搜索类插件卸掉仍然复现,说明它命中的是「某个适配器缺方法」而不是「某个插件写错了」(#9278)。

DeepSeek Harness 三步处置

  1. 先确认不是插件引起的:临时禁用视觉/搜索类插件后重跑 /compact,若仍报同一句,即可排除单插件因素(#9278)。
  2. 装守卫插件绕过:报告者发布了 c6huli/dsh-llm-pricing-guard,把「适配器没有这个方法」当作「无图片计价」处理(#9278)。
  3. 代码侧正解方向:把 ?. 换成 typeof … === 'function' 守卫并保留 this 绑定,同时让 tokenMeter 把缺方法视为无图片计价(#9278)。

DSH插件 排查注意事项

压缩类问题的排查顺序建议是:先看「触发时机」→ 再看「摘要发给了谁」→ 最后看「花了多少」,这三步对应本文三节,别混着改配置。 九条要点:

  1. headroomTokens 是绝对常量:它不随窗口缩放,这是「中等窗口过早压缩」的根本原因(#9131)。
  2. thresholdRatio 不能等于留存比例:调得太晚会出现压缩抖动(刚压完又触发)(#9131)。
  3. 摘要默认 maxTokens 也跟着 headroom 走:这是「压缩很慢」的直接原因,显式设小最有效(#9131)。
  4. 切模型后卡死优先怀疑摘要目标:阈值重算没问题,问题在摘要没判断装不装得下(#9814)。
  5. 重放是设计不是 bug:理解这一点,才不会「等官方修掉重放」(#9635)。
  6. 溢出后先备份再动 JSONL:离线删区段是可行的自救,但没有备份就没有退路(#9635)。
  7. CONTEXT_WINDOW_EXCEEDED ≠ 额度用尽:两者文案相近、含义完全不同,别照着「充值」的方向修(#9635)。
  8. 压缩变贵先查推理档:摘要请求缺 reasoningEffort 是缓存失效的根因,量级可达每次 25 万 token(#9629)。
  9. 修复状态如实:以上五条在讨论所指版本(0.2.0-rc.2~0.2.1-alpha.*)里都未修;本文给出的都是配置级与用户侧处置(调参、固定摘要 provider、剪枝插件、离线删区段),不是官方修复。

压缩这类问题往往和「插件装了什么」纠缠在一起——毕竟摘要 provider、剪枝插件、计价守卫都是插件形态。如果你想先在界面上把当前环境挂了哪些插件、最近有没有装卸看清,与其在各处手敲命令,不如用 DSH Plugin Hub 这类可视化插件市场:已安装列表、装卸历史与系统日志集中在一处,排配置冲突会快不少。

设置

来源:Discussion #9131、Discussion #9814、Discussion #9635、Discussion #9629、Discussion #9278。

常见问题

DeepSeek Harness 里模型窗口是 262K,但每次跑到 186K 左右就自动压缩,是没吃满上下文吗?

DeepSeek Harness 在 186K 就自动压缩,是因为触发阈值不是单一的百分比,而是 min(窗口×0.8, 窗口−输出预留−headroom) 这两个分支取小,而 headroomTokens 默认是 65536 的绝对常量、不随窗口缩放。按公式反推,186K 说明生效的是第二支而不是 0.8 那一支。想让它晚点触发,改配置里的 headroomTokens(例如调到 16384)并让 thresholdRatio 更靠近 1。

DeepSeek Harness 从大窗口模型切到小窗口模型后整个会话卡死、/compact 也失败,为什么?

DeepSeek Harness 切小窗口后卡死,是因为压缩摘要请求被发给了目标模型自己。阈值会按目标模型的窗口重算(这一步是对的),但构造摘要请求时没有判断目标模型装不装得下整段历史,于是摘要请求携带整段历史再发给一个小窗口模型,必然超限;而自动压缩路径还会静默吞掉失败,界面上只看到上游 400。缓解办法是把摘要用的 provider 固定到大窗口模型,或使用按声明窗口改投更大路由的社区插件。

DeepSeek Harness 上下文溢出后会话完全不可用、连 /compact 都失败,还有救吗?

DeepSeek Harness 溢出后不可用有三层原因叠在一起:本地压力估算低估真实 prompt、摘要把整段影子区原样重放给刚溢出的同一路由、以及产品内没有强制截断的恢复入口。可行的自救是固定一个大窗口模型做摘要、使用溢出恢复类插件,或离线直接改会话 JSONL 把最旧的可丢区段删掉再重开。注意重放是刻意的设计(为了复用缓存),不是单纯的 bug。

DeepSeek Harness 的 /compact 报 imageRequestPricing is not a function,是什么原因?

DeepSeek Harness 的 /compact 报 imageRequestPricing is not a function,是可选链用错位置的典型:?. 只保护「适配器不存在」,不保护「适配器存在但没有实现这个方法」。第三方插件用鸭子类型注册适配器、不带图片计价方法时,tokenMeter 在解析图片计价阶段就抛 TypeError,与哪个插件无关(把视觉与搜索类插件卸掉仍然复现)。绕过办法是装一个把缺失方法当「无图片计价」处理的社区守卫插件。

DeepSeek Harness 压缩为什么每次都多花二十几万未缓存 token,是正常的吗?

DeepSeek Harness 压缩每次都多花二十几万未缓存 token 并不正常。压缩摘要请求构造时没有带上与会话一致的 reasoning_effort,导致它的缓存键与主请求不同、后半段完全没命中缓存。讨论中实测把摘要请求的推理档改成与会话一致后,缓存命中率从 1.2%~8% 提升到 99.9%,单次压缩省下约二十几万未缓存 token。处置方式是用辅助调用回填推理级别的社区插件,或直接给摘要 provider 固定同一档位。

相关术语

headroomTokens
headroomTokens 是 DeepSeek Harness 压缩配置里为「留给后续输出」预留的 token 数,默认 65536,且是一个不随模型窗口缩放的绝对常量。它与 thresholdRatio 共同决定自动压缩的触发点,也常被用作摘要调用的输出上限。— https://github.com/deepseek-ai/deepseek-harness/discussions/9131
ThresholdRatio(压缩触发比例)
thresholdRatio 是压缩触发阈值里的比例系数,与 headroomTokens 一起参与 min(窗口×比例, 窗口−输出预留−headroom) 的计算。它越接近 1,压缩触发越晚;但必须留出比留存比例更大的余量,否则会出现刚压缩完又立刻触发的抖动。— https://github.com/deepseek-ai/deepseek-harness/discussions/9131
summarizationProvider
summarizationProvider 是执行压缩摘要时使用的模型路由。它独立于当前会话模型,因此可以被固定到一个窗口更大的模型上;当会话模型窗口过小时,是否把它固定到大窗口模型直接决定压缩能否完成。— https://github.com/deepseek-ai/deepseek-harness/discussions/9814
CONTEXT_WINDOW_EXCEEDED
CONTEXT_WINDOW_EXCEEDED 是「输入加输出超过模型窗口」时返回的错误码,属于超限类失败而非额度不足。它也是上下文溢出的判据之一,与「额度已用尽」是完全不同的含义。— https://github.com/deepseek-ai/deepseek-harness/discussions/9635

来源