AI 流式输出断线后怎么处理:SSE 事件序号、重放与重复片段去重
页面上的回答刚显示到“订单已经进入……”就停住,浏览器控制台却只剩一条 SSE 连接断开的提示。AI 流式输出遇到这种情况,最危险的处理方式是立刻再发一次请求,然后把新收到的文字直接接在旧文本后面:网络抖动、代理重试或服务端重放都可能让半句话重复两遍。
稳定的做法是把流式响应当成有序事件,而不是一串无法定位的字符:先记录事件 ID 和序号,再按序追加;重连时只接收缺失事件,重复事件直接丢弃,最后还要核对完成态。
要点速览
- SSE 断线只说明传输链路中断,不代表模型任务一定失败。
sequence_number适合做顺序检查,事件 ID 适合做幂等去重。- 客户端不能凭已显示的字符猜断点,服务端要提供事件缓存或完整结果查询。
- 只有收到完成事件并核对最终文本,才能把消息标成成功。
为什么重试一次会得到重复答案
流式接口通常是一边生成、一边通过 Server-Sent Events(SSE)发送事件。客户端已经收到的内容可能只存在浏览器内存里,服务端却还在生成;也可能服务端已经完成,只是最后几个事件没有穿过反向代理。两种情况在客户端看起来都像“卡住了”,处理方式却不同。
Responses API 的流式事件带有事件类型、对象标识和序号。以文本增量为例,response.output_text.delta 表示一小段增量,response.output_text.done 表示文本部分已经收尾。把 delta 当作普通字符串拼接,既看不到中间缺口,也无法判断某一段是否已经处理过。
先把每个事件变成可核对的记录
先定义一条内部事件记录,不要让页面 DOM 成为唯一的状态来源。下面的结构足够支撑顺序检查、短时重放和刷新后恢复:
type StreamEvent struct {
RequestID string `json:"request_id"`
EventID string `json:"event_id"`
Seq int64 `json:"sequence_number"`
Kind string `json:"type"`
Delta string `json:"delta,omitempty"`
Done bool `json:"done,omitempty"`
}
收到事件时,先检查 RequestID 是否属于当前页面,再检查 Seq。如果序号比上次大 1,可以追加;如果等于上次,说明是重复投递;如果跳过了某个序号,就先不要把当前片段展示为最终文本,应进入补取流程。

序号和事件 ID 各自解决什么问题
序号回答“前后顺序是什么”,事件 ID 回答“这一条是否已经处理过”。两者不要混成一个字段:序号可能只在单次响应内有意义,事件 ID 则更适合写入去重表。服务端存储时可以使用 (request_id, event_id) 作为唯一键,客户端则保存当前最大序号和已完成的事件集合。
如果上游没有提供可靠序号,也不要从文本内容推测边界。可以让自己的流式网关为每个下游事件补一个单调递增序号,但要在网关重试时保持这组序号不变,否则同一片段会被当成新事件。
断线后应该重放缺口,而不是拼字符
最小的恢复流程是:先暂停页面追加;根据 request_id 查询服务端是否已经完成;未完成则带上“已收到的最大序号”请求事件重放;完成后再补一次完整结果核对。如果事件缓存已经过期,就只能重新发起一次请求,并使用新的请求编号,不能假装它从旧请求的位置继续。
func accept(e StreamEvent, state *State) bool {
if e.RequestID != state.RequestID || e.EventID == "" {
return false
}
if _, ok := state.Seen[e.EventID]; ok {
return false
}
if e.Seq != state.LastSeq+1 {
state.Missing = true
return false
}
state.Seen[e.EventID] = struct{}{}
state.LastSeq = e.Seq
state.Text += e.Delta
state.Done = e.Done
return true
}
这个函数故意把“缺序号”当成异常状态,而不是把当前 delta 硬塞进文本。生产代码还要限制 Seen 的大小,并在完成后清理短时缓存;长时间保留每个字符级事件,成本往往比保存最终文本更高。
重放、去重和完成态要一起验收
我更建议把恢复状态分成四种:receiving 表示正常接收,waiting_replay 表示缺口待补,completed 表示收到完成事件,failed 表示明确失败。页面刷新时先读取状态,再决定是继续收流、查询最终结果还是显示失败,不要只根据文本框里有没有字来判断。
一次可复现的核对可以这样做:模拟页面显示半句话后断开,让测试代理在序号 4 后断开,再把序号 4、5、6 重复发一次,随后补发序号 5、6。正确结果应只追加一次 5 和 6,最终文本与未断线的基准结果一致,状态从 waiting_replay 变为 completed。日志至少保留 request_id、最后序号、缺口范围、重放次数和最终状态。

