zai-org / zai-org/feedback

[Bug] 本地压缩估算90.4万token、上游实际计数113万;context_exceeded且retryable=false后仍循环重发88次,压缩失败无历史裁剪降级,fork无法解除超窗(3.10.1 / OpenAI-compatible)

Open
#415 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

类别

对话 / Agent 交互(auto-compact / 上下文压缩)

涉及框架

ZCode Agent,OpenAI-compatible provider

严重程度

阻塞使用 · Blocking

复现频率

必现 · Always(该超长会话触发压缩时)

问题描述

先说明排除项:本机 ZCode 3.10.1(内部版本 0.16.5)的 resources/glm/zcode.cjs 已内置 #93 的修复,OpenAI-compatible 流式请求会携带 stream_options: { include_usage: true },因此本问题不是 #93 所述 usage 缺失。

一个超长会话(第 202 轮、2503 条 message、8638 条 part;message + part 序列化记录合计约 14.2 MB)触发自动压缩(querySource=compact)后,客户端记录的待压缩历史估算为 903,733 / 903,788 token,但上游对实际压缩请求的计数约为 1,129,914 / 1,129,945 token,超过模型明确返回的 1,048,576 token 上限,因而被 HTTP 400 拒绝:

The input (1129914 tokens) is longer than the model's context length (1048576 tokens).
The input (1129945 tokens) is longer than the model's context length (1048576 tokens).

因此,现有证据并不支持“网关实际窗口小于 90.4 万”。相反,上游窗口约为 104.86 万;关键问题是:客户端的 preCompactTokenCount 比上游实际输入少算约 22.6 万 token(约 25%)。可能未计入或低估了系统提示、压缩摘要指令、工具定义、请求封装,或客户端与上游 tokenizer 存在差异。无论具体来源是哪一项,压缩预算没有按最终请求体做足够的安全校验,导致客户端判断可发送、上游实际判定超窗。

随后又暴露出两个恢复问题:

  1. retryable=false 后仍循环新建压缩请求:母会话在约 17 分钟内连续发起 88 个压缩请求,均为同一 queryId、不同 requestId、attempt=1,全部以 context_exceeded 失败;这不是单个请求的常规 retry,而像是外层 compact 调度反复重新入队,未继承连续失败计数或未触发熔断。
  2. 失败后没有观察到能解除超窗的历史减量降级:客户端代码存在对旧工具结果做 microcompaction 的机制,但本次事故中未观察到 context_exceeded 后按最终预算裁剪/分块历史;后续请求仍以近似规模失败。
  3. fork 不能解除超窗:fork 会话为近全量克隆(2493 条 message,母会话 2503 条),第一份压缩输入仍为 1,129,954 token,大于 1,048,576;随后又连续失败 8 次。因此 fork 不是该故障的有效恢复手段。

复现步骤

  1. 使用 OpenAI-compatible 通道的模型,在同一会话持续对话,并产生大量工具输出,直到上下文接近模型窗口。
  2. 等待 auto/reactive compact 触发。
  3. 客户端依据 preCompactTokenCount 认为压缩输入尚可发送,但最终请求经上游 tokenizer 计数后超过窗口,收到 400 context_exceededretryable=false
  4. 观察客户端继续以同一 queryId 创建新的 compact 请求,每个请求又从 attempt=1 开始,且没有通过裁剪/分块使输入降到窗口内。
  5. (可选)fork 该会话;近全量历史被复制,新会话仍然超窗。

预期表现

  • 在发送压缩请求前,按最终请求组成(历史、系统提示、摘要指令、工具定义等)和保守 tokenizer 预算校验,并为 tokenizer 差异保留安全余量。
  • 一旦上游明确返回 context_exceeded,下一次必须减少输入:分块摘要、裁剪较早历史,或使用不会先携带整份超窗上下文的降级路径。
  • 尊重 retryable=false;同一 compact query 连续失败达到上限后停止重新入队,展示明确错误和可执行的恢复指引。
  • fork 时提示它会近全量复制上下文,不能作为超窗恢复方案;提供“仅保留最近消息/选定消息创建新会话”等真正减量的方式。

