langchain-openai: Responses API streaming silently drops response.failed/error events. A failed stream is indistinguishable from a successfu

在使用 langchain-openai 的 Responses API 流式输出( use_responses_api=True 且 llm.stream(...) )时,若服务端返回 response.failed 或 error 事件,转换器会把这些终止事件直接丢弃,调用方拿到的结果和“成功完

快速结论:在使用 langchain-openai 的 Responses API 流式输出(use_responses_api=True 且 llm.stream(...))时,若服务端返回 response.failed 或 error 事件,转换器会把这些终止事件直接丢弃,调用方拿到的结果和“成功完成”完全一样。优先排查流式响应是否以终止事件结束,以及失败事件里是否带有 response.error。

适用环境:Issue 中确认可复现于 langchain-openai 1.1.12 与 1.4.0;使用 langchain_openai.ChatOpenAI 并开启 Responses API 流式调用;复现脚本基于 httpx.MockTransport,无需 API Key 与网络。其他 Python、操作系统、CUDA、显卡信息 Issue 未提及,不做推断。

最快修复方案:Issue 讨论中给出的修复方向是把 response.failed 加入 _convert_responses_chunk_to_generation_chunk 中的终止事件元组并补充独立的错误处理分支,但截至 Issue 讨论链未见已合并的确认修复,暂无确认的一步修复方案。升级到包含该修复的 langchain-openai 版本前,需自行在应用层对失败/缺失终止事件做兜底检测。

注意事项:仅把 response.failed 加进终止元组并不是完整修复:_construct_lc_result_from_responses_api 只在 response.error 非空时才抛错,而 ResponseFailedEvent.response.error 在 SDK 类型约束下允许为 None,此时流仍会“成功”结束。另外 SDK 的 Stream.__stream__ 只对 SSE 负载顶层的 error 键抛 APIError,而 response.failed 把 error 嵌在 response 之下,所以上游不会兜底。

问题场景

用户通过 langchain-openai 调用 OpenAI Responses API 的流式接口:构造 ChatOpenAI(model=..., use_responses_api=True),然后对 llm.stream("...") 或异步 astream 迭代消费。当服务端在生成过程中返回终止失败事件(response.failed,或裸的 error 事件),或者连接在流中途断开、完全没有终止事件时,调用方不会收到任何异常或失败信号,拿到的结果与正常完成无法区分。

这一现象在 with_structured_output 场景下会进一步表现为误导性报错,把服务端失败伪装成 schema 解析问题。

报错原文

langchain-openai: Responses API streaming silently drops response.failed/error events. A failed stream is indistinguishable from a successful completion.

Structured Output response does not have a 'parsed' field nor a 'refusal' field

原因分析

根因位于 langchain_openai/chat_models/base.py 的 _convert_responses_chunk_to_generation_chunk:其中 elif chunk.type in ("response.completed", "response.incomplete") 是唯一处理 Responses API 终止事件的分支,response.failed 和裸 error 事件都落到最后的 else 被丢弃并返回 None,不向调用方传递任何信号。

与此同时,_construct_lc_result_from_responses_api 虽然会在 response.error 被设置时抛 ValueError,但由于失败事件根本没走进这个分支,这条错误传播路径实际从未被触达。

此外,_stream_responses / _astream_responses 的迭代只是自然结束,不检查是否观测到过终止事件,因此一个完全没有终止事件的流(例如中途断连)同样被当作成功完成。

讨论中还指出两点容易被忽略的因素(属推断性分析):一是 Response.error 在 SDK 中为可选字段,失败事件可能不带 error 对象,因此只把 response.failed 并入终止元组并不能保证一定抛错;二是 SDK 层只对 SSE 顶层 error 键抛 APIError,而失败事件里的 error 嵌套在 response 下,上游不会兜底。

环境排查

  • 确认 langchain-openai 版本,Issue 中复现于 1.1.12 与 1.4.0。
  • 确认调用是否显式设置 use_responses_api=True,以及是否使用 stream / astream。
  • 确认是否使用 with_structured_output,该场景下失败会被表现为 parsed / refusal 字段缺失。
  • 确认失败事件中 response.error 是否存在,以及是否存在 response.failed 携带 error: null 或缺省 error 的情况。
  • 确认流是否以任意终止事件结束,排查是否存在连接中途断开导致无终止事件的情况。
  • Issue 未提供 Python、CUDA、PyTorch、显卡等信息,无需据此排查。

解决步骤

  1. 先用 Issue 中的最小复现脚本(基于 httpx.MockTransport,无需 API Key 与网络)确认本地行为:构造包含 response.failed、裸 error、以及无终止事件三种帧序列,观察 llm.stream(...) 是否抛出异常。
  2. 检查 _convert_responses_chunk_to_generation_chunk 中处理终止事件的 elif 分支,确认 response.failed 是否被包含;同时确认是否有针对 response.failed 无 error 对象的单独分支。
  3. 确认裸 error 事件是否被单独处理(该事件不含 response 对象,只有 code、message、param,无法复用终止响应分支)。
  4. 在 _stream_responses 与 _astream_responses 中增加“是否观测到终止事件”的标记,流结束时若未观测到则抛错,覆盖“中途断连”场景。
  5. 错误类型保持 ValueError,与非流式路径遇到带 error 响应时的行为一致,避免引入新的异常类型。
  6. 可优先尝试:将 response.failed 加入终止事件元组作为第一步验证,但需注意这不足以覆盖 error 为空的失败事件,需配合第 2、3 步的独立分支。
  7. 在应用层(升级前的临时兜底)对流结束做校验:若整个流未出现任何终止状态、或 metadata 中缺少预期完成标记,则视为失败并中断上层逻辑。

验证方法

套用修复后,运行 Issue 中的三个场景:带 response.failed(含 error)应抛出携带服务端 code/message 的 ValueError;带 response.failed 但 error 缺省或为 null 时应抛出指明该响应的错误而非静默完成;完全没有终止事件的流也应抛错。异步路径复用同一个转换器,需一并验证。此外运行 test_responses_stream.py 与 test_base.py 确认无回归(讨论中提到修复后为 231 passed)。

参考来源

langchain-ai/langchain #39039

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 25434

发表回复

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