DSH plugin 报 Output token limit reached?是输出上限不是上下文溢出
长跑 DeepSeek Harness 时反复弹出 Output token limit reached、回答被截断、发 continue 又立刻撞回同一个错误,绝大多数情况不是上下文溢出,而是输出侧封顶或「声明窗口与运行时真实窗口不一致」。 有效输出预算是三者取最小:请求声明的输出上限、provider/模型自身能力、以及 上下文窗口 − 提示词 − 预留;处置的关键是对齐 contextWindow 并开一个新会话让新值生效。
DeepSeek Harness 的两种截断:输出上限与上下文溢出到底差在哪
同一条报错文案背后是两种不同事件,判断错方向就会一直调错参数。 社区里可复现的证据分层很清楚:
- 输出侧封顶:把模型条目里的
maxTokens故意声明为 24,透传代理记录到的请求体确实带max_tokens: 24——声明值真的会落到线上;模型撞到它时,会话日志以reason: {kind: "max-tokens"}结束该轮,部分文本保留,CLI 以非零码退出,没有崩溃也没有静默丢内容。这说明该错误族的上游是「声明」而非「窗口」(#1166)。 - 剩余窗口封顶:llama.cpp 的实测日志
prompt eval time ... / 65216 tokens+eval ... / 320 tokens+total time ... / 65536 tokens,紧接一行stop processing: n_tokens = 65535, truncated = 1。这里65,536是运行时窗口,320是还能生成的上限——请求声明 8,192 也塞不进只剩 320 的余量,于是截断由「窗口余量」决定(#1166)。 - 声明与运行时错配:Ollama 侧
num_ctx设 64K、目录里却是 256K,于是每次跑到约 25%(64K)就报错;LM Studio 场景里声明 65535 之后新会话恢复、旧会话仍然不能压缩也不能继续(#1166)。 - 与上下文溢出判然不同:会话里的输入提示词可能离窗口上限很远(上例的 prompt 量本身并不大),却仍然报这一条——这正是「输出上限」与「输入溢出」的直接区分点。
- 子代理会把概率放大:多子代理跑长输出任务(如整篇翻译)时,同一个模型调用被重复很多次,撞上输出/窗口边界的机会成倍增加;社区反馈里「官方 DeepSeek 路由不出现、换第三方网关就频繁卡住」的现象,指向那条路由的流式输出与声明值,而不是子代理机制本身(#1116)。
DeepSeek Harness 判定机制:stop reason length 如何变成 max-tokens
理解这条映射链,才能知道为什么「压缩」帮不上忙、continue 也救不回来。 机制如下:
length→max-tokens:provider 返回的 stop reasonlength被映射为持久的max-tokens轮次原因,截断的那一步保留部分助手消息,且不再从这一步派发工具调用。换句话说,被截断的轮次是「完成但被削顶」,而不是失败重试。- 不会自动压缩:
dsh-agent-loop把max-tokens结束当作一次已完成的模型请求;自动压缩主要由「步进前的 token 压力」或「provider 确认的上下文窗口溢出」触发。因此撞输出上限并不必然触发压缩/恢复,这是continue原地复现的直接原因。 - 内置适配器的默认值:内置 DeepSeek 适配器的会话默认
maxTokens是 256,000,可被适配器配置、模型级上限或显式的 agent/request 值覆盖;适配器有意不把这个值与上下文窗口做钳制,所以「声明 256K 窗口、实际 64K」这类不一致不会被自动挡住。 - 目录发现是另一条链、且只在按钮上:模型最大上下文的发现只挂在 Models 页的「fetch available models」按钮上,请求时无人调用;
resolveModel()(adapter.ts:256)只用目录配置、已安装目录或默认值。因此就算读列表的代码完全正确,也只是预填一个需要你手动保存的表单字段——运行中重启本地服务换--ctx-size,Harness 不会察觉(#1166)。 - 本地运行时该读哪个字段:llama.cpp 家族在
GET /v1/models里用n_ctx表示运行时窗口;注意n_ctx_train是训练期参数、值可能不同,不是你要的那个(有同类项目因读错n_ctx_train而踩坑)。另外meta.n_ctx只在该模型真正加载后才出现,未加载时看不到、/props会返回n_ctx: 0——这也是「换模型后要重新解析」的原因。 - 有界续写而非硬顶:最安全的恢复方式是保留部分产物、发起一次有界续写(例如只补缺失的段落),并在抬高任何上限之前,先记录 prompt/输出用量与最终 stop reason(Handbook)。
DSH plugin 处置:对齐 contextWindow、开新会话与有界续写
处置顺序是「先对齐声明、再换新会话、最后才是有界续写」,顺序颠倒会白折腾。 按清单执行:
- 把
contextWindow改成运行时真实值:Ollama 看模型的num_ctx,LM Studio 看上下文滑块,llama.cpp 看GET /v1/models的n_ctx;官方 DeepSeek API 的 V4 模型支持到 1M,可放心按真实能力写。自定义 provider 若没写maxTokens,会走通用路由的兜底值,与你那条网关的真实上限对不上就会提前截断,所以两条都要显式声明:
llm-pi-ai:
providers:
你的网关:
models:
- id: glm-5.2
contextWindow: <运行时真实上下文窗口>
maxTokens: <该模型真实的最大输出>
- 改完必须开新会话:旧会话保留旧的窗口记录,改配置不会回填,实测「新会话正常、旧会话既不能压缩也不能继续」。同时注意重启 Harness 可能把改动覆盖回默认值,改完先确认落盘值再开新会话。
- 同时检查输出侧:除了
contextWindow,还要看maxTokens/ providermax_tokens(复现记录里出现过 8,192 的输出声明)。有效输出预算取三者最小值,任何一项偏小都会成为瓶颈。 - 用对照实验确认归属:同一个任务、同一个提示词,在官方 DeepSeek 路由上再跑一遍。不卡 → 属于那条第三方网关的流式/序列化问题,应找网关方;照样卡 → 回到上面的声明对齐与压缩策略(#1116)。
- 长跑任务先压缩再继续:不要靠反复
continue硬顶,压缩后再续写,或把长输出任务拆成多个有界子任务。想看清每次请求具体卡在哪个上限,可用dsh plugin --profile web add dsh-budget之类的用量 DeepSeek插件记录每会话的 token 用量、时延与成本(社区插件,作者自述维护)。 - 想自查「线上到底发了什么」,可用带透传记录的验证工具(如 pi2dsh 的
verify-provider-threads-e2e.mjs)核对max_tokens是否真的落到请求体。DSH插件 本身的装卸与版本核对,建议走 DSH Plugin Hub 的「设置 → 插件市场」,失败会自动回滚 manifest。本地模型的窗口与连接类问题,另见 本地模型连接排查 与 本地模型配置。
DSH plugin 排查注意事项
先分清「输出封顶」与「上下文溢出」——这条报错多数不是输入塞满,判断错方向就会一直调错参数。 六条要点:
- 不要把这条报错当上下文溢出治:报错时先看 prompt 量离窗口还有多远,以及会话日志里的结束原因是不是
max-tokens。 continue不是恢复机制:它只是在同一预算下重试;预算没变,结果就不会变。- 声明与实际必须一致:本地服务的窗口由启动参数决定,改参数后 Harness 不会自动感知。
n_ctx与n_ctx_train别搞混:前者是运行时窗口,后者是训练期长度。- 旧会话不要指望修好:保留产物、开新会话、做有界续写更省时间。
- 子代理高频命中是放大效应:多子代理并发长输出时更易撞边界,优先对齐声明值而不是换子代理运行时。

来源:Discussion #1166、Discussion #1116、pi2dsh provider-threads-e2e、DeepSeek Harness Handbook · Output token limit。
常见问题
在 DeepSeek Harness 里,Output token limit reached 通常不是上下文溢出,而是「输出侧上限」或「声明窗口与运行时窗口不一致」。它把 provider 的 stop reason length 映射为持久的 max-tokens 轮次原因,截断的那一步保留部分助手消息、不再派发工具调用;典型证据是 prompt 离窗口上限还很远却照样触发——例如 llama.cpp 日志里 65,216 prompt + 320 generated = 65,536 total 且 truncated = 1(来源:Discussion #1166)。
在 DeepSeek Harness 里,撞到输出上限不会被当作「需要自动压缩」,所以 continue 会原地复现。dsh-agent-loop 把 max-tokens 结束视为一次已完成的模型请求,自动压缩主要由「步进前的 token 压力」或「provider 确认的上下文窗口溢出」触发,因此输出封顶并不会自动进入压缩/恢复流程。在窗口余量只剩几百 token 的场景里(如余量 320 token 而请求声明 8,192),续写的可用预算仍然不够,于是 continue 立刻复现(来源:Discussion #1166)。
DeepSeek Harness 的本地部署特别容易遇到这个报错,是因为「声明值」和「运行时真实值」两条链没对齐:目录里写的是模型发布上限(例如 256K),而本机服务实际以 64K / 65535 之类的小窗口在跑,于是跑到真实窗口的百分比位置就报错。有效输出预算实际是三者取最小:请求声明的输出上限、provider/模型本身能力、以及 上下文窗口 - 提示词 - 预留。修法是把 contextWindow(Ollama 对应 num_ctx、LM Studio 对应上下文滑块)改成运行时真实值,并**开一个新会话**让新值生效(来源:Discussion #1166)。
DeepSeek Harness 官方建议旧会话别救:旧会话保留旧的窗口记录,改配置不会回填,实测「新会话正常、旧会话仍然既不能压缩也不能继续」。稳妥做法是保留旧会话里的部分产物,新开一个会话做有界续写(只补缺失段落),并在提高任何上限之前先记录 prompt/输出用量与最终 stop reason(来源:Discussion #1166)。
相关术语
- output cap(max-tokens 轮次原因)
- output cap 是 DeepSeek Harness 把 provider 的 stop reason `length` 映射成的持久轮次原因。它表示这次生成被输出预算截断,而不表示输入把上下文窗口塞满。— https://github.com/deepseek-ai/deepseek-harness/discussions/1166
- contextWindow / n_ctx
- contextWindow / n_ctx 是声明的上下文窗口大小。llama.cpp 家族在 `GET /v1/models` 里以 `n_ctx` 暴露真实值(`n_ctx_train` 是训练期参数,不是运行时窗口);Ollama 对应 `num_ctx`,LM Studio 对应上下文长度设置。— https://github.com/deepseek-ai/deepseek-harness/discussions/1166
- compaction(压缩)
- compaction 是 DeepSeek Harness 在上下文压力下自动收敛历史、腾出空间的机制。它由步进前的 token 压力或 provider 确认的窗口溢出触发,因此不覆盖「输出被截断」这一类结束原因。— https://github.com/deepseek-ai/deepseek-harness/discussions/1166
来源
- deepseek-harness Discussion #1166:Does anybody also encounter "Output token limit reached" error while using dsh?· deepseek-ai(GitHub Discussions)
- deepseek-harness Discussion #1116:多个子代理卡住,提示已达到输出 token 上限、回答被截断· deepseek-ai(GitHub Discussions)
- pi2dsh provider-threads-e2e:声明 maxTokens 到落线的可复现审计(含 token-limit 用例)· GitHub(weijiafu14/pi2dsh)
- DeepSeek Harness Handbook:Output token limit——source-linked diagnostic and bounded recovery checklist· sandbaseai.github.io