实际表现

  • 本地压缩前估算约 90.4 万 token,但上游实际收到约 113.0 万 token,超过 104.86 万窗口。
  • 母会话连续失败 88 次,持续约 17 分钟;fork 后又失败 8 次。
  • 没有观察到失败触发的有效历史裁剪/分块;用户只能另开全新会话并人工迁移必要上下文。

证据

  • 日志:~/.zcode/cli/log/zcode-2026-08-29.jsonl
    • 母会话 12:02:07Z–12:19:31Z:同一 queryId 连续 88 个 compact 请求;每个均为新 requestId、attempt=1、HTTP 400、reason=context_exceededretryable=false;12:19:33Z 用户取消。
    • 上游原始错误明确给出 1129914 > 10485761129945 > 1048576
    • fork 后原始错误为 1129954 > 1048576;12:20:43Z–12:22:09Z 又有 8 个 compact 请求失败,12:22:12Z 用户取消。
  • 会话库 compaction part:
    • cmp_0f61818e:auto,interrupted,preCompactTokenCount=903733
    • cmp_e05fd636:reactive,interrupted,preCompactTokenCount=903788
    • 同一会话 08-15 / 08-17 曾成功压缩,preCompact 分别为 298,906 / 230,091;其他会话 08-18 / 08-20 也曾在 5,851–6,648 成功,说明压缩机制并非始终失效,而是大输入的预算与失败恢复存在缺陷。
  • 8/29 统计:母会话 88 次、fork 8 次,共 96 次 context_exceeded;全天共有 108 条失败事件、107 个唯一 requestId(另含 3 次 auth_failed、6 次 timeout、3 条取消事件对应 2 个唯一 requestId)。
  • 本地配置当前声明 limit.context=1000000limit.output=384000,但该文件修改时间晚于事故,只能说明当前配置,不能据此反推 8/29 的历史根因;本 Issue 以日志中上游返回的 1,048,576 窗口为准。
  • 代码核对(本机 3.10.1):
    • OpenAI-compatible include_usage 修复已存在。
    • 存在 thresholdPercentOverride,默认 preflight-v1 阈值百分比为 100,并预留 reserve / buffer;但用户配置中未见公开的 compact threshold 入口。
    • 存在旧工具结果 microcompaction,故不能笼统表述为“完全没有减量机制”;本事故缺少的是 context_exceeded 后足以解除超窗的最终请求预算、裁剪/分块和熔断。

ZCode 版本

3.10.1.6272(CLI 内部版本 0.16.5)

设备 / 系统

Windows 11 家庭中文版 25H2,x64(客户端记录:win32 10.0.26200 x64;系统 Build 26200.9168)

模型渠道

OpenCode Zen 网关(baseURL opencode.ai/zen/go/v1),OpenAI-compatible,模型 deepseek-v4-pro。上游错误明确返回模型窗口为 1,048,576 token。

关联

  • #93(OpenAI-compatible usage 缺失;本机版本已包含该修复)
  • #114(同族问题:auto-compact 对 OpenAI-compatible 失效及阈值不可配置)
  • #348(上下文压缩可编排)
  • #412(compact 命令增强)

建议(Asks)

  1. 用最终请求组成做压缩预算,并为 tokenizer 差异设置保守安全余量;不要只依据 preCompactTokenCount 判断可发送。
  2. 记录并展示预算构成(历史、system、tools、压缩指令、预留输出),方便定位约 22.6 万 token 的差额来源。
  3. 收到 context_exceeded 后,下一次强制减量(裁剪/分块/分层摘要),禁止原规模重发。
  4. 外层 compact 调度继承连续失败次数并尊重 retryable=false;达到上限后熔断,避免每次重建请求都显示 attempt=1
  5. 提供真正减量的新会话迁移能力;不要把近全量 fork 或仍可能走同一路径的 /compact 当作已验证的恢复方案。
  6. 将 compact threshold / safety margin 暴露为可配置项,并给 OpenAI-compatible provider 提供合理默认值。

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 with resources/glm/zcode.cjs and the cited JSONL log, tracing compact scheduling, preflight token accounting, and context_exceeded handling. Done means the final request budget includes all components with safety margin, failures reduce input before retrying, and retryable=false stops repeated requeueing after a bounded number of attempts.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
ai, backend-api-design
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.