Intermittent /v1/responses stream closes during reasoning without a terminal event

当 Ollama 收到 POST /v1/responses 流式请求,且模型已开始输出 reasoning 增量后,流可能被干净地关闭,没有任何终止事件,核心报错为 Intermittent /v1/responses stream closes during reasoning without

快速结论:当 Ollama 收到 POST /v1/responses 流式请求,且模型已开始输出 reasoning 增量后,流可能被干净地关闭,没有任何终止事件,核心报错为 Intermittent /v1/responses stream closes during reasoning without a terminal event。优先排查服务端在流内出错时的错误路由,而不是模型或客户端超时。

适用环境:Issue 中确认的环境为 Ollama 0.31.1,Linux 主机,NVIDIA Blackwell GPU;模型 nemotron-3-super:120b-a12b-q4_K_M(架构 nemotron_h_moe,约 123.6B 参数,Q4_K_M 量化,上下文长度 262144),端点 POST /v1/responses,开启流式,reasoning 参数为 {“effort”:”medium”,”summary”:”auto”}。Issue 中未提供 Python、PyTorch、CUDA 的具体版本。

最快修复方案:暂无确认的一步修复方案。Issue 中定位到根因位于 middleware writer 的错误路由与缺少终止事件,属于服务端代码修复范围;未给出用户侧可验证的一步配置修改。

注意事项:Issue 讨论中提到的修复方向是服务端代码层面的改动,不应被当作已发布的用户可配置项;是否以及何时在正式版本中修复,Issue 未明确说明。重试只能临时绕过,因为同样的请求字节在不同采样结果下可能成功也可能失败。

问题场景

用户通过 Ollama 的 POST /v1/responses 端点发起流式 Responses 请求,使用启用了 reasoning(summary: auto)的 nemotron_h_moe 大模型,请求中包含大量多轮工具回放内容(Issue 复现请求约 133 个 input items、38 个 function tools、约 249 KB JSON、约 57K input tokens)。流在输出 response.reasoning_summary_text.delta 事件期间被关闭;相同请求立即重试可以成功,但同样的请求体在后续尝试中仍可能再次失败。用户最初通过 Hermes Agent 观察到该问题,但用直接 curl 重放即可复现,说明与客户端无关。

报错原文

event: response.reasoning_summary_text.delta
data: {"delta":"...","type":"response.reasoning_summary_text.delta",...}

<EOF>

Intermittent /v1/responses stream closes during reasoning without a terminal event

失败的流不会出现 response.reasoning_summary_text.done、response.output_item.done、response.function_call_arguments.done、response.completed、response.incomplete 或 response.failed 中的任何一个。curl 退出码为 0,HTTP 响应为 200,chunked text/event-stream,客户端看到的是正常关闭的流。

原因分析

根据 Issue 评论中的定位,根因在 middleware writer,而不是模型或解析器。生成过程在流中途失败时,服务端把错误信封放入响应通道,由 streamResponse 写入 c.Writer。此时响应已以 HTTP 200 提交,而 ResponsesWriter.Write 依据状态码选择错误路径还是内容路径:

code := w.ResponseWriter.Status()
if code != http.StatusOK {
    return w.writeError(data)
}
return w.writeResponse(data)

由于状态码是 200,错误信封被送进 writeResponse,并被反序列化为 api.ChatResponse。该结构没有 Error 字段,反序列化静默成功并得到 Done 为 false 的零值。ResponsesStreamConverter.Process 收到空响应后不发出任何事件,handler 直接返回,连接关闭,于是出现 reasoning 增量之后紧跟 EOF 的现象。评论中给出的验证为:

json.Unmarshal([]byte(`{"error":"boom"}`), &api.ChatResponse{})
-> err=nil, Done=false, Content="", Model=""

这同时解释了 response.reasoning_summary_text.done 缺失:该事件来自 processCompletion 中的 finishReasoning(),只有 Done 为 true 时才会触发。关于间歇性:工具调用参数是在 done=true 时集中校验的,请求字节虽然完全一致,但模型采样输出每次不同,因此一次能解析、下一次可能解析失败。较小的单工具请求在初始测试中稳定完成,与这一解释吻合。

评论还指出存在第二个缺口:openai/ 与 middleware/ 中均不存在 response.failed 和 response.incomplete,即使错误正确路由也没有终止事件可发,因此只修路由并不足够。另需注意,关联 Issue #10976 在评论中被判断为另一个问题(ChatHandler 在有工具时把 Think 提升为 true,且已有 #16758 跟进)。

环境排查

  • 确认 Ollama 版本是否为 0.31.1,并记录升级后是否仍复现。
  • 确认请求端点、是否启用流式、reasoning 参数(effort 与 summary)。
  • 确认请求规模:input items 数量、function tools 数量、JSON 体积与 input token 数,判断是否属于重回放形态。
  • 确认模型与量化格式(示例为 nemotron-3-super:120b-a12b-q4_K_M、Q4_K_M),以及显卡型号与驱动环境(Issue 中为 NVIDIA Blackwell,Linux)。
  • 确认 curl 退出码与 HTTP 状态码,排除客户端超时或网络中断;Issue 中失败流在数秒内关闭,curl 退出码为 0。
  • Issue 未提供 Python、PyTorch、CUDA 版本,如排查环境差异需自行记录,但不应视为本问题的直接因素。

解决步骤

  1. 先确认不是客户端或超时问题:用相同的请求文件直接通过 curl 重放,并检查退出码为 0、HTTP 状态为 200、流在 reasoning 增量后直接 EOF。Issue 中已用此方式在 Hermes 完全绕过的情况下复现。
  2. 用 SHA-256 校验多次请求体是否字节级一致,以排除请求差异;Issue 中已确认失败请求与成功重试完全一致。
  3. 对相同请求多次重放,分别记录成功(出现 response.completed)和失败(无终止事件)的比例。Issue 中示例为 20 次尝试内出现 completed 与 missing-terminal-event 各一次,最终验证为第 1-3 次完成、第 4 次失败。
  4. 根据需要可优先尝试:缩短或简化请求中的多轮工具回放与工具数量,观察失败率是否变化。Issue 中小型单工具请求稳定完成,但这是观察结果,未作为确认修复方案。
  5. 临时缓解可优先尝试:在客户端检测到 reasoning 增量后 EOF 且无终止事件时,对同一请求做有限次重试并记录失败次数;Issue 明确指出重试只绕过现象,不解决根因。
  6. 服务端根因处理:错误信封进入内容路径导致 Done 为零值、终止事件缺失,属于代码修复范围。可参考评论中定位的两处修改方向(修正错误路由、补充 response.failed / response.incomplete 终止事件),但 Issue 未提供可直接应用的补丁或确认已发布的版本。
  7. 若排查中看到与工具相关的 Think 行为变化,注意其属于关联但独立的问题(#10976 / #16758),不要与本问题混为一体。

验证方法

对同一请求重复重放,确认每次被接受的流式请求都以恰好一个结构化终止事件结束(response.completed、response.incomplete 或 response.failed)。失败时也应出现 response.failed 并带有可操作的错误信息,而不是在 response.reasoning_summary_text.delta 之后直接关闭。同时确认失败场景下不再出现 HTTP 200 加干净 EOF 的组合,并且 response.reasoning_summary_text.done、response.output_item.done 等收尾事件按预期出现。

参考来源

ollama/ollama #17118

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29744

发表回复

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