Repository navigation
Replies: 1 comment
|
Every source claim in this report reproduces on The mechanism, verified from the published tarball
const CHARS_PER_TOKEN = 3.5
export function estimateContextTokens(context) {
const usageInfo = getLastAssistantUsageInfo(messages) // newest assistant message that is
if (usageInfo) { // neither "aborted" nor "error",
… // with a non-zero usage total
return { tokens: usageTokens + trailingTokens, … }
}
… // otherwise: pure character estimate
}
Your own arithmetic then follows exactly: That last pair is worth having before your suggestion 1 is written: The patch, and where a fix can actually landThe patch does not touch the clamp — confirmed, with one file more than you listed. Where a fix lands matters as much as whether one exists: The compaction threshold: verbatim, and closer to a hard error than the report says
const thresholdTokens = Math.floor(Math.min(
contextWindow * policy.thresholdRatio,
pressureBudgetTokens,
))with What the report does not say is that this function throws on both ends, and the reported catalog is one step from one of them:
So "give the catalog a documented upper bound" is not tuning advice: it is the difference between a session that compacts constantly and one that does not start. The classification half is already correct — and the gap is next to itYour event sequence shows the 400 arriving as What your log shows is the other gap in that same handler. Read The summarizer's own stream died, no durable reduction landed, so the shipped recovery yields and the raw 400 reaches you — "no durable reduction ⇒ no retry authorized ⇒ the original 400 is thrown to the user" (translated from the Chinese original). That fall-through is the trigger condition of Prevention: the one request-side lever a plugin ownsSuggestion 1 is upstream's and I agree with it. For the local half, the seam is newer than most reports assume: prepareCall(config: LlmCallConfig, signal?: AbortSignal, configure?: ConfigureCall): Promise<PreparedLlmCall>( type ConfigureCall = (controls: Readonly<LlmCallControls>, model: Readonly<LlmResolvedModelInfo>) => LlmCallControlsand Its honest limit is your own defect: such a plugin's budget comes from the harness's token meter, which is an estimate, and your error term is an estimate that undercounted dense tool output by 2.42% and 1.59%. A clamp driven by the same class of number needs a conservative margin and should be observable (report before enforce), which is why I would describe the seam here rather than claim it solved. One more fact from the adapter's own README ( |
Uh oh!
There was an error while loading. Please reload this page.
摘要
@earendil-works/pi-ai@1.1.0(DSH 经@deepseek-ai/dsh-llm-pi-ai使用)在clampMaxTokensToContext中用本地启发式估算把max_tokens填满整个上下文窗口,只保留固定的CONTEXT_SAFETY_TOKENS = 4096。只要估算比服务端真实计数低超过 4096,拼出的请求就是输入 + 输出 = contextWindow + 1,被 provider 以 400 拒绝。这是确定性的客户端缺陷,与具体 upstream 无关。环境
0.2.0-rc.2(desktop profile)@deepseek-ai/dsh-llm-pi-ai→@earendil-works/pi-ai@1.1.0openrouter/z-ai/glm-5.3-flashcontextWindow: 1048576,maxTokens: 943718现有
patches/@earendil-works__pi-ai@1.1.0.patch未触及该函数(它只删除流式 tool 参数的逐 delta JSON 解析)。现象
同一 session 内两次 400,算术完全一致(
W = 1048576):est = W − 4096 − outputest + 4096(客户端认为的总量)第一次的 provider 报文自己给出了构成:
1051794 tokens (193907 text input, 115647 tool input, 742240 in the output)—— 被低估的正是 tool 输出。第二次原文:
外层由 OpenRouter 包装:
{"message":"Provider returned error","code":400,"metadata":{"raw":"…","provider_name":"Parasail","is_byok":false}}。根因
随包发布的 pi-ai(
dist/utils/simple-options.js/dist/utils/estimate.js):调用点
buildBaseOptions:estimateContextTokens优先复用最近一条 assistant 的 usage,再对其后的消息做启发式估算(estimateMessageTokens/estimateTextTokens);tool 结果恰好落在被估算的那一段。因此当估算偏差 ε > 4096 时,请求总量必然是W + (ε − 4096)。DSH 侧没有本地兜底:仓库内 grep
clampMaxTokens/CONTEXT_SAFETY_TOKENS无结果。安全网为什么没兜住
该 session(900 个 step)共 136 次
compaction/start,只有 99 次干净收尾:Stream ended without finish_reason×8、pi-ai stream idle timeout×2、terminated×2、summary is not smaller than the shadowed content×6、summarization truncated at the token cap×2。主路由assistant/attempt失败中TRANSPORT×28、TIMEOUT×4。失败前后的事件序列(
CONTEXT_WINDOW_EXCEEDED已被正确归一化,溢出恢复也确实启动了):恢复用的 summarizer 自身流中断 → 没有持久缩减 → 不授权 retry → 原始 400 抛给用户。
放大因素
profile 给该模型声明
maxTokens: 943718(窗口的 90%)。@deepseek-ai/dsh-compaction-basic的阈值是即 harness 认为 prompt 必须压到约 39k,而模型实际能接受接近 1M 的输入。结果是每一步都在压缩(turn 62 有 66 个 step);压缩一旦失败,请求就带着约 300k 的 prompt 出去。此处
O直接取自请求 envelope 的maxTokens,所以把maxTokens声明为窗口的 90% 会把压力预算压到几乎为零。建议
max(4096, 2% × estimate));或不要“填满窗口”,保留固定比例。dsh-llm-pi-ai在传入options.maxTokens前预留安全比例;现有 patch 文件是合适的落点。maxTokens生成成窗口的 90%,会让上述阈值公式塌陷,建议给出上限建议或告警。复现要点
任意 pi-ai 路由 + tool 输出占比高的 prompt,使本地估算低估超过 4096 tokens 即可命中。本案例中
objdump反汇编文本被大量回灌,估算偏低 4097(另一处 7314)。All reactions