Node.js SDK toText() silently resolves when an SSE stream reports an error

在使用 Dify Node.js SDK( dify-client )的 toText() 处理 SSE 流时,如果服务端以 HTTP 200 返回一个 event: "error" 的错误载荷, toText() 不会抛出异常,而是静默 resolve:错误流返回空字符串,错误前的部分文本则被当作

快速结论:在使用 Dify Node.js SDK(dify-client)的 toText() 处理 SSE 流时,如果服务端以 HTTP 200 返回一个 event: "error" 的错误载荷,toText() 不会抛出异常,而是静默 resolve:错误流返回空字符串,错误前的部分文本则被当作成功结果。此时应优先检查流中是否出现 event.data.event === 'error',而不是只看 HTTP 状态码。

适用环境:Dify 自托管(源码方式);Node.js SDK dify-client 3.1.0,基于 main 分支提交 69e9ca4e01ce05da52255939330fbcec454dce23(2026 年 10 月 16 日);Node.js 24.21.0。Issue 使用本地回环 HTTP 夹具配合 SDK 的真实 fetch 传输复现,未部署 Dify 服务端,也未调用模型或外部服务。

最快修复方案:暂无确认的一步修复方案。Issue 中经源码确认的临时做法是:不要调用 toText(),改为直接迭代流,并在 event.data.event === 'error' 时自行抛出 APIError(携带 statusCode、responseBody 和 stream.requestId)。该做法来自源码分析与建议,未经实际执行验证,可优先尝试。

注意事项:判断错误的字段是解析后数据中的 event.data.event,不是迭代器外层包装的 event.event(后者来自 SSE 的 event: 行,本例夹具并未发送)。原始迭代与 toText() 会消费同一个迭代器,不能先 toText() 再遍历,否则流已被消耗。此问题不同于非 2xx HTTP 响应的处理(#43968),也不同于 text_chunk/message_replace 的文本收集(#44177)。

问题场景

用户以源码方式构建 sdks/nodejs-client(在该目录执行开发构建 vp pack),然后用 ChatClient.createChatMessage 发起 response_mode: 'streaming' 的聊天请求,并通过 stream.toText() 获取最终文本。当 SSE 流在 HTTP 200 下返回 event: "error" 的错误载荷(例如速率限制错误,含 code、status: 429、message)时,toText() 静默正常返回,错误没有被暴露给调用方。

报错原文

Node.js SDK toText() silently resolves when an SSE stream reports an error

预期行为是抛出一个 APIError,且满足:statusCode === 429、requestId === 'req-stream'、responseBody 等于错误载荷。实际结果为 resolved(空流得到空字符串,部分文本流得到已输出的片段文本)。

原因分析

根据 Issue 中维护者对源码的检查,toText() 只追加从流中提取的文本,不检查错误载荷。因此纯错误流贡献的是空字符串,而错误出现在文本之后的流会保留那段部分文本,看起来像成功完成。该结论来自源码检查,并非已执行的复现实验结果。

另一个容易混淆的点:错误判别字段位于 event.data.event,而迭代器外层的 event.event 由 SSE 的 event: 行填充;该夹具没有发送 event: 行,因此不能用外层字段判断。原始迭代会保留解析后的 JSON 载荷。

环境排查

  • 确认使用的 SDK 来源与版本:dify-client 3.1.0,源码基于 main 分支提交 69e9ca4e01ce05da52255939330fbcec454dce23。
  • 确认 Node.js 版本:Issue 环境为 Node.js 24.21.0。
  • 确认是否为自托管源码部署场景;若涉及服务端错误载荷,核对返回体的 status 与 message 字段是否与 SDk 预期一致。
  • 确认已包含此前相关的 SSE 空白处理修复(#44083 已在测试基线中)。
  • Issue 未提供 Python、CUDA、PyTorch 或显卡信息,本问题与这些依赖无关,无需排查。

解决步骤

  1. 在 sdks/nodejs-client 目录按仓库开发流程构建(Issue 中为 vp pack),生成 dist。
  2. 不要对聊天流调用 toText(),改为直接迭代 createChatMessage 返回的流对象。
  3. 在迭代过程中判断 event.data.event === 'error';命中时抛出 APIError(可优先尝试的临时方案),构造参数可参考:错误载荷中的 message 作为消息,附加 { statusCode: payload.status, responseBody: payload, requestId: stream.requestId }。
  4. 用 Issue 提供的回环 HTTP 夹具思路做自测:分别模拟“只有错误”和“部分文本后接错误”两种流,确认两种情况都会以 APIError 拒绝,且 statusCode、requestId、responseBody 正确。
  5. 长期修复需在 SDK 内处理错误事件(与 #44177 涉及的文本收集逻辑衔接),是否合入以官方仓库后续变更为准。

验证方法

运行自测脚本后,观察输出中两个场景(empty 与 partial)的 result 均为 rejected,isAPIError 为 true,statusCode 为 429,requestId 为 req-stream,且 responseBody 与错误载荷一致。若仍得到 resolved,说明错误事件未被正确处理。

参考来源

langgenius/dify #44198

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29541

发表回复

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