连接关了不等于回答完成。先看是否收到协议定义的完成事件,再区分客户端解析、代理缓冲或超时、服务端失败。已经展示的半段文字应保留为“未完成”,重试结果不要直接追加到旧内容。
适用前提
本文以 OpenAI Responses 的 HTTP SSE 为例。Chat Completions 和第三方兼容接口的事件格式可能不同,不能混用解析器;代码只消费本地合成事件,不连接任何模型。
先识别当前流的协议
Responses 的文字增量可见 response.output_text.delta,正常完成事件为 response.completed,错误也可能以事件形式出现。不要拿 Chat 的 choices 增量读取规则套到 Responses 上,更不能只看到 HTTP 200 就把业务状态标成成功。若使用 SDK,让 SDK 负责 SSE 分帧;自行解析时,网络读取块与完整事件不是一一对应。
官方依据:来源 1
将生成过程拆成明确状态
建议在应用中区分等待、接收、完成、失败和中断。官方事件参考还区分 response.failed 与 response.incomplete;收到它们时应检查对应原因,不能当普通完成。某些高级流程存在额外状态变化,下面只示范不启用 steering 等功能的普通文字请求;遇到未处理的终态应显式报告,不猜测成功。
官方依据:来源 2
先用本地事件验证 UI,而不是重复烧额度
保存为 stream-state.mjs 后运行 node stream-state.mjs。两个合成用例都含文字增量,但只有一个结束于完成事件;预期分别输出 completed 与 interrupted。真实项目还要把 SDK 异常接到失败分支,并为输出项分别建缓冲区。
function consume(events) {
let text = '', status = 'receiving';
for (const event of events) {
if (event.type === 'response.output_text.delta') text += event.delta;
if (event.type === 'response.completed') status = 'completed';
if (['response.failed', 'response.incomplete', 'error'].includes(event.type)) {
status = 'failed'; break;
}
}
return {text, status: status === 'receiving' ? 'interrupted' : status};
}
const delta = {type:'response.output_text.delta', delta:'这是一段演示'};
console.assert(consume([delta]).status === 'interrupted');
console.assert(consume([delta,{type:'response.completed'}]).status === 'completed');
console.log(consume([delta,{type:'response.completed'}]).status);
console.log(consume([delta]).status);用三层证据定位中断位置
客户端层:查看是否在 JSON 尚不完整时就解析、是否取消了请求、是否把某一种事件当成所有事件。代理层:对照服务端收到事件的时间与前端展示时间;若后端持续收到而前端成批出现,再检查自己控制的代理是否缓冲、是否有空闲超时。服务端层:记录请求 ID、最后事件类型与终态原因。这里是排查顺序,不是已确认某个商家或 CDN 有问题。
- 先用本地夹具复现解析逻辑,不包含任何 Key。
- 在受控测试环境对比有无自建代理的路径;不得绕过组织必需的网络或数据控制。
- 每次只调整一个超时或缓冲参数,并保留原配置以便恢复。
恢复时防止重复内容与重复动作
把首次输出标为未完成,给每次尝试单独编号。只有确认请求可安全重放,才在预算与次数上限内重试;新回答放到新尝试中,不拼到旧字符串后。若流里触发过发邮件、支付等动作,先查询动作状态并按业务幂等键去重。对已获授权且可检索的保存响应,可以依据供应商文档查询状态,但不能假定所有中转都支持断点续传。
2026-10-21 阅读下列官方资料。操作步骤为本站编辑归纳,代码与数字为本地合成示例;未调用付费模型,不是服务商实测报告,也不构成接口或性能承诺。