Skip to content

[bug] compress 保护区过滤在完整性调整之后挖洞,导致 assistant tool-call 消息缺 reasoning_content(DeepSeek 思考模式 400) #684

Description

@LH1993-creator

环境

  • billion-context 0.1.100(npm 全局安装,proxy 模式)
  • 客户端:bili dsh@deepseek-ai/dsh@0.1.5-rc.1
  • 上游:https://api.deepseek.com/chat/completions(OpenAI 兼容协议)
  • 模型:deepseek-flash(DeepSeek-V41-Flash),reasoningEffort: high,思考模式开启
  • 系统:Windows
  • 会话标记:pfa-a723d224e63437ca
  • 日志:~/.local/state/billion-context/bili.log 第 15441–15445 行

现象

compress 之后的重放请求被上游直接拒绝:

{"error":{"message":"The `reasoning_content` in the thinking mode must be passed back to the API.","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}

日志原文:

[acp-proxy: compress requested 2 range(s): m00001–m00077, m00090–m00092]
[acp-proxy: [Compressed m00001–m00077, m00090–m00092 → 2 block(s), ~46753 tokens saved.
  Excluded 2 protected message(s) m00003, m00078 from compression range (recent/last-user zone).;
  Excluded 3 protected message(s) m00089, m00091, m00093 from compression range (recent/last-user zone).]]
[acp-loop] round 1: re-request view refreshed to post-compress fold
[acp-loop] round 1: proxy tool executed; re-requesting so the model sees the result
[acp-proxy: compress loop upstream error 400: {"error":{"message":"The `reasoning_content` in the thinking mode must be passed back to the API.", ...}}]

时间线:折叠完成 11:47:26.041 → 400 11:47:26.257间隔 216 ms,中间没有任何用户操作
同一轮折叠之前的请求是成功的([acp-usage] round 1 input=93330 cached=91136)。
所以触发者是折叠本身,不是客户端送进来的历史。

根因

applySingleRange() 的执行次序有问题:轮次完整性调整在前,保护区过滤在后

次序 代码位置(npm dist/index.js, v0.1.100) 作用
1 46021 applyPairBoundaryAdjustments() 保证轮次完整:把 reasoning ↔ tool-call ↔ tool-result 补齐成完整的一轮
2 4608246095 hitProtectedRaw 保护区过滤 把「最近 N 条 + 最新用户消息」内的消息从范围中间剔除
3 剔除之后没有再复检完整性

一个已经校验合格的连续范围,被挖掉中间几格之后就可能只剩半个轮次。

证据:会话快照

~/.local/share/billion-context/sessions/openai/api.deepseek.com_16ced886da89c98308353b78.json

该快照在报错后约 500 ms 落盘(savedAt = 2026-09-10T11:47:26.763Z),内容就是失败请求所用的视图。

压缩后活跃视图的最后 3 条:

# role contentType toolName toolCallId reasoningContent
13 system text 摘要块 acp_summary_b2
14 assistant tool-call pwsh call_01_1KCOFhwi1Eb3IQbsMedl8972
15 tool tool-result pwsh call_01_1KCOFhwi1Eb3IQbsMedl8972

注意 call_01_ 这个编号:同轮的第一个调用 call_00_… 连同该轮的 reasoning 一起被折叠了
只剩第 2 个调用孤零零地活着。

对照组(同一快照内未被折叠的轮次,结构正确):

[assistant reasoning] → [assistant tool-call call_00] → [assistant tool-call call_01] → [tool result ×2]

重建请求时 coreToOpenai() 会把连续的 assistant 消息合并成一条,于是:

  • 正常轮次 → {role:"assistant", tool_calls:[…], reasoning_content:"…"}
  • 被打洞的轮次 → {role:"assistant", tool_calls:[…]} ❌ → 上游 400

#539 / #540 的关系

机制同族。#539 是 acp-loop 重放时漏掉本轮新生成reasoning_content
src/loop/core.ts 的 signature 门控,已由 PR #540 修复)。
本 issue 是历史消息被折叠切半导致同一后果 —— 同属「re-request 送出的 assistant 消息
带 tool_calls 却没有 reasoning_content」。PR #540 新增的回归测试
tests/issue539-loop-reasoning.test.ts 只覆盖了「本轮新生成」这一路,没有覆盖折叠切半。

另一条独立的丢失路径(#651 / PR #667,0.1.98 起)

dropCompressReasoning()DEFAULT_COMPRESS_REASONING = { drop: true, threshold: 2048 }
会在 compress 工具调用闭合后,删除该轮 compress 之前的 reasoning 消息。

  • 0.1.96 中不存在该函数(grep -c dropCompressReasoning dist/index.js = 0)
  • 0.1.98 起出现

在思考类模型上这会稳定触发同一个 400。也就是说:即使本次的空洞问题侥幸没触发,
一个已闭合的 compress 轮次在下一个请求里也会独立复现同样的报错。

建议修复

  1. 把保护区过滤提到 applyPairBoundaryAdjustments() 之前(先挖洞、再补完整性);或者
  2. 挖洞之后再跑一次 applyPairBoundaryAdjustments(),把因空洞而残缺的轮次整体移出压缩集合
  3. 顺带:adjustBoundariesForReasoningPairs() 目前向后扩展只走一格(newEndIndex = j2 + 1),
    遇到「一轮多个工具调用」根本补不全,建议改为扩展到「该 assistant 轮次的所有 tool-call
    及其 tool-result 全部包住」
  4. dropCompressReasoning 建议按 provider 自动判定,思考类 provider 默认 drop: false

可能相关的已有工作

临时规避

billion-context.json

{
  "compress": {
    "reasoning": { "drop": false },
    "minCompressRangeChars": 20000
  }
}

仅缓解(关掉第二条路径 + 抬高压缩门槛让紧贴尾部的小范围被拒),不能根治

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions