👁 94 次阅读
Open WebUI 为何会把思考标签重新注入历史消息?
来源链接:https://github.com/open-webui/open-webui/issues/23339现象描述
在 open-webui/open-webui 的 issue #23339 中,报告者描述了一个与推理模型(reasoning model)多轮对话相关的渲染故障。核心现象是:Open WebUI 会从存储的output 条目重建对话历史,并可能把推理/思考内容(例如 思考标签)重新注入到下一轮的 messages 中,注入路径为 convert_output_to_messages(..., raw=True)。
对于部分供应商与模型,这些标签一旦出现在历史中,模型就会开始模仿并放大它们——产生多个、嵌套或变体的思考标签。报告者给出的截图与解释指出:
> The reason is: if you pass the thought process to the LLM with a thinking tag as the main text, the LLM will attempt to mimic it and also output a fake thinking tag.
由此引发的后果是前端推理解析与渲染被破坏:思考块泄漏到普通文本、出现重复块、markdown 格式异常或产生其它伪影(artifacts)。
需要说明的是,该 issue 当前状态为 open,没有关闭时间,也没有关闭原因,标签为空。截至最后一条评论日期 2026-09-07,共有 44 条实质评论,反应合计 0。由于 issue 仍处于开放状态,官方尚未合入框架层的方案,下文讨论的缓解手段均来自社区侧。
背景原因
要理解这个故障,需要看清 Open WebUI 在“界面展示的内容”和“模型实际看到的内容”之间做了一次耦合。 据 issue 讨论中 ShirasawaSama 于 2026-04-02 的说明,Open WebUI 会把结构化的output 转换回下一轮给 LLM 的 messages,且常常是带着 raw=true 完成的。这使得“what the UI shows”和“what the model sees”紧密耦合。其直接后果包括:上游的一处小改动、或者某个供应商泄漏出 思考标签,都可能污染提示词(pollute prompts)、增加 token 成本、并重新注入敏感的工具数据(re-inject sensitive tool data)。
visig9 于 2026-04-04 补充了一个供应商官方的多轮对话要求,引用了 Gemma 4 模型卡中的内容:
> 3. Multi-Turn Conversations
> – No Thinking Content in History: In multi-turn conversations, the historical model output should only include the final response. Thoughts from previous model turns must not be added before the next user turn begins.
也就是说,某些模型(如 Gemma 4)在官方层面就要求:多轮对话的历史模型输出只应包含最终回复,前序轮次的思考内容不得插入到下一轮用户消息之前。这与 Open WebUI 当前把推理块拼回历史的做法直接冲突。
报告者在 issue 的“Relevant code”部分点出了两处关键代码位置:
backend/open_webui/utils/middleware.py:process_messages_with_output()使用了convert_output_to_messages(..., raw=True)backend/open_webui/utils/misc.py:convert_output_to_messages(..., raw=True)会把推理以成对思考标签的形式追加
messages 内容被贴回了思考标签。
排查或量化方法
针对这一机制,issue 讨论中出现了若干可用于定位或量化的切入点。 定位触发链路:报告者已经明确指出convert_output_to_messages(..., raw=True) 是重新注入的源头,因此可以从 process_messages_with_output() 的调用入手,检查传入的 raw 参数是否为 True。
观察模型放大行为:报告者的两张截图展示了思考块的内容被并入正常文本区,以及模型输出伪造的思考标签的情形。判断是否命中该故障,可以关注历史中是否已有 思考标签,以及模型后续输出是否出现嵌套、变体或伪造的思考标签。
从供应商兼容性角度量化:visig9 的做法是把供应商的官方多轮对话规范作为判据。如果所用模型(例如 Gemma 4)的模型卡明确要求历史中不得包含思考内容,那么当前行为就构成规格违反。
社区提出的两种缓解方向:tq1008 于 2026-06-12 总结了两条思路——一是在存入对话历史前剥离思考标签块,这是最干净的修法,但需要修改 convert_output_to_messages;二是使用 raw=False。第二条在原文中未展开完整写法,据 issue 讨论属于可尝试的方向。
插件侧的自动修复:Classic298 在 2026-05-26 发布了一个 userspace 过滤器(Filter)插件,其用途是:为推理模型在轮内与跨轮保留 reasoning_content,修复多轮工具调用场景下供应商以 "reasoning_content is missing in assistant tool call message at index N" 拒绝下一次请求、或静默丢失模型内容的问题。该插件位于其插件仓库 keep-reasoning-content 目录。
完整解决方案(含必要代码)
由于 issue 仍为 open,官方尚未在框架层合入方案,以下内容均来自社区在讨论中给出的处理路径。 方案一:以过滤器插件保留 reasoning_content(社区实践) Classic298 在 2026-05-26 的评论给出如下说明: > # Userspace filter that preserves reasoning_content for reasoning models (within and across turns) > > A Filter plugin for Admin Panel → Functions that fixes the multi-turn tool-call case where the provider rejects the next request with"reasoning_content is missing in assistant tool call message at index N", or silently loses the model’
插件的获取位置在讨论中有明确记录:gaby 于 2026-06-24 询问能否贴出过滤器,Classic298 回复“its on my plugin repo”;随后 gaby 回复“Thanks, found it. https://github.com/Classic298/open-webui-plugins/tree/main/keep-reasoning-content”。安装位置为 Admin Panel → Functions。
需要说明的是,该插件针对的是“保留 reasoning_content”这一目标,与报告者“默认不再注入推理”的诉求方向不同,二者解决的问题并不完全重合。据 issue 讨论,该插件是在官方方案缺位情况下的可用替代。
方案二:把思考内容放入独立字段(社区建议,未落地)
nekomiya-hinata 于 2026-04-04 提出了一个与开关互补的思路:不要把推理块用 思考标签拼进原始的 content 字符串,而是把思考内容放进消息载荷中一个专门的、独立字段(可配置或固定,例如 reasoning 或 reasoning_content)。这样做可以从结构上避免标签拼接带来的提示词污染与模型模仿。该建议在讨论中被 ShirasawaSama 于 2026-04-08 引用并继续讨论。
方案三:raw=False 与历史落库前剥离标签
tq1008 于 2026-06-12 提出的两条缓解手段:
1. Strip thinking blocks before storing in chat history —— 最干净的修复,但需要改动 convert_output_to_messages。
2. Use raw=False —— 原文在此处截断,具体用法据 issue 讨论属于待验证方向。
报告者提出的开关设计
报告者在 “Alternatives Considered” 中给出了一个模型级开关的设计意图,例如 reinject_reasoning / include_reasoning_in_history:
- Default: OFF
- When OFF: keep tool call structure, but do not add reasoning/thinking blocks back into
messages. - When ON: preserve current behavior for users/providers that require it.
参数或对比表格
下表汇总 issue 讨论中出现的各类处理路径及其定位。表中内容均据 issue 讨论整理,官方尚未确认任何一项为最终方案。| 路径 | 关键机制 | 提出者/时间 | 状态 |
| userspace 过滤器插件 | 保留推理模型的 reasoning_content,修复工具调用场景下的 "reasoning_content is missing in assistant tool call message at index N" | Classic298,2026-05-26 | 已发布,位于其插件仓库 keep-reasoning-content 目录 |
| 独立字段方案 | 把思考内容放入 reasoning / reasoning_content 等独立字段,而非拼进 content 字符串 | nekomiya-hinata,2026-04-04 | 建议,未落地 |
| 落库前剥离标签 | 在存入对话历史前剥离思考标签块,需改动 convert_output_to_messages | tq1008,2026-06-12 | 社区建议 |
raw=False | 调整转换调用参数 | tq1008,2026-06-12 | 社区建议,原文未完整展开 |
| 模型级开关 | 默认 OFF,关闭时不把推理块加回 messages | 报告者 | 期望行为,未实现 |
常见错误排查
错误一:把该问题当成纯前端渲染缺陷。 报告者的分析指向convert_output_to_messages(..., raw=True) 与 process_messages_with_output(),问题的根源在于进入下一轮 messages 的内容,而非界面绘制。仅调整前端解析无法阻断模型的模仿行为。
错误二:在历史中保留思考标签后再期望模型不模仿。 报告者明确写道,把带 思考标签的思考过程作为主文本传给 LLM,模型会尝试模仿并输出伪造的思考标签,形成嵌套与变体。
错误三:忽略供应商的多轮对话规范。 visig9 引用的 Gemma 4 模型卡要求历史模型输出只包含最终回复,前序思考内容不得插入下一轮用户消息之前。忽略这条规范会在这些模型上持续触发故障。
错误四:把社区的保留推理插件当成“关闭注入”的等价物。 Classic298 的过滤器目标是保留 reasoning_content 以修复工具调用报错,与报告者“默认不注入推理”的方向相反。两者不可混用为同一件事。
错误五:把该条目当作已有结论的议题。 该 issue 当前状态为 open,无关闭时间、无关闭原因,事实文件中也没有维护者给出的实现结论;把它当成已有定论的议题来引用,与当前状态不符。
实践建议
优先确认故障是否命中同一条代码路径。 检查process_messages_with_output() 是否以 raw=True 调用了 convert_output_to_messages,这是报告者给出的直接定位线索。
对供应商规范做前置校验。 如果所用模型(如 Gemma 4)的模型卡明确要求历史中不含思考内容,应把这作为必须满足的条件,而不是依赖模型自行忽略。
在官方方案缺位期间采用社区过滤器。 Classic298 的插件已发布并给出安装入口(Admin Panel → Functions),且提供了明确的适用症状("reasoning_content is missing in assistant tool call message at index N" 报错或模型内容静默丢失)。安装前应确认其目标与自身需求是否一致。
关注结构化字段方案的讨论进展。 nekomiya-hinata 提出的独立字段方案(reasoning / reasoning_content)试图从结构上分离推理与正文,避免标签污染,是讨论中较为根本的方向,但目前尚无落地记录。
不要把 issue 的当前状态误读为已解决。 该 issue 保持 open,社区方案属于临时缓解,长期修复仍取决于上游对 convert_output_to_messages(..., raw=True) 行为的调整。
FAQ
Q1:这个问题的根因在哪段代码? A:据报告者在 issue 中列出的相关代码,backend/open_webui/utils/middleware.py 的 process_messages_with_output() 使用了 convert_output_to_messages(..., raw=True);backend/open_webui/utils/misc.py 的 convert_output_to_messages(..., raw=True) 会把推理以成对思考标签的形式追加。
Q2:为什么模型会输出伪造的思考标签?
A:报告者解释为,如果把带有思考标签的思考过程作为主文本传给 LLM,模型会尝试模仿它,从而也输出伪造的思考标签,进而产生多个、嵌套或变体的思考标签,最终破坏前端的推理解析与渲染。
Q3:Gemma 4 对这个行为有什么要求?
A:据 visig9 于 2026-04-04 引用的 Gemma 4 模型卡,多轮对话中历史模型输出只应包含最终回复,前序轮次的思考内容不得插入到下一轮用户消息开始之前。
Q4:社区目前有可直接使用的修复吗?
A:Classic298 发布了一个 userspace 过滤器插件,用于为推理模型在轮内与跨轮保留 reasoning_content,地址为 https://github.com/Classic298/open-webui-plugins/tree/main/keep-reasoning-content,安装位置为 Admin Panel → Functions。该插件解决的是工具调用场景下的 reasoning_content 缺失问题,与“默认不注入推理”的诉求方向不同。
Q5:能否通过调整 convert_output_to_messages 的参数规避?
A:tq1008 于 2026-06-12 提出两条思路:一是在存入对话历史前剥离思考标签块(需改动 convert_output_to_messages,被认为是最干净的修法),二是使用 raw=False。后者在原文中未完整展开,具体效果据 issue 讨论仍属待验证。
Q6:这个 issue 已经关闭或修复了吗?
A:没有。该 issue 当前状态为 open,无关闭时间、无关闭原因、无标签。截至最后一条评论日期 2026-09-07 共 44 条实质评论,讨论仍在持续,官方尚未在框架层合入方案。
🔗 相关阅读(你可能还想看)
📄 Outlook 节点创建草稿添加附件为何报 UnableToDeserializePostBody?
📄 Flowise 点击市场或聊天页为何整页白屏?
📄 如何解决 Azure AI Search 向量存储的 $filter 递归深度超限错误?
📄 如何解决 shadcn/ui Sidebar 与 Header 重叠的问题?
📄 Next.js 开发服务器为何会 JavaScript heap out of memory?
📄 Nuxt 偶发 Cannot read properties of undefined 报错是怎么回事
📄 如何解决「Visual Studio Code for ipad」相关的问题?
📄 如何在AutoGen中实现企业级代理治理的加密行动收据(AAR)?
📄 记忆内容未清洗直接注入系统提示词,如何防范间接提示注入?
crewAI LiteAgent 将记忆内容直接拼接到系统提示词,存在间接提示注入风险。社区提出分层防御方案,但 iss