LLM text chunks are streamed as message_file when assistant message has files

当同一条 assistant 消息既有附件文件、又在流式输出 LLM 文本时,Dify 后端会把文本分片误标成 message_file 事件,导致前端把回答文本当成文件事件丢弃或渲染异常。优先排查流式响应里文本分片的 event 类型是不是变成了 message_file 。

快速结论:当同一条 assistant 消息既有附件文件、又在流式输出 LLM 文本时,Dify 后端会把文本分片误标成 message_file 事件,导致前端把回答文本当成文件事件丢弃或渲染异常。优先排查流式响应里文本分片的 event 类型是不是变成了 message_file。

适用环境:Dify 自托管源码部署(Self Hosted / Source),跟踪 upstream main 分支;Issue 明确引用的提交为 b96ea94505e3abe00daba69faa8bba9bcf9deb0a,评论中复现相关代码位置基于 86af36429da4c5433621520e704ed9dff7be869a。Issue 未提供操作系统、Python、CUDA、显卡等环境信息。

最快修复方案:暂无确认的一步修复方案。Issue 只确认了问题仍存在于 main,并指出曾被关闭的 PR #31414 的思路(让 LLM 文本分片无条件使用 StreamEvent.MESSAGE)方向正确,但没有合并为已验证修复;已合并的 PR #32019 只是收窄了返回 MESSAGE_FILE 的条件。

注意事项:Issue 中提到的改法均为代码级推断或未合并方案,改动前务必评估对 QueueMessageFileEvent(文件元数据本身仍应以 message_file 流式发送)的影响,避免把真正的文件事件也一并改掉;同时需自行补充单元测试验证文本分片与文件事件是否正确分离。

问题场景

在 Dify 自托管源码部署中,使用会流式返回 assistant 回复、且同一条 assistant 消息还会产生附件的聊天/补全流程(例如工作流或工具节点在 LLM token 流式输出之前或期间发出 QueueMessageFileEvent)。此时 LLM 的文本分片会以文件事件类型下发,前端可能把回答文本当作文件事件处理,造成流式回答文本丢失或显示不正确。

报错原文

LLM text chunks are streamed as message_file when assistant message has files

实际表现是文本分片的 SSE 字段为 event: message_file,而预期应为 event: message;文件元数据则继续通过 QueueMessageFileEvent 以 event: message_file 单独发送。

原因分析

最可能的原因在流式管线的处理链路:

  • MessageCycleManager.message_file_to_stream_response() 处理文件事件时,会把消息 id 缓存进 _message_has_file,并正常发出 message_file 事件。
  • 之后处理 QueueLLMChunkEvent 时,EasyUIBasedGenerateTaskPipeline._process_stream_response() 会调用 MessageCycleManager.get_message_event_type(message_id=...) 预计算事件类型,并复用到 LLM 文本分片上。
  • get_message_event_type() 在消息 id 存在于 _message_has_file,或数据库中已存在该消息的 assistant MessageFile 时,会返回 StreamEvent.MESSAGE_FILE;于是文本分片被传给 message_to_stream_response(..., event_type=StreamEvent.MESSAGE_FILE),而非 StreamEvent.MESSAGE。

评论补充说明:PR #32019 合并后为 get_message_event_type() 增加了 .where(MessageFile.belongs_to == "assistant") 过滤,收窄了返回 MESSAGE_FILE 的条件,但 _precomputed_event_type 的缓存逻辑仍然存在。只要首个 QueueLLMChunkEvent 处理前数据库里已有该消息的 assistant MessageFile 记录,缓存的事件类型就会是 MESSAGE_FILE,后续所有文本分片都会被误标。本质原因是文本分片与文件元数据属于不同语义的事件类型,文本分片的类型不应取决于该消息是否带文件。

环境排查

  • 确认部署方式为 Self Hosted(Source),并核对当前代码是否仍在 upstream main 上,相关提交可对照 b96ea94505e3abe00daba69faa8bba9bcf9deb0a 与 86af36429da4c5433621520e704ed9dff7be869a。
  • 检查 api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py 的 _process_stream_response() 是否仍存在对 QueueLLMChunkEvent 的事件类型预计算/缓存(Issue 指出约在 L348–L358 附近)。
  • 检查 api/core/app/task_pipeline/message_cycle_manager.py 中 get_message_event_type() 的返回逻辑,以及是否包含 PR #32019 引入的 belongs_to == "assistant" 过滤。
  • Issue 未提供 Python、CUDA、PyTorch、显卡等版本信息,无需据此排除问题。

解决步骤

  1. 先复现并抓取 SSE 响应,确认同一条 assistant 消息触发文件事件之后,后续文本分片的 event 是否变为 message_file。
  2. 对照当前代码,确认 _process_stream_response() 中 QueueLLMChunkEvent 是否复用了 get_message_event_type() 返回的事件类型(即 _precomputed_event_type 缓存路径仍生效)。
  3. 确认该消息在首个 LLM 分片处理前是否已存在 assistant MessageFile 记录或已被写入 _message_has_file,这是触发误标的前提条件。
  4. 可优先尝试按 PR #31414 的思路修正:让 QueueLLMChunkEvent 的文本分片无条件使用 StreamEvent.MESSAGE,不再依赖消息是否附带文件;QueueMessageFileEvent 仍单独以 message_file 发送。注意该 PR 已被关闭,此方案属于 Issue 讨论中的方向性建议,尚未验证合并。
  5. 若暂不改动核心逻辑,可先绕开触发条件(避免同一条 assistant 消息在文本流式期间同时产生附件),但 Issue 未验证该规避方式,属推测性做法。

验证方法

修改后重新运行同一流程:同一 assistant 消息既有附件又流式返回文本时,文本分片应显示为 event: message,文件元数据仍为 event: message_file;前端能正常渲染完整回答文本,流式内容不再被丢弃。建议同时补充单元测试覆盖“消息带 assistant 文件时 LLM 分片仍为 message”的场景。

参考来源

langgenius/dify #36990

相关讨论中提及的 PR:#31414、#32019;相关 Issue/PR:#32420、#32242(均为 Issue 评论引用,未逐一核实内容)。

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 32664

发表回复

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