Skip to content

fix(stream): preserve Chatflow terminal events and completion - #165

Open
SamizuHM wants to merge 1 commit into
imfangs:mainfrom
audit-llm:fix/chatflow-stream-completion
Open

fix(stream): preserve Chatflow terminal events and completion#165
SamizuHM wants to merge 1 commit into
imfangs:mainfrom
audit-llm:fix/chatflow-stream-completion

Conversation

@SamizuHM

@SamizuHM SamizuHM commented Jul 30, 2026

Copy link
Copy Markdown

Problem

Chatflow streams end with two terminal events, message_end and workflow_finished. Dify's streaming guide says they arrive in that order:

message_end then workflow_finished (both arrive, in that order) for Chatflow apps

The current SDK treats the first terminal event as the end of the stream: it dispatches the event, then breaks out of the read loop and discards the rest of the SSE response. In the documented order that is harmless, because workflow_finished really is the last event. In real deployments the order is not guaranteed. When a Dify server sends workflow_finished first and message_end second, the SDK stops at workflow_finished and silently drops message_end.

Chatflow 流以 message_endworkflow_finished 两个终止事件结尾。Dify 官方文档定义它们的到达顺序为:先 message_end,后 workflow_finished

当前 SDK 把遇到的第一个终止事件当作流的终点:分发该事件后立刻跳出读取循环,丢弃 SSE 响应中剩余的内容。在文档顺序下这没有问题,因为 workflow_finished 确实是最后一个事件;但真实部署不保证顺序。当 Dify 服务器先发 workflow_finished、再发 message_end 时,SDK 会在 workflow_finished 处停止读取,message_end 被静默丢弃。

Why the code ended up this way

This is not a freshly invented design decision; it reflects three rounds of changes in this repo, none of which solved the underlying order problem:

Each iteration chose one event as the single boundary, so each iteration is wrong for the other order. The two events are both terminal and their order is environmental, not contractual. Building the read loop around “stop at the first terminal event” cannot work for both orderings.

现状不是一次性的设计决策,而是本仓库三轮改动叠加的结果,没有一轮真正解决顺序问题:

每一轮都只挑一个事件作为边界,所以每一轮在另一种顺序下都是错的。这两个事件都是终止事件,而它们的顺序取决于环境、不构成协议契约。把读取循环建立在“遇到第一个终止事件就停”之上,无法同时兼容两种顺序。

Real-stream observation

A probe against a real Chatflow produced 343 events ending with:

... -> workflow_finished (event 342) -> message_end (event 343)

The reverse of the documented order. With the current code, event 343 is never read.

对真实 Chatflow 做了一次探针,收到 343 个事件,结尾为:

... -> workflow_finished(第 342 个)-> message_end(第 343 个)

与文档顺序相反。在当前代码下,第 343 个事件永远不会被读取。

Solution

  • Make the Chatflow read loop order-independent: track message_end and workflow_finished per stream, and stop reading only after both have been dispatched. If the connection reaches EOF first, EOF ends the read normally.

  • Separate error handling from normal completion: a Dify error event or transport failure ends the stream through onError / onException and does not invoke the normal completion callback.

  • Wire the normal completion callback into the other streaming entry points (chat, completion, workflow, workflow events, knowledge pipeline) so all streams share the same lifecycle contract.

  • 让 Chatflow 读取循环与事件顺序无关:按流分别跟踪 message_endworkflow_finished,两个事件都分发后才停止读取;若连接先到达 EOF,则以 EOF 正常结束读取。

  • 把错误处理与正常完成分开:Dify error 事件或传输层异常通过 onError / onException 结束流,不触发正常完成回调。

  • 把正常完成回调接入其余流式入口(chat、completion、workflow、workflow events、knowledge pipeline),让所有流共享同一生命周期契约。

Behavior after this change

Stream outcome Event callbacks onStreamComplete
Chatflow, both terminal events, any order both delivered yes
Chatflow, one terminal event, then EOF delivered event is dispatched yes, EOF fallback
Dify error event onError no
transport failure onException no

onStreamComplete means the SDK has finished reading a non-error response; it does not assert that the business operation succeeded.

流结果 事件回调 onStreamComplete
Chatflow 两个终止事件都到达(任意顺序) 两者都交付
Chatflow 只到其中一个终止事件,随后 EOF 已到达的事件照常分发 是,EOF 兜底
Dify error 事件 onError
传输层异常 onException

onStreamComplete 表示 SDK 已读完非错误响应,不代表业务操作成功。

Tests

In-memory SSE tests cover documented order, reverse order, EOF before both terminal events, error events not triggering completion, and completion on the remaining streaming entry points. All pass with mvn -Dtest=ChatflowStreamTerminalEventTest,PipelineStreamCompletionTest,WorkflowEventsStreamCompletionTest test.

内存 SSE 测试覆盖文档顺序、反序、两个终止事件到达前 EOF、error 事件不触发完成,以及其余流式入口的正常完成回调。mvn -Dtest=ChatflowStreamTerminalEventTest,PipelineStreamCompletionTest,WorkflowEventsStreamCompletionTest test 全部通过。

@SamizuHM
SamizuHM force-pushed the fix/chatflow-stream-completion branch from 46a6ff8 to 9c76fa8 Compare July 30, 2026 18:46
@SamizuHM SamizuHM changed the title fix(chatflow): preserve message end and stream completion fix(stream): preserve Chatflow terminal events and completion Jul 30, 2026
@SamizuHM
SamizuHM force-pushed the fix/chatflow-stream-completion branch from 86fc23c to 4f3beb0 Compare July 30, 2026 20:07
@SamizuHM
SamizuHM marked this pull request as ready for review July 31, 2026 05:23
Copilot AI review requested due to automatic review settings July 31, 2026 05:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The job was not started because the account is locked due to a billing issue.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The job was not started because the account is locked due to a billing issue.

@SamizuHM
SamizuHM marked this pull request as draft July 31, 2026 05:37
@SamizuHM
SamizuHM force-pushed the fix/chatflow-stream-completion branch from 4f3beb0 to 00d825d Compare July 31, 2026 08:45
@SamizuHM
SamizuHM marked this pull request as ready for review August 11, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants