🎉 新版站点已上线 — 新用户请在 v2.cloudmist.cloud 注册使用,本站教程均以新站地址为准 · 老用户账号已迁移完成,用原用户名/邮箱 + 原密码可直接登录 新站(登不上请在用户名后加 _82350),原站余额可继续在 api.cloudmist.cloud 用完,新站需重建令牌并改用新站 base_url
首页 › 教程 · 文章 › API 流式输出怎么接?SSE 分帧、文本组装与完成判断

API 流式输出怎么接?SSE 分帧、文本组装与完成判断

云岚API · 教程 · 更新于 2026-10-25

接收 API 流式输出,应先把网络字节还原成完整 SSE 事件,再按实际接口组装内容,最后检查协议定义的结束状态。收到文字、读到连接末尾,都不能单独证明生成成功。本文面向写应用的开发者;官方资料核对日期为 2026 年 10 月 24 日,配套演练只回放人工编写的数据,没有调用真实 API。

三层边界,先别混在一起

你拿到的东西 它代表什么 不能直接推断什么
网络读取返回的一块字节 本次读取获得的数据 一个字符、一个 JSON 或一个事件已完整
按 SSE 规则分出的事件 一段带事件类型和 data 的消息 模型整个响应已经结束
接口的完成状态 当前响应的生命周期结果 答案事实正确、工具任务全部完成

同一条事件可以跨多个网络块;一次读取也可能装着多个事件。不要对每个块直接调用 JSON 解析,再把异常一律归为服务端返回坏数据。尚未跑通普通请求时,先核对地址、协议与模型;已能收到流、但应用显示错乱或提前结束时,再沿下面两层检查。

第一层:先分出完整 SSE 事件

SSE 使用 UTF-8,空行结束一个事件。处理顺序应是增量解码、按行读取、累计字段、遇到空行才交给下一层;同时处理 LF、CRLF 和 CR 换行。多条 data: 要用换行拼接,冒号开头的注释不属于回答正文。流末尾没有完成分隔的事件应丢弃,不能靠“连接已经关闭”把它补成完整事件。WHATWG SSE 解析规则

例如下面是一个人工编写的事件,末尾空行是分隔符:

: 心跳注释
event: demo
data: 第一行
data: 第二行

它的事件名是 demo,data 是“第一行\n第二行”。data 可以是普通文本,SSE 本身不要求 JSON;只有具体 API 约定它是 JSON 时,才在分帧后解析。

这里的关键是保留跨块状态。中文可能被切在一个 UTF-8 字符的中间,分隔符也可能跨块;每块单独解码或简单 split("\n\n") 容易漏掉这些情况。已使用官方 SDK 时,先确认拿到的是 SDK 已解析的事件对象,避免重复做字节层解析。

第二层:按接口识别增量和结束

Chat Completions 与 Responses 都可使用 SSE,但正文位置和事件含义不同。OpenAI 的流式指南说明:Chat 的生成片段在 delta,它有时只有角色信息或为空,不能假设每次都有正文。流式响应指南

场景 文本怎么组装 完成怎么确认
Chat Completions 文本流 按 choice 分别追加 delta.content 看对应 choice 的 finish_reason,并处理该接口的流结束标记
Responses 文本流 按输出项/内容块归组,追加 response.output_text.delta 看响应级终态,不把某个文本块结束当整体完成

Chat 的官方 HTTP 示例以 data: [DONE] 结束;它是协议标记,不是 JSON。finish_reason 还需要区分正常停止、长度上限、工具调用等情况。不要把所有非空结束原因都显示成“回答完整”。Chat 流式参考

Responses 的 response.output_text.done 表示一块文本完成,response.completed 才是响应级完成事件;response.incomplete、response.failed 要分别处理。完整文本快照用于核对或替换对应缓冲区,不要再追加一遍,否则界面会重复显示。Responses 事件定义

也别给所有接口套 [DONE]。例如 DeepSeek 当前 Responses 文档明确使用 completed、incomplete、failed 终态事件,没有该消息。原生 Messages、Gemini 等协议应查各自文档,不能因为都叫“流式”就复用同一终态判断。DeepSeek Responses 流式说明

显示可以渐进,完成状态要单独保存

建议把“已显示的文本”和“响应状态”分开记录。下面是应用层设计示例,并非厂商统一状态码:

