[Bug] 3.11.2 从持久化状态重建的请求(工具结果续接 / fork 首轮)未按上游真实上下文裁剪:openai-compatible 上游 400,会话卡死且 fork 恢复失效
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 22
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
摘要
3.11.2 起,从持久化状态重建的模型请求(工具结果回传后的续接请求、fork 会话的首条请求)未按上游真实上下文限制做有效裁剪,导致长会话的重建请求被 openai-compatible 上游以 HTTP 400 拒绝(错误体明确指出上下文超出可处理范围),且该错误不可重试。后果:①长会话在任意一次工具调用后的续接即卡死(多次重试同样失败);②fork 恢复工作流失效——fork 首条请求复制活跃链后同样 400,fork 会话不产生任何持久化记录。全新空白会话(活状态直出请求)不受影响。
环境
- ZCode Desktop 3.10.1.6272 → 3.10.2.6414 → 3.11.2.6792(2026-09-04 16:18 自动升级),Windows 10 26200 x64
- 自定义 openai-compatible 渠道(baseURL 指向本机中转),模型 kimi-k3 / glm-5.3 / qwen3.8-max;配置中 qwen3.8-max
limit.context = 1,000,000 - 失败集中于 3.11.2 升级当日晚间起;3.10.2 时代同等规模会话多日正常续接与 fork(观察性对比)
上游真实上下文实测(canary 法,可复现)
构造「开头口令 + 随机填充 + 结尾询问口令」请求探测 qwen3.8-max:
| 请求填充 | 上游报告 prompt_tokens | 结果 |
|---|---|---|
| 300,000 字符 | 255,809 | ✅ 口令完整记得 |
| 400,000 字符 | 341,043 | ✅ 记得 |
| 465,000 字符 | 396,492 | ✅ 记得 |
| 470,000 字符 | 400,410 | ✅ 记得(边界有 ±抖动:同量级请求此前曾被 400 拒绝) |
| 475,000+ 字符 | — | ❌ HTTP 400 |
即上游真实上限 ≈ 40 万 token(有多通道/计数抖动),而用户配置声明为 100 万——虚标 2.5 倍。两日前同渠道同模型会话在 262,308 input tokens 时续接失败,失败请求体估算已逾 40 万 token(详见下)。
故障实例 A:续接请求卡死(sess_058dd90d,model-io 日志轮转前捕获)
- 22:05:21Z 请求成功:裁剪窗口(请求记录 64 条消息、部分省略),usage.inputTokens = 262,308,正常返回 tool-call;
- 22:07:14Z 续接请求失败:消息数骤增为 292 条(自最近 compaction 边界以来的活跃链,内容 571,587 字符 + 工具定义 129,238 字符),上游 400(错误体:「请求内容过大……本次上下文……超过了可处理范围」),4 次重试逐字相同地失败,回合终止,会话卡死;
- 该 292 条重建请求未经 token 预算裁剪——若按配置声明的 1M 估算则「余量充足」,裁剪永不触发。
故障实例 B:fork 恢复失效(sess_45aedb28,058dd90d 的 fork)
- fork 首条请求 = 291 条消息的活跃链重建 → 同样 400;
- 会话未产生任何持久化消息(空壳),标题/元数据存在但无法使用;
- 3.10.x 时代对更大规模会话(数千消息)执行 fork 均正常恢复。
触发条件说明(未完全定界)
活状态小上下文的续接请求不受影响(多日正常);已观测的触发场景为「重建时活跃链体积 ≳ 上游真实上限」。此外,中转 400 的错误措辞未被 ZCode 的「上下文超出」识别规则匹配(现显示为通用 "Provider rejected the model request." 且终止回合),建议一并放宽;另请勿将 model-io 日志中失败请求记录的 bodySource:"ai_sdk_options" 等字段误读为线上格式——那是日志器记录的序列化前选项对象(本报告最初亦曾误判,特此标注)。
修复建议
- 重建类请求(工具结果续接 / 冷恢复续接 / fork 首轮)与活状态请求走同一条上下文裁剪管线(按 provider 声明的 limit.context 预算裁剪);
- 放宽上游 400 错误体中「上下文超出」的识别(当前因措辞不匹配而显示为通用错误且不可重试,用户无从自救);
- 长期:可考虑在收到上游「上下文超出」400 时自动下调该 provider 模型的 limit.context。
Workaround(用户侧)
- 将自定义渠道模型的
limit.context改为实测真实值(如 350,000)以强制裁剪生效; - 放弃 fork 续接,改用「全新空白会话 + 读交接文档」。
已排除
- 非用户侧修改所致(升级已清除全部本地改动,进程均运行官方 3.11.2);非数据库损坏(tool part 完整);非会话体积本身(新空白小会话同样出现过网关层失败,另属中转不稳定问题,与本回归区分)。
关联:顺序型 tool call ID 复用导致的工具块渲染丢失/错位见 #456(独立缺陷)。
附:原始 model-io 记录因日志轮转已被清理,上文引用数字为轮转前捕获;可按最短复现重建。
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the tool-result continuation, cold-recovery continuation, and fork-first-request reconstruction paths, then compare them with the active-state request path. Use the model-io records and the reported long-session reproductions to verify whether the same context-trimming budget is applied; done means reconstructed requests are trimmed consistently and the described upstream context-limit 400 is recognized appropriately.
Written by the indexing model from the issue text.
Assessment
- Domain
- ai, api
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100