bug: empty assistant placeholders are replayed to the provider and permanently break the chat

当 Open WebUI 的聊天历史里残留一条“空助手占位消息”( content: "" 、 done: false 、无 output 、无 tool_calls )时,后端重建历史会把它当作正常 assistant 消息重放给上游,严格的 OpenAI 兼容提供商会因此拒绝本次及之后所有请求,

快速结论:当 Open WebUI 的聊天历史里残留一条“空助手占位消息”(content: ""、done: false、无 output、无 tool_calls)时,后端重建历史会把它当作正常 assistant 消息重放给上游,严格的 OpenAI 兼容提供商会因此拒绝本次及之后所有请求,导致该会话被永久卡死。优先排查历史链路上是否被写入过空助手节点。

适用环境:Open WebUI v0.9.5(Docker,官方 ghcr.io/open-webui/open-webui:main 镜像,Docker Desktop);上游 dev 分支 5d9a09a88a9094ebfcd249340be3aaee544b34d0 复现相同行为;macOS Sonoma 14(Darwin 25.4.0);使用 OpenAI 兼容提供商(非 Ollama 侧问题,Ollama 版本不适用)。

最快修复方案:Issue 中报告者明确指出这是历史重建路径的缺陷,临时绕过办法是改走 LiteLLM 代理接入上游,让 LiteLLM 侧处理空 assistant 消息。数据库层面的修复(直接编辑 webui.db,从 chat.history.messages 删除空助手消息并修补相邻节点的 childrenIds / currentId)属于极端自救手段,并不适合普通终端用户。

注意事项:Issue 中 #23176 曾于 2026-04-14 关闭,但同样的空占位持久化行为在 v0.9.5 和上游 dev 上依然存在,说明该关闭并未真正阻止空行被写入或被上游重放。#29169 相关改动在最新 dev(7e6c8d7b6)上测试时,空 assistant 消息仍会被发送给上游;虽然报告者测试的三个提供商的四个模型都没有拒绝,但并不代表严格校验的提供商会放行。LiteLLM 绕过方式仅在 Issue 中被提及,未提供详细配置验证。

问题场景

在 Open WebUI(Docker 部署,v0.9.5)中与本地 Ollama 模型对话时,如果在模型开始输出任何文本之前点击 Stop,该条被中断的回复会以空内容(无错误状态)保存为 assistant 消息。之后把该会话切换到某个 OpenAI 兼容连接下的模型并继续发送消息时,后端在重建聊天历史时会沿着 currentId 的父链走,把这条空占位消息作为 {"role": "assistant", "content": ""} 重放给上游提供商。严格的 OpenAI 兼容上游(服务端 schema 拒绝空 content 且无 tool_calls 的 assistant 消息)会直接拒绝该请求。由于空占位每次都会被重放,会话中后续每一次补全请求都会以同样方式失败,聊天实际上被永久破坏。

报错原文

bug: empty assistant placeholders are replayed to the provider and permanently break the chat

the message at position 15 with role 'assistant' must not be empty

原因分析

最可能的原因在聊天历史重建路径(load_messages_from_db() 相关代码路径):后端在组装发送给上游的消息数组时,只过滤掉带有 error 状态的空 assistant 消息,而没有过滤掉“无可用载荷”的 assistant 消息——即 content 为空、没有 output items、也没有 tool_calls 的节点。被中断或孤立产生的 assistant 占位节点会持久化进 chat.history.messages,并在每次重建历史时被重新纳入 payload。因为该节点位于 currentId 行走的父链上,从更早轮次重新生成也无法绕过它。Issue 交叉引用的 #23176 描述了同一类空占位(前端观察角度),本次 Issue 是其后端后果;#22327 涉及同一 load_messages_from_db() 路径但属于不同 bug 类别,仅说明该历史重建路径测试覆盖不足。

环境排查

  • 确认 Open WebUI 版本,Issue 中确认受影响版本为 v0.9.5,上游 dev 于 5d9a09a88a9094ebfcd249340be3aaee544b34d0 亦复现;后续在 dev(7e6c8d7b6)复测时空消息仍被发送。
  • 确认部署方式:Docker,官方 ghcr.io/open-webui/open-webui:main 镜像。
  • 确认宿主操作系统:macOS Sonoma 14(Darwin 25.4.0),Docker Desktop。
  • 确认上游提供商类型:问题出现在 OpenAI 兼容提供商,与 Ollama 自身版本无关(Issue 中标注 Ollama 版本不适用)。
  • 检查会话数据库 webui.db 中对应 chat 的 chat.history.messages,重点找 content 为空且无 tool_calls、无 output 的 assistant 节点,以及其相邻节点的 childrenIds / currentId 指向。
  • 浏览器无影响,症状在服务端(发往上游的 payload),与浏览器类型无关。

解决步骤

  1. 先确认症状模式:该会话切到严格 OpenAI 兼容提供商后,是否每一次新提交都以同样的“position N with role ‘assistant’ must not be empty”报错失败,且从更早轮次重新生成也无济于事。这是空占位被反复重放的典型特征。
  2. 临时绕过(可优先尝试):按 Issue 中报告者的建议,改经 LiteLLM 接入该上游,由 LiteLLM 侧承担消息适配,避免严格 schema 直接拒绝空 assistant 消息。注意这是绕过而非修复。
  3. 数据库层面自救(仅限能直接访问数据的自托管场景):定位 webui.db,在对应 chat 的 chat.history.messages 中移除空 assistant 占位消息,并同步修补相邻节点的 childrenIds 与 currentId,使父链不再经过该节点。操作前务必备份数据库。Issue 明确指出这不是普通终端用户可以使用的恢复路径。
  4. 跟踪代码修复:本 Issue 相关改动见 PR #29169。但在最新 dev(7e6c8d7b6)上,空 assistant 消息仍会被发送给上游;报告者测试的三个提供商的四个模型未拒绝,因此该 PR 是否彻底解决严格提供商的拒绝行为尚无定论。
  5. 如果正在评估是否升级:由于 #23176 关闭后问题仍在 v0.9.5 与上游 dev 上出现,不要仅凭该 Issue 已关闭判断问题已修复,应以实际使用的严格上游是否仍报错为准。

验证方法

复现路径:在与本地 Ollama 模型的新会话中,在模型输出任何文本之前点击 Stop,使该回复以空内容保存;然后把会话切换到 OpenAI 兼容连接下的模型,再发送一条消息。如果后端仍把空 assistant 作为 {"role": "assistant", "content": ""} 发送,严格提供商会再次以“must not be empty”拒绝。要验证问题是否被修复,应确认该空占位不再出现在发往上游的 payload 中,并确认后续请求能够正常获得补全响应;仅凭某个提供商未报错不足以下结论,因为部分提供商并不会拒绝空 assistant 消息。

参考来源

open-webui/open-webui #25083

相关:#23176、#22327、#22882、#24157、#24714;相关 PR:#29169

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 27282

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注