API 错误排查
流式响应中断排查
流式中断排查讲清 OpenAI-compatible API 中 streaming interrupted、stream、SSE、代理缓冲、超时、客户端取消、部分内容保存和 OfApp.cn 接入检查。
streaming interrupted 表示流式响应已经开始返回,但连接在完成事件、结束标记 或业务保存前断开。用户看到的现象通常是:答案写到一半停住、前端一直转圈、 终端没有收到结束事件、聊天记录里只保存了半段内容,或 SDK 抛出连接中断类 错误。
流式中断不等于模型一定坏了。它常常发生在网络、代理、浏览器、Serverless 平 台、SSE 解析、超时设置和服务端 5xx 的交界处。本文示例按 OfApp.cn 的 OpenAI 兼容入口组织;具体模型、流式事件、端点能力、额度和价格请以 OfApp.cn 控制台或官方文档为准。
| 项目 | 说明 |
|---|---|
| 阅读时间 | 约 7 分钟 |
| 更新时间 | 2026-07-03 |
| 适合读者 | 开发者、产品经理、AI 工具维护者、前端工程师、运维和正在接入聊天 UI 的团队 |
| 适合任务 | 排查 stream 中断、SSE 断连、代理缓冲、超时、客户端取消、半段内容保存和重试策略 |
streaming interrupted 是什么
Section titled “streaming interrupted 是什么”普通请求会在服务端生成完整结果后返回一个响应。流式请求会把结果拆成多个 事件或 chunk,客户端边接收边展示。OpenAI 官方文档说明,流式响应适合长输 出,因为应用可以在模型继续生成时先处理开头部分。
中断发生在“已经开始返回”和“业务确认完成”之间。它可能已经产生了可见文本,
但没有收到 response.completed、[DONE]、SDK 迭代结束或你自己定义的保存
成功信号。排查时要同时看两层:连接为什么断,以及已经收到的内容有没有被
正确保存。
常见触发场景
Section titled “常见触发场景”| 场景 | 典型表现 | 优先排查 |
|---|---|---|
| 网络抖动 | 偶发停在半句,刷新后恢复 | 客户端网络、移动网络、公司代理 |
| 代理缓冲 | 终端或网页等很久后一次性出现内容 | Nginx、CDN、平台网关、响应缓冲 |
| 超时过短 | 每次固定在 30 秒或 60 秒附近断开 | 浏览器、后端、代理、Serverless 超时 |
| 客户端取消 | 用户刷新、切页、关闭弹窗后断流 | AbortController、路由切换、组件卸载 |
| 事件解析错误 | 收到数据但 UI 不更新或报 JSON 错 | SSE 分帧、空片段、事件类型分支 |
| 服务端 5xx | 流开始后返回错误事件或连接关闭 | 请求 ID、状态码、服务端错误页 |
| 并发过高 | 多个 stream 同时失败 | 连接数、队列、限流和重试放大 |
先记录哪些证据
Section titled “先记录哪些证据”流式中断很难只靠一张截图判断。建议在日志里记录脱敏后的结构化信息:
| 字段 | 用途 | 注意事项 |
|---|---|---|
| 端点和模式 | 区分 /responses、/chat/completions、stream: true |
不要只写“调用 AI 失败” |
| 断开时间 | 判断是否命中固定超时 | 使用带时区时间 |
| 已接收长度 | 判断是没返回、返回一半还是接近结束 | 保存字符数或 chunk 数 |
| 结束信号 | 判断是否收到完成事件 | 记录 response.completed、[DONE] 或 SDK 正常结束 |
| 错误类型 | 区分网络、解析、5xx、取消 | 保留 SDK 原始类型名 |
| 请求 ID | 对齐服务商日志 | 如果响应头有 x-request-id,要保存 |
| 客户端 ID | 网络超时时也能对齐请求 | 可设置 X-Client-Request-Id |
| 部署链路 | 浏览器、后端、代理、CDN、平台 | 标出哪一层负责转发 stream |
OpenAI API Reference 建议生产环境记录请求 ID;在网络超时拿不到响应头时,也
可以给请求显式带上自己的 X-Client-Request-Id,便于排查请求是否到达。
最小 cURL 复现
Section titled “最小 cURL 复现”先绕开前端、业务后端、队列和页面状态。OfApp.cn 官方文档展示的 OpenAI 兼
容 Base URL 是 https://api.ofapp.cn/v1,并列出 /v1/responses、
/v1/chat/completions、/v1/images/generations 和 /v1/messages 等示
例端点。
调试流式文本时,可以先用 Chat Completions 的常见 OpenAI-compatible 写法:
export OPENAI_BASE_URL="https://api.ofapp.cn/v1"export OPENAI_API_KEY="sk-xxxx"export OPENAI_MODEL="请填入控制台可用模型"export CLIENT_REQUEST_ID="stream-check-$(date +%s)"
curl -N "$OPENAI_BASE_URL/chat/completions" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -H "X-Client-Request-Id: $CLIENT_REQUEST_ID" \ -d '{ "model": "'"$OPENAI_MODEL"'", "messages": [ { "role": "user", "content": "请用 6 个短句解释什么是流式输出。" } ], "stream": true }'看三件事:
| 结果 | 判断 |
|---|---|
| 终端逐块输出且正常结束 | API 侧基本可用,回到前端、代理或业务后端 |
| 终端一次性吐出全部内容 | 中间层可能发生缓冲,或当前链路没有按事件流展示 |
| 终端也中断 | 记录请求 ID、断开时间、状态码和已接收长度 |
如果非流式请求都失败,不要继续查 SSE。先回到 API Key、Base URL、模型名、 端点路径、权限、限流或 5xx 错误页。
Chat Completions 和 Responses 的差异
Section titled “Chat Completions 和 Responses 的差异”OpenAI 迁移文档说明,Chat Completions 流式返回增量 chunk,常见读取位置是
choices[0].delta.content;Responses 流式使用带 type 的 Server-Sent
Events,文本场景会出现类似 response.output_text.delta、
response.completed 和 error 的事件。
| 维度 | Chat Completions 流式 | Responses 流式 |
|---|---|---|
| 客户端读取 | 遍历 chunk | 按事件 type 分支 |
| 文本增量 | choices[0].delta.content |
response.output_text.delta |
| 完成信号 | SDK 迭代结束或结束标记 | response.completed |
| 错误处理 | 捕获迭代异常和错误对象 | 处理 error 事件与连接异常 |
| 迁移风险 | 旧客户端更常见 | UI 需要识别事件类型 |
不要把两个流式结构混在一起。前端如果按 Chat Completions 的 delta 去读
Responses 事件,可能会“收到数据但不显示”;反过来也一样。
JavaScript 保存部分内容示例
Section titled “JavaScript 保存部分内容示例”流式 UI 不应该只把片段写到屏幕。页面展示、完整保存和中断提示要分开处理。
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL,});
export async function runStreamingAnswer({ messages, onDelta, onInterrupted }) { let fullText = ""; let receivedChunks = 0;
try { const stream = await client.chat.completions.create({ model: process.env.OPENAI_MODEL, messages, stream: true, });
for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content ?? ""; if (!text) continue;
receivedChunks += 1; fullText += text; onDelta(text); }
return { status: "completed", text: fullText, receivedChunks }; } catch (error) { await onInterrupted({ partialText: fullText, receivedChunks, message: error.message, status: error.status || error.statusCode, });
return { status: "interrupted", text: fullText, receivedChunks }; }}这段代码的重点不是“自动续写”,而是保住已经收到的内容。中断后可以让用户选 择保留、重新生成、切到非流式,或基于已生成内容发起新的补写请求。
代理和部署平台怎么查
Section titled “代理和部署平台怎么查”很多线上流式问题不在模型,而在转发链路。你可以按“浏览器 → 你的后端 → 代理 / CDN → OpenAI-compatible API”逐层确认。
| 层级 | 检查点 | 现象 |
|---|---|---|
| 浏览器 | 页面是否刷新、组件是否卸载、Abort 是否触发 | 用户操作后立即断 |
| 前端请求 | 是否用 fetch 读取 stream,是否处理空片段 | 控制台报解析错误 |
| 业务后端 | 是否把上游 chunk 立即写出 | 后端收到,前端不动 |
| Nginx / 网关 | buffering、read timeout、idle timeout | 固定时间断或一次性输出 |
| CDN / 平台 | 是否支持长连接和 SSE | 本地可用,线上失败 |
| 上游 API | 是否返回错误事件、5xx 或请求 ID | curl 也中断 |
如果你控制 Nginx,可以检查是否需要关闭响应缓冲,并把超时设置调到能覆盖长 输出场景。不同平台配置方式不同,改动前要看平台文档和现网流量特征。
什么时候可以重试
Section titled “什么时候可以重试”流式请求的重试比普通请求更敏感,因为用户可能已经看到一半内容。
| 情况 | 是否自动重试 | 建议 |
|---|---|---|
| 还没收到任何内容 | 可以有限重试 | 1 到 2 次退避重试 |
| 已收到部分内容 | 谨慎 | 保存内容,让用户确认是否继续 |
| 用户主动取消 | 不重试 | 尊重取消动作 |
| 解析错误 | 不盲目重试 | 修客户端解析或事件分支 |
| 429 或并发过高 | 降速后再试 | 看 429 Rate Limit |
| 5xx 或网络抖动 | 可以有限重试 | 看 500 Server Error 错误排查 |
不要对同一段长输出做无上限重试。更稳的产品策略是:保存半段结果,提示“连接 中断,已保留已生成内容”,再给用户“继续补写”“重新生成”“切换非流式”的选项。
前端交互怎么设计
Section titled “前端交互怎么设计”流式输出的用户体验要覆盖成功和失败两种路径。
| 状态 | 页面应该做什么 |
|---|---|
| 连接中 | 显示正在生成,不要阻塞用户滚动和复制 |
| 收到片段 | 逐步渲染,同时写入完整 buffer |
| 空片段 | 忽略或记录,不把它当成错误 |
| 中断 | 保留已生成内容,显示可恢复操作 |
| 完成 | 标记消息已完成,保存结束信号和完整文本 |
| 重试 | 说明可能生成不同内容,避免静默覆盖 |
聊天产品还要区分“正在生成的临时消息”和“已经保存的历史消息”。如果每个 chunk 都直接写数据库,容易产生大量写入;如果只在完成时保存,又会在中断时丢失内 容。常见做法是前端保留实时 buffer,后端按时间或 chunk 数做节流保存。
OfApp.cn 接入检查
Section titled “OfApp.cn 接入检查”接入 OfApp.cn 时,把流式问题拆成三类更容易定位:
| 检查项 | 做法 |
|---|---|
| Base URL | 按官方文档使用 https://api.ofapp.cn/v1;如果工具会自动拼 /v1,按工具说明配置 |
| Key 来源 | 使用自己的 OfApp Key,不把真实 Key 写进前端、仓库、日志截图或公开提问 |
| 端点选择 | 先确认非流式 /responses 或 /chat/completions 可用,再测试 stream |
| 模型能力 | 模型名、流式事件、额度和价格以 OfApp.cn 控制台或官方文档为准 |
| 客户端 | Codex、SDK、网页和第三方客户端要分别确认 Base URL、Key 和超时 |
| 失败证据 | 提供脱敏请求时间、端点、状态码、请求 ID、是否 stream 和已接收长度 |
如果只是想快速体验聊天、写作、读文件或生图,可以先从 OfApp.cn 的天才游乐场 开始;如果要把能力接进自己的工具或项目,再回到 OpenAI-compatible API 做 最小请求和流式验证。
- OpenAI Streaming API responses:说明流式响应、SSE 和常见文本事件。
- OpenAI 迁移到 Responses API:说明 Chat Completions chunk 与 Responses typed events 的差异。
- OpenAI API 错误码指南:说明连接错误、5xx 和其他 API 错误类型。
- OpenAI API Reference:建议记录请求 ID,并可提供
X-Client-Request-Id。 - OfApp.cn API 直转文档:OfApp.cn 的 Base URL、Key、端点和可用范围请以官方文档与控制台为准。
- OfApp.cn 天才游乐场:普通用户可以从网页入口体验聊天、写作、读文件和生图。
流式中断后能从断点继续吗?
Section titled “流式中断后能从断点继续吗?”通常不能把底层连接原样续上。更现实的做法是保存已接收文本,再让用户选择基 于这段内容继续补写、重新生成,或切换到非流式请求。
为什么本地正常,线上就中断?
Section titled “为什么本地正常,线上就中断?”本地请求链路短,线上常常多了网关、Nginx、CDN、Serverless 平台、鉴权中间 件和浏览器路由。固定时间断开、多路 stream 同时失败、内容一次性出现,都指 向线上链路配置。
为什么终端一次性输出,不是逐字出现?
Section titled “为什么终端一次性输出,不是逐字出现?”可能是代理或终端链路发生缓冲,也可能是当前端点或客户端没有按事件流展示。
先用 curl -N 测试,再逐层去掉代理和业务封装。
已经收到部分内容,还要自动重试吗?
Section titled “已经收到部分内容,还要自动重试吗?”不建议静默自动重试。长文本重新生成可能和前半段不一致,也可能重复扣请求。 更好的交互是保存部分内容,并让用户决定继续补写还是重新生成。
stream 中断和 500 Server Error 怎么区分?
Section titled “stream 中断和 500 Server Error 怎么区分?”如果流开始后出现 5xx、错误事件或服务端请求 ID,按 5xx 排查;如果非流式稳 定可用而 stream 失败,重点查 SSE、代理、超时和客户端解析。
前端可以直接拿 OfApp Key 调流式接口吗?
Section titled “前端可以直接拿 OfApp Key 调流式接口吗?”不建议。真实 Key 应保存在服务端或本机安全配置中。浏览器前端应调用自己的后 端接口,由后端再请求 OfApp.cn 或其他 OpenAI-compatible API。
- OpenAI API 流式输出示例:查看 curl、JavaScript、Python 和前端展示。
- 错误重试示例:设计有限退避、熔断和失败队列。
- 500 Server Error 错误排查:处理中途 5xx 和请求 ID。
- 429 Rate Limit:排查并发、长输出和重试放大。
- Chat Completions API 示例:理解
messages、stream和 chunk。 - Responses API 示例:理解 typed events 和迁移注意事项。
- curl 示例:用终端验证 Base URL、响应头和流式输出。
流式中断要从“非流式是否可用”开始,而不是直接改前端。排查顺序是:最小请
求、curl -N、代理缓冲、超时设置、事件解析、部分内容保存、有限重试。接入
OfApp.cn 时,先确认 Base URL、Key、端点和模型可用,再验证当前链路是否能稳
定处理 stream。