Repository navigation
Replies: 4 comments
|
补充两点独立印证 + 一个观察。 1. 上游硬数字印证(第三方实测,与本文互证)同一 DeepSeek 网关返回的原文数字:
⇒ 可用输入 = (来源:API易 2026-08-05 对 DeepSeek V4 系列的网关实测,含 2. 关于「自动压缩为什么没救回来」
本机 推测(供维护者参考):本次越界的 400 响应体被截断成 31 字节且没有 3. 补充:本机可用的覆盖点(供其他用户规避)
|
|
Your arithmetic holds, but the mechanism inside the harness is not root cause #2, and the difference decides what fixes it. Everything below is source from The threshold does subtract an output reserve — just not the number your request sends
const messageBudgetTokens = contextWindow - reservedCompletionTokens
const pressureBudgetTokens = messageBudgetTokens - policy.headroomTokens
const thresholdTokens = Math.floor(Math.min(
contextWindow * policy.thresholdRatio, // DEFAULT_THRESHOLD_RATIO = 0.8 (:20)
pressureBudgetTokens,
))and the reserve is ( function reservedCompletionTokens(agent: Agent, defaultMaxTokens: number | undefined): number {
const configured = agent.session.requestHeader()?.config.maxTokens
return configured ?? defaultMaxTokens ?? 0 // ← note the last fallback
}
The wire takes the other number. The adapter forwards a cap only when the call carries one ( So the two numbers have different owners, and with your route (
That gap is the wallThe gate is a fraction of the declared window, while the provider refuses everything above Two consequences worth separating:
The meter is not the gap, for what it's worth: What we can contributeWe publish |
|
这个是共享上下文1m ,包含了工具,提示词等。技能加载多了会消耗上下文,可以使用极简模式测试数据。 |
|
Narrowing what we wrote earlierWe first stated the predicate as
So "the reserve actually carried on the wire" is not readable from either side of the seam — which is exactly why the compaction threshold (a fraction of the declared window) and the wire cap are free to disagree. That disagreement is the other half of your report, and it is a core question: 0.4.0 does not address it. What 0.4.0 changes is what the failure means, so the tree can recover from it. What 0.4.0 does insteadTwo numbers the plugin does own: the harness' own meter, and the declared window. On a turn like yours (declared window 1,000,000, the session's own meter at 673,267) the terminal failure becomes:
Three properties worth stating, because they are what makes it safe to leave on by default:
The README section The line is a substitute — the earlier arithmetic is not observable here carries the same narrowing with the source lines. 136 tests; the rule is pinned in both directions — it fires at the line and declines below it, an attributed code at the same price and a |
Uh oh!
There was an error while loading. Please reload this page.
TL;DR (English) — With provider
opencode-go/ modeldeepseek-v4.1-flash(catalog:contextWindow: 1000000,maxTokens: 384000), every request carriesmax_tokens: 384000. The endpoint rejects the request with HTTP 400 onceprompt_tokens + max_tokens > 2^20 (1,048,576). The 400 body is a 31-byte JSON fragment ({"model":"deepseek-v4.1-flash"}) without anerrorfield, so the OpenAI SDK surfaces it as400 status code (no body)and the desktop UI shows「本轮运行失败 400 status code (no body)」. Effective usable input is therefore1,048,576 − 384,000 = 664,576tokens (63.4% of the advertised 1M), and the auto-compaction threshold (thresholdRatio × contextWindow) ignores that output reserve, so long sessions deterministically hit the wall.环境
0.2.0-rc.2(Windows),desktopprofile,直连(无本地代理)opencode-go,baseURL: https://opencode.ai/inference/go/openai/v1deepseek-v4.1-flash:contextWindow: 1000000,maxTokens: 384000观测数据(同一会话,数字来自 API 返回的真实 usage)
+ 384,000边界落在 662,208 ~ 673,267 之间,命中
2^20 − 384,000 = 664,576。注意 turn 25 那行:
626,644 + 384,000 = 1,010,644 > 1,000,000仍然成功 —— 说明窗口是 2^20 (1,048,576),而不是 1,000,000。失败后该会话每条请求都必败(
usage: 0、0 token、INVALID_REQUEST,无 llm/retry),只能手动/compact才能恢复(/compact 之后同一会话又正常工作了 1 小时、turn 30→37)。复现
A. 最小复现——同一份 ~2.93 MiB 的
messagespayload,只改max_tokens:384000{"model":"deepseek-v4.1-flash"}8同一份 payload 再加 15 万个汉字(≈+150k token)后,
max_tokens=8仍 200、max_tokens=384000仍 400 —— 即判定与「输出预留」强相关。B. 自然复现:让会话 prompt 自然增长到 66 万 token 以上,下一次任意请求即 400(错误 UI 见上)。
根因分析
max_tokens直接取模型目录的maxTokens(384000);而 DSH 自己的压缩实现默认只预留headroomTokens = 65536。两者不一致 → 每次请求白扔 36.6% 的窗口。thresholdRatio × contextWindow计算,没有减去max_tokens,因此不会在这个预留造成的墙之前触发(该长会话 30 轮内一次自动压缩都没发生)。error字段 → SDK 只能报400 status code (no body)。用户侧完全看不出是「上下文+输出预留超限」,只能猜测网络/代理/密钥问题,排查成本极高。建议
max_tokens = min(catalog maxTokens, headroomTokens / 可配置值)(或按window − prompt动态收敛),让 1M 窗口真正可用 ~94%。contextWindow − max_tokens(或显式扣除预留)。error字段;现在这种 31 字节碎片会把所有 400 都伪装成「no body」。agent-default-modelentry 的 config 不接受maxTokens(实测手动写入后,应用启动时该键被剥离,且那次启动核心服务不可用:sessionController is unavailable,会话列表空白)。建议要么支持该键,要么在文档里指明正确位置是:llm-pi-ai.providers.<provider>.modelOverrides[model].maxTokens(schema 已支持)agent-loop.agents[].maxTokens(附:排查中确认
llm-pi-ai的modelOverrides/models[]都声明了maxTokens,所以用户侧是有覆盖点的,只是位置不直观、且与压缩的headroomTokens语义不一致。)All reactions