zai-org / zai-org/feedback

[Bug] 3.11.2 从持久化状态重建的请求(工具结果续接 / fork 首轮)未按上游真实上下文裁剪:openai-compatible 上游 400,会话卡死且 fork 恢复失效

Open
#524 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

priority: P2
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" 等字段误读为线上格式——那是日志器记录的序列化前选项对象(本报告最初亦曾误判,特此标注)。

修复建议

  1. 重建类请求(工具结果续接 / 冷恢复续接 / fork 首轮)与活状态请求走同一条上下文裁剪管线(按 provider 声明的 limit.context 预算裁剪);
  2. 放宽上游 400 错误体中「上下文超出」的识别(当前因措辞不匹配而显示为通用错误且不可重试,用户无从自救);
  3. 长期:可考虑在收到上游「上下文超出」400 时自动下调该 provider 模型的 limit.context。

Workaround(用户侧)

  1. 将自定义渠道模型的 limit.context 改为实测真实值(如 350,000)以强制裁剪生效;
  2. 放弃 fork 续接,改用「全新空白会话 + 读交接文档」。

已排除

  • 非用户侧修改所致(升级已清除全部本地改动,进程均运行官方 3.11.2);非数据库损坏(tool part 完整);非会话体积本身(新空白小会话同样出现过网关层失败,另属中转不稳定问题,与本回归区分)。

关联:顺序型 tool call ID 复用导致的工具块渲染丢失/错位见 #456(独立缺陷)。

:原始 model-io 记录因日志轮转已被清理,上文引用数字为轮转前捕获;可按最短复现重建。

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.