反向代理和浏览器还有三个坑
- 代理缓冲会让多个事件成批到达,服务端和客户端都不能把一次网络读取当作一个事件。
- 心跳只能证明连接还活着,不能证明模型生成已经推进;心跳应使用独立事件类型。
- 用户点“重新生成”时要创建新的
request_id,旧请求的迟到事件不能写进新消息。
哪些情况不适合自动续接
涉及扣款、写库、发送通知这类有副作用的工具调用时,不能因为流断了就自动再次触发动作。先查询业务任务状态,确认没有完成记录,再用幂等键恢复;模型文本可以重建,业务副作用不能靠字符串去重。
如果上游没有事件重放能力,最稳妥的降级是把请求标成“处理中”,通过结果查询接口拿完整响应;查询也失败时明确显示“结果待确认”,并记录请求编号。不要向用户承诺已经完成,也不要把新请求的半截内容伪装成旧回答的续写。
常见问题:AI 流式断线处理
收到重复的 delta,直接比较文本可以吗?
不建议。相同文字可能在不同位置合法出现,使用事件 ID 和请求编号去重更可靠。
没有 sequence_number 还能做断线恢复吗?
可以由自己的网关补序号,但必须保证重试和重放沿用原序号;如果做不到,就退回完整结果查询,不要猜字符断点。
收到 done 事件后还要保存文本吗?
要。done 只说明生成阶段收尾,仍应把最终文本、请求编号和状态一起持久化,供刷新、审计和业务后续使用。
断线后立刻重新请求是不是最快?
只有在旧请求明确失败且没有副作用时才适合。否则先查旧请求状态或重放缺口,避免重复生成和重复触发业务动作。
把“显示了文字”改成“结果可验证”
流式输出的难点不在于把 delta 追加到页面,而在于断线、重复、乱序和完成态都能被记录和复查。事件 ID 保证幂等,序号发现缺口,重放或完整结果查询负责恢复,最终状态负责收口。四层都在,网络抖一下只是一次可处理的传输问题;少了其中任何一层,页面看起来正常也可能已经丢字或重复执行。
Go worker pool 如何处理突发任务:队列背压、拒绝策略和优雅停机
- 上一篇
- Go worker pool 如何处理突发任务:队列背压、拒绝策略和优雅停机
- 下一篇
- Go database/sql 连接池 MaxOpenConns 怎么设:等待队列、连接复用与超时验收
-
- 科技周边 · 人工智能 | 14小时前 |
- MCP区分资源读取与工具调用的实现方法
- 359浏览 收藏
-
- 科技周边 · 人工智能 | 17小时前 | 错误处理 · 参数校验 · agent · openai api · AI工程 · Tool calling · 函数调用 业务错误 JSON Schema Tool calling strict 工具参数校验 模型错误
- Tool calling校验工具参数并区分模型与业务错误的实现方法
- 380浏览 收藏
-
- 科技周边 · 人工智能 | 19小时前 | openai api · 结构化输出 · AI工程 · Pydantic JSON Schema Structured Outputs
- Structured Outputs让模型结果贴合 JSON Schema的实现方法
- 191浏览 收藏
-
- 科技周边 · 人工智能 | 21小时前 | 人工智能 · 结构化输出 · JSON解析 · 模型输出 · 接口容错 · 大模型结构化输出 模型输出JSON缺字段 JSON兜底解析 JSON Schema校验 AI接口异常处理
- 模型输出 JSON 缺字段时如何设计兜底解析
- 272浏览 收藏
-
- 科技周边 · 人工智能 | 22小时前 | 人工智能 · 显存管理 · 推理优化 · 本地推理 KV Cache batch size
- 本地推理 KV cache 和 batch size 如何做取舍
- 251浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 | API · 性能优化 · ai · AI 提示词缓存 Responses API Prompt Caching
- AI 提示词缓存如何按稳定前缀组织请求
- 357浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 |
- 分类评测集不均衡时如何比较 macro 与 micro 指标
- 473浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 |
- Agent 工具返回文件路径时如何限制工作区范围
- 381浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 |
- AI 流式响应中的 finish_reason 如何决定持久化时机
- 195浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 |
- LoRA adapter 合并后 tokenizer 配置如何核对
- 162浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 49次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 146次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 81次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 51次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 31次使用
-
- Go 流式响应怎么做:ResponseController.Flush、SSE 与断开回收的取舍
- 2026-07-26 463浏览
-
- Go SSE 写出后浏览器为什么还收不到:ResponseController.Flush、代理缓冲与写超时
- 2026-08-22 430浏览
-
- Go SSE 接口怎么持续向浏览器推送事件
- 2026-09-06 215浏览
-
- Go Server-Sent Events 怎么正确刷新事件并关闭连接
- 2026-09-07 491浏览
-
- Go http.Flusher 什么时候能把流式响应及时发给客户端
- 2026-09-09 468浏览

