Files
Fu-Jie_openwebui-extensions/plugins/debug/async_context_compression/ISSUE_EXPLANATION.md
fujie cd95b5ff69 fix(async-context-compression): reverse-unfolding to prevent progress drift
- Reconstruct native tool-calling sequences using reverse-unfolding mechanism
- Strictly use atomic grouping for safe native tool output trimming
- Add comprehensive test coverage for unfolding logic and issue drafts
- READMEs and docs synced (v1.4.1)
2026-03-11 03:54:40 +08:00

4.3 KiB
Raw Blame History

异步上下文压缩插件:当前问题与处理状态总结

这份文档详细梳理了我们在处理 async_context_compression(异步上下文压缩插件)时,遭遇的“幽灵截断”问题的根本原因,以及我们目前的解决进度。

1. 根本原因:两种截然不同的“世界观”(数据序列化差异)

在我们之前的排查中,我曾错误地认为:outlet(后置处理阶段)拿到的 body["messages"] 是由于截断导致的残缺数据。 但根据您提供的本地运行日志,您是对的,body['messages'] 确实包含了完整的对话历史

那么为什么长度会产生 inlet 看到 27 条,而 outlet 只看到 8 条 这种巨大的差异?

原因在于OpenWebUI 的管道在进入大模型前和从大模型返回后,使用了两种完全不同的消息格式

视图 AInlet 阶段(原生 API 展开视图)

  • 特点:严格遵循 OpenAI 函数调用规范。
  • 状态:每一次工具调用、工具返回,都被视为一条独立的 message。
  • 例子:一个包含了复杂搜索的对话。
    • User: 帮我查一下天气1条
    • Assistant: 发起 tool_call1条
    • Tool: 返回 JSON 结果1条
    • ...多次往复...
    • **最终总计27 条。**我们的压缩算法trim是基于这个 27 条的坐标系来计算保留多少条的。

视图 BOutlet 阶段UI HTML 折叠视图)

  • 特点:专为前端渲染优化的紧凑视图。
  • 状态OpenWebUI 在调用完模型后,为了让前端显示出那个好看的、可折叠的工具调用卡片,强行把中间所有的 Tool 交互过程,用 <details type="tool_calls">...</details> 的 HTML 代码包裹起来,塞进了一个 role: assistantcontent 字符串里!
  • 例子:同样的对话。
    • User: 帮我查一下天气1条
    • Assistant: <details>包含了好多次工具调用和结果的代码</details> 今天天气很好...1条
    • 最终总计8 条。

💥 灾难发生点: 原本的插件逻辑假定 inletoutlet 共享同一个坐标系。

  1. inlet 时,系统计算出:“我需要把前 10 条消息生成摘要,保留后 17 条”。
  2. 系统把“生成前10条摘要”的任务转入后台异步执行。
  3. 后台任务在 outlet 阶段被触发,此时它拿到的消息数组变成了视图 B总共只有 8 条)。
  4. 算法试图在只有 8 条消息的数组里,把“前 10 条消息”砍掉并替换为 1 条摘要。
  5. 结果就是:数组索引越界/坐标彻底错乱,触发报错,并且可能将最新的有效消息当成旧消息删掉(过度压缩)。

2. 目前已解决的问题 ( Done)

为了立刻制止这种因为“坐标系错位”导致的数据破坏我们已经落实了热修复Local v1.4.0

添加了“折叠视图”的探针防御:

  • 我写了一个函数 _is_compact_tool_details_view
  • 现在,当后台触发生成摘要时,系统会自动扫描 outlet 传来的 messages。只要发现里面包含 <details type="tool_calls"> 这种带有 HTML 折叠标签的痕迹,就会立刻终止并跳过当前的摘要生成任务。
  • 收益彻底杜绝了因数组错位而引发的任务报错和强制裁切。UI 崩溃与历史丢失问题得到遏制。

3. 当前已解决的遗留问题 ( Done: 逆向展开修复)

之前因为跳过生成而引入的新限制:包含工具调用的长轮次对话,无法自动生成“历史摘要” 的问题,现已彻底解决。

最终实施的技术方案:

我们通过源码分析发现OpenWebUI 在进入 inlet 时会执行 convert_output_to_messages 还原工具调用链。因此,我们在插件的 outlet 阶段引入了相同的 逆向展开 (Deflation/Unfolding) 机制 _unfold_messages

现在,当后台任务拿到 outlet 传来的折叠视图时,不会再选择“跳过”。而是自动提取出潜藏在消息对象体内部的原生 output 字段,并将其重新展开为展开视图(比如将 8 条假象重新还原为真实的 27 条底层数据),使得它的坐标系与 inlet 完全对齐。

至此,带有复杂工具调用的长轮次对话也能安全地进行背景自动压缩,不再有任何截断和强制删减的风险!