DSH 消息数量上限与压缩恢复
DeepSeek Harness 长会话:Token 预算、消息上限与 HTTP 400
- 作者
- DeepSeekAgent.io 编辑部
- 发布
- 更新
长会话能否继续,不只取决于上下文窗口还剩多少 Token。一次模型请求同时受到模型窗口、最大输出、网关请求体、消息条数与工具 Schema 等限制;其中任何一项先到上限,Provider 都可能直接返回 HTTP 400。
2026 年 10 月 21 日,一份基于 DeepSeek Harness v0.2.1-rc.2 的社区复现记录了这种差异:Session 运行到 180 个 Goal Round,预计请求包含约 2057 条消息和 79 万 Token。模型配置声明 100 万 Token 窗口,DSH 的 Token Pressure 尚未达到压缩阈值,但上游网关在第 2048 条消息处开始拒绝请求。继续重试只会重新构造同一份超限历史。
这不是“上下文窗口计算错误”的同义问题。Token 预算回答请求有多大,消息预算回答请求由多少个离散节点组成;工具密集型 Agent 会同时放大两者,但增长速度并不相同。
同一段上下文有两把尺子
DSH 在请求前把 Session 事件投影成模型消息。用户输入、Assistant 输出、工具调用、工具结果、Developer Message 与压缩摘要都可能形成独立节点。
两个 Session 都是 20 万 Token,形状可以完全不同:
- 100 条长消息,每条约 2000 Token;
- 2000 条短消息,每条约 100 Token。
对模型上下文窗口而言,两者可能都合法;对限制单次消息数量的 API 网关而言,第二种请求可能已经无法进入模型。缩短每条消息的文本也未必有用,因为网关检查的是数组中的消息数量。
社区案例通过裁剪同一份捕获请求定位了边界:1022 与 2047 条消息返回 200,2048 条消息返回 400;把每条内容截成 40 个字符、移除 reasoning_content,结果仍未改变。这个结论只适用于被测试的兼容网关,不能外推为所有 DeepSeek 或 OpenAI 兼容接口都采用 2048 条上限,但它证明了“Token 未超限”不能排除请求结构上限。
为什么自动压缩没有触发
compaction-basic 的主动压缩会根据 Token Pressure 计算阈值。简化后可以表示为:
thresholdTokens = min(
contextWindow × thresholdRatio,
contextWindow − reservedCompletionTokens − headroomTokens
)
在复现环境中,窗口是 100 万 Token,thresholdRatio 为 0.8,输出预留 32K,额外余量 65,536。计算出的主动压缩阈值约为 80 万 Token,而失败请求约为 79 万 Token,因此 DSH 仍认为它可以继续。
这套判断对上下文溢出是合理的,却看不到网关的消息数量上限。Provider 返回的错误也不是标准 context_length_exceeded,DSH 无法把它识别成 Context Overflow 并启动补救压缩。于是形成稳定故障:
消息数量超过网关限制
→ Provider 返回 400
→ Session 历史没有缩短
→ 重试重新投影相同消息
→ 再次返回 400
为什么工具任务更容易撞上消息上限
普通对话往往是一问一答两个节点。Agent 的一轮工作可能包含计划、若干工具调用、对应结果、状态更新与下一轮验证。一轮只增加几千 Token,却能增加十几甚至几十条消息。
以下行为会加速节点增长:
- 反复读取小文件或逐项搜索,而不是批量读取;
- 工具调用失败后立即用相同参数重试;
- 多个 Sub-agent 把阶段性结果回传到父 Session;
- 插件在运行中增加或移除工具,写入 Developer Message;
- Goal 自动续轮在没有新进展时仍继续验证;
- 工具结果被剪枝后,模型重新获取同一批证据。
因此“平均每轮 Token 不高”并不等于长会话安全。对工具密集任务,轮数、消息节点和 Token 应并列监控。
当前版本可以怎么预防
社区提出为 Compaction Policy 增加独立的 maxMessages,当 measurement.nodes.length 达到预算时触发压缩,并在工具结果剪枝和每次压缩后重新检查。该补丁在报告者的分支中通过了相关测试,但截至本文撰写时,不属于 v0.2.1-rc.2 官方发布内容,不能把 maxMessages 当作当前版本已有配置。
不修改 DSH 源码时,可以采用以下控制:
- 给任务设置轮数上限。 简单任务在 20–30 轮仍没有收敛,应先暂停检查,不要让 Goal 无条件续到上百轮。
- 减少碎片化工具调用。 批量搜索、按范围读取和一次返回结构化结果,通常比大量单文件调用产生更少节点。
- 在进入长尾前主动
/compact。 不要只看 Token 进度条;工具调用数量快速增长时也应提前压缩。 - 保留阶段性成果。 把已确认结论写入短文档或任务摘要,压缩后从摘要继续,不必重复取证。
- 按 Provider 建立实测阈值。 同一个模型经过不同兼容网关时,请求限制可能不同,应在隔离 Session 中逐步测试。
降低自定义模型声明的 contextWindow,可以让 Token Pressure 更早触发。例如社区案例把 100 万临时降到 50 万后,约 1250 条消息时开始压缩,避开了该网关的边界。但这只是用 Token 阈值近似消息阈值:它会牺牲所有 Session 的可用上下文,也无法保护“消息很多、每条很短”的请求。
已经持续报 400 怎么恢复
先区分两种情况:
- 新建 Session 可以工作,旧 Session 每次都以相同 400 失败:优先怀疑旧历史已经超过某项持久限制;
- 所有 Session 都失败:先检查 Provider、Key、额度、路由和全局插件,不要直接处理历史文件。
旧 Session 的推荐恢复顺序是:
- 停止自动续轮,记录完整错误、版本、Provider 与模型路由;
- 导出或复制对话内容,保留原始 Session;
- 如果
/compact仍能执行,先手动压缩再发送一条最小测试消息; - 若压缩请求也经过同一超限历史并失败,在新 Session 中导入一份人工整理的任务摘要;
- 核对已执行的文件修改、发布、支付或远程操作,避免在新 Session 重复副作用;
- 原 Session 保留为审计记录,不直接编辑压缩日志。
手工修改 session.v4.jsonl.zstd 风险很高。Session 还关联 Header、序号、校验与 Projection Cache;即使压缩文件能够重新读取,也可能从侧边栏消失或无法继续追加。恢复的目标应是保住任务上下文和副作用状态,不是强行让原文件继续写入。
建议增加的监控
| 指标 | 建议观察方式 |
|---|---|
| 输入 Token | 每轮值和最近十轮增长率 |
| 投影消息数 | 用户、Assistant、工具调用、结果与 Developer Message 总数 |
| Goal Round | 是否持续产生新进展 |
| 重复工具调用 | 相同工具与参数的重复比例 |
| Compaction | 触发原因、压缩前后 Token 与节点数 |
| Provider 400 | 是否固定在同一 Session、同一请求结构 |
理想的请求预算不应只有 Token。它还应包含消息数量、工具定义大小、请求体字节数、轮数、时间和费用,并允许按 Provider 路由分别配置。这样 DSH 才能在最先到达的限制之前主动收缩,而不是等错误发生后猜测是哪一把尺子超限。
相关阅读
- DeepSeek Harness 长会话压缩:内置 Compaction、本地代理与恢复
- 一个简单任务为什么跑了 50 轮?
- DeepSeek Harness Sub-agent:推理预算耗尽与空结果
- DeepSeek Harness 动态工具与 KV Cache