必须按行解析sse流,只处理以“data: ”开头的行,提取其后内容并严格校验是否为独立完整的“[done]”;同时结合连接关闭信号双重确认,并适配不同平台(如azure)的结束标识差异。
![如何识别 sse 流中的特殊结束标记(如 [done]) 确保业务状态机正确关闭](https://img.php.cn/upload/article/001/242/473/178020354021382.png?x-oss-process=image/resize,p_40)
识别 SSE 流中的 [DONE] 结束标记,关键不在“找字符串”,而在于**按规范解析事件流结构**。直接用 includes('[DONE]') 容易误判——比如用户内容里真出现 [DONE],或服务端把 [DONE] 拆在两块数据里传过来,就会漏判或错判。
必须按行解析,且只处理 data: 行
SSE 协议规定:每条消息由若干字段行(如 data:、event:、id:)组成,字段行以换行符 \n 结尾,不同消息之间用空行(\n\n)分隔。只有以 data: 开头的行才携带实际载荷,其他行(如 event: ping)是控制信息,不能参与业务逻辑判断。
- 收到数据后,先用
\n切分成行,再逐行检查是否以data:开头(注意末尾有空格) - 对匹配的行,取
data:后面的内容(即line.slice(6)),这才是真正要解析的 payload - 不要对整段响应体做全局字符串搜索,避免跨行或嵌套干扰
区分合法 [DONE] 和普通文本内容
[DONE] 是服务端主动发送的结束哨兵,它必须满足两个条件才算有效:
- 它是一整行
data:的全部内容(前后无其他字符,包括空格) - 它出现在一个完整消息单元的
data:字段中,且该消息单元不包含其他字段(如event:)
例如,以下都是合法的结束标记:
data: [DONE]data: [DONE]
而这些不是:
data: 正在生成中... [DONE]data: {"status":"done"}
event: done\ndata: {}
结合连接关闭信号做双重确认
仅靠 [DONE] 不足以保证状态机安全退出。因为网络可能丢包,客户端可能没收到;也可能服务端因异常提前断连,根本来不及发 [DONE]。
- 前端监听
EventSource.onclose或 fetch +reader.read()返回{ done: true } - 后端在连接断开时(如 Node.js 的
req.on('close')或 Spring 的SseEmitter#complete())触发清理逻辑 - 状态机应在任一信号到达时进入“终止中”状态,并在确认最终输出完整、资源已释放后才标记为“已关闭”
注意 Azure 等兼容层的格式差异
不是所有服务端都严格遵循 OpenAI 风格的 data: [DONE]。Azure OpenAI 返回的是包裹结构,典型片段是:
这里没有 [DONE],而是靠 "finish_reason":"stop" 表示结束。所以业务代码需适配目标平台的协议约定,不能硬编码匹配 [DONE]。










