- 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)
4.3 KiB
异步上下文压缩插件:当前问题与处理状态总结
这份文档详细梳理了我们在处理 async_context_compression(异步上下文压缩插件)时,遭遇的“幽灵截断”问题的根本原因,以及我们目前的解决进度。
1. 根本原因:两种截然不同的“世界观”(数据序列化差异)
在我们之前的排查中,我曾错误地认为:outlet(后置处理阶段)拿到的 body["messages"] 是由于截断导致的残缺数据。
但根据您提供的本地运行日志,您是对的,body['messages'] 确实包含了完整的对话历史。
那么为什么长度会产生 inlet 看到 27 条,而 outlet 只看到 8 条 这种巨大的差异?
原因在于,OpenWebUI 的管道在进入大模型前和从大模型返回后,使用了两种完全不同的消息格式:
视图 A:Inlet 阶段(原生 API 展开视图)
- 特点:严格遵循 OpenAI 函数调用规范。
- 状态:每一次工具调用、工具返回,都被视为一条独立的 message。
- 例子:一个包含了复杂搜索的对话。
- User: 帮我查一下天气(1条)
- Assistant: 发起 tool_call(1条)
- Tool: 返回 JSON 结果(1条)
- ...多次往复...
- **最终总计:27 条。**我们的压缩算法(trim)是基于这个 27 条的坐标系来计算保留多少条的。
视图 B:Outlet 阶段(UI HTML 折叠视图)
- 特点:专为前端渲染优化的紧凑视图。
- 状态:OpenWebUI 在调用完模型后,为了让前端显示出那个好看的、可折叠的工具调用卡片,强行把中间所有的 Tool 交互过程,用
<details type="tool_calls">...</details>的 HTML 代码包裹起来,塞进了一个role: assistant的content字符串里! - 例子:同样的对话。
- User: 帮我查一下天气(1条)
- Assistant:
<details>包含了好多次工具调用和结果的代码</details> 今天天气很好...(1条) - 最终总计:8 条。
💥 灾难发生点:
原本的插件逻辑假定 inlet 和 outlet 共享同一个坐标系。
- 在
inlet时,系统计算出:“我需要把前 10 条消息生成摘要,保留后 17 条”。 - 系统把“生成前10条摘要”的任务转入后台异步执行。
- 后台任务在
outlet阶段被触发,此时它拿到的消息数组变成了视图 B(总共只有 8 条)。 - 算法试图在只有 8 条消息的数组里,把“前 10 条消息”砍掉并替换为 1 条摘要。
- 结果就是:数组索引越界/坐标彻底错乱,触发报错,并且可能将最新的有效消息当成旧消息删掉(过度压缩)。
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 完全对齐。
至此,带有复杂工具调用的长轮次对话也能安全地进行背景自动压缩,不再有任何截断和强制删减的风险!