观察结果 应用怎么处理
收到正文增量,尚无终态 可以预览,保持生成中
收到已验证的正常终态 标记本次响应完成,再验收业务结果
收到 incomplete、failed 或错误 保留已有内容和原因,进入相应分支
连接结束却没有有效终态 标记中断或未确认,不伪造成功
收到未实现的事件类型 记录并按能力边界处理,不把它当正文

拒绝信息也应与普通答案区分。即使响应正常结束,输出仍可能是拒绝或需要工具执行;完成事件不是业务验收章。收到工具请求时转到工具执行与回传流程,需要把答案入库时再做JSON 与字段验收。

中断后保留协议、最后事件、结束原因、请求标识及可用用量。不要自动无限重发:原请求可能已消耗资源,工具是否执行也可能尚未确认。Codex 客户端报 stream disconnected 时,使用专门的分场景排查,本文不替代客户端诊断。

用离线演练检查自己的接收逻辑

下载 SSE 流式接收演练,解压后在该目录运行:

python3 replay.py
python3 -m unittest -v test_stream_lab.py

资源只使用 Python 内置模块,不读 Key 或环境变量,不连接服务器。它把合成数据切成不同大小的字节块,演示 SSE 分帧,以及限定 Responses 文本事件的组装与终态检查;最终输出也是预先写好的内容,不是模型回答。

先对照正常完成、缺完成事件、截断和失败样例,再故意移除最后一个空行,观察接收结果。验收重点是中文没有乱码、分片方式改变不影响事件、完整快照没有重复追加、未完成没有显示为成功。包内另有空白记录表,可记录你以后真实接入时的条件与结果。

它不是完整 SDK:没有 HTTP、自动重连、持久化、多模态或工具参数组装。id、retry 的重连行为也不在本地演练范围。云岚API的模型、分组及协议支持需分别验证,离线测试通过不能代替真实兼容性检查。

常见问题

HTTP 200,为什么仍不能算完成?

HTTP 状态先说明请求层面的结果;后续流仍可能中断或报告失败。需要继续处理事件及终态,随后检查答案是否完成你的任务。

一次读取就是一个 token 吗?

不能这样计数。读取块、事件和模型 token 是不同单位;切块方式也不是计费用量。需要核账时使用实际返回的用量与账单,不用块数量推算。

看到 output_text.done 就能停止读取吗?

不能仅据此停止,它只对应文本块。继续检查响应级终态;如果把同一文本的 done 快照再次追加到 delta 后面,还会造成重复。

SSE 断开后能从最后一个字继续吗?

不能默认支持。是否可恢复取决于服务端与客户端协议,SSE 的事件 ID 不自动提供模型生成续传保证。先保存状态并核对服务说明,不把重新发起请求当成原请求无损续接。

返回的是语音时,先区分音频字节与事件

本文的文本增量组装不能直接用于音频文件。语音接口可能直接返回音频字节,也可能把音频数据放在事件中;先核对所选输出方式,再决定解码、保存和播放流程。参见 OpenAI Speech 音频保存与完整性验收,不要把任意网络块直接当成完整音频。

事件已经收完,回答仍被截断怎么办?

完成接收与完成用户任务需要分别核对。如果已取得响应状态或结束原因,却发现文本缺段、JSON 未闭合,按API 输出截断的分类与补齐流程区分输出预算、工具调用和其他停止原因;不要只凭流关闭就把内容标记为合格。

Vercel AI SDK 的聊天界面应该接收哪一种流?

应用框架可能在上游事件之外定义自己的消息协议。使用 useChat 等前端组件时,继续看 Vercel AI SDK 的消息转换与前端流检查,分别确认上游模型适配器、自有服务端路由和浏览器消息传输;不能把提供方原始 SSE 直接当成框架的 UI 消息流。

注册后查看试用额度与领取条件

按当前模型列表选择服务,先核对令牌分组、接口协议与价格,再用自己的任务验证。

注册并查看试用条件 →已有账号?进入控制台创建令牌 · 调用报错先排查
注册、充值和调用均在云岚API 新业务站完成
相关内容
本站为云岚API 注册、登录与充值入口站点 · 模型与价格以控制台实时数据为准 · 更新于 2026-10-25
Claude、GPT、Gemini 等名称为各自权利人商标,本站为独立第三方 API 中转/聚合服务,与上述厂商无隶属或授权关系。