在大型语言模型与多智能体协作场景中,Server-Sent Events(SSE)因其轻量级、单向 HTTP 推送的特性,成为承载流式输出的首选协议。然而,当业务从单一模型扩展到多模型并行补全或级联推理时,连接管理复杂度显著上升 —— 如何在网络波动、模型响应延迟不一致、代理层超时等情况下保证用户体验与数据完整性,成为工程落地的核心挑战。本文将从连接保活、心跳策略、断线续传与超时阈值四个维度,给出可落地的参数建议与监控要点。

一、连接保活与心跳策略:对抗代理层超时

SSE 本质上是基于 HTTP/1.1 长连接的单向通道,这一特性在带来兼容性的同时,也使其成为代理服务器、负载均衡器与浏览器超时策略的 “猎物”。在多模型流式场景中,单个请求的持续时间可能从数秒到数分钟不等,远超默认的 30 秒或 60 秒空闲超时阈值。若不主动干预,连接会在模型尚未完成推理时被意外切断,导致用户体验中断与数据丢失。

1.1 心跳消息的发送频率

业界的最佳实践是每隔 15 至 30 秒发送一次心跳事件。心跳消息不携带业务数据,仅作为连接存活信号,通常以注释行(以冒号开头)的形式发送,例如 : heartbeat:\n。这种格式会被 EventSource 客户端忽略,但能有效重置代理层与浏览器的空闲计时器。在多模型场景中,若各模型的推理耗时差异较大,建议将心跳间隔设置为 20 秒 —— 这一数值在大多数云负载均衡器(如 AWS ALB 的 60 秒默认空闲超时)与浏览器行为之间取得了平衡。

1.2 心跳消息的工程实现

在后端实现层面,心跳消息应作为独立任务与模型推理任务并行执行。以 Python FastAPI 为例,可使用 asyncio.create_task 启动心跳协程,在主请求循环持续运行期间按固定间隔发送心跳。当模型推理完成或客户端主动断开时,心跳任务应被正确取消,避免资源泄漏。以下是一个简化示例:使用 BackgroundTasks 注册心跳函数,在每次迭代中发送评论行并等待固定间隔。

二、重连与断线续传:Last-Event-ID 的正确使用

SSE 协议原生支持断线续传机制,客户端在重新连接时可通过 Last-Event-ID 请求头将最后一次接收的事件 ID 告知服务器,服务器据此判断应从哪个事件继续推送。这一机制在多模型流式输出中尤为关键 —— 当一个请求涉及多个模型的分块响应时,丢失某个片段可能导致整个输出不可用或语义错乱。

2.1 事件 ID 的设计原则

每个 SSE 事件应携带唯一的自增 ID,ID 的设计需考虑两点:其一,ID 应在模型推理的 token 粒度上递增,而非仅在完整响应块层面,以保证续传时的精细度;其二,ID 的分配应在服务器端完成,确保即使客户端暂时离线,服务器仍能准确记录已推送的最后位置。在多模型场景下,可采用 模型标识_自增序号 的复合 ID 格式,例如 gpt-4_1023claude-3_0456,以区分不同模型的输出流。

2.2 客户端的续传逻辑

客户端在接收事件时应持久化 lastEventId 至本地存储(localStorage 或内存)。当网络中断触发 EventSource 的 onerror 回调并触发自动重连时,浏览器会自动在请求头中携带 Last-Event-ID。服务端在接收到该请求头后,应从数据库或内存中查询对应模型的当前推送位置,跳过已发送的事件并继续推送剩余内容。若服务器不支持续传(例如使用了无状态的流式响应),则应在响应头中标注 Cache-Control: no-store 并在客户端实现完整的响应缓冲与去重逻辑。

2.3 重试间隔的动态调整

SSE 协议支持通过 retry 字段动态调整重试间隔。服务器可在每次事件中携带 retry: 毫秒数 向客户端建议重连等待时间。在多模型场景中,建议根据模型推理耗时动态调整:推理较快的模型可设置较短的 retry 值(如 3000 毫秒),而推理耗时较长的模型则建议设置为 8000 至 10000 毫秒,避免客户端在模型仍在推理时过早重试导致重复请求。

三、超时阈值的分层配置

在 SSE 多模型流式场景中,超时问题存在于多个层级:客户端的请求超时、服务端的连接保持超时、代理层的空闲超时以及模型推理本身的执行超时。每一层都需独立配置并相互协调。

3.1 服务端连接超时

建议在服务端实现一个硬性的连接超时阈值,例如 30 分钟。该阈值的作用是防止无限占用服务器资源,确保连接在异常情况下能被回收。达到超时后,服务器主动关闭连接并发送结束事件 event: close\n data: {"reason": "timeout"}\n。客户端在收到结束事件后,应立即触发带有 Last-Event-ID 的新连接请求,以继续获取剩余输出。

3.2 代理层与云服务的超时配置

在使用 Nginx、API Gateway 或云负载均衡器时,必须将其空闲超时设置调整为大于心跳间隔。建议将代理层的空闲超时设置为心跳间隔的 2 至 3 倍,即 40 至 90 秒,以确保心跳消息能够持续维持连接活跃。同时,需确保代理层支持 HTTP 长连接且不会对 SSE 响应的 Content-Type(text/event-stream)做特殊处理或缓存。

3.3 客户端超时与兜底策略

客户端应设置请求级别的超时计时器。当超过预设时间(例如 5 分钟)仍未收到任何事件时,应主动关闭连接并触发重连流程。此外,建议在客户端实现一个 “最大重试次数” 计数器(例如连续重试 3 次后仍失败),此时应向用户展示明确的错误提示并将当前已接收的部分输出保存至本地,供后续手动恢复使用。

四、多模型场景下的特殊考量

当单个请求涉及多个模型并行推理或级联调用时,SSE 连接的复杂度进一步提升。每个模型的输出可能以不同速率到达,服务器需要实现流复用(stream multiplexing)机制,将不同模型的输出映射到不同的 event 类型或使用流 ID 进行区分。客户端则需根据 event 字段或流 ID 分别处理不同模型的输出,并在 UI 上对应渲染。

在监控层面,建议为每个模型的输出流设置独立的指标:模型推理耗时(从请求到首个 token 的时间)、 token 吞吐量(tokens per second)、连接中断次数与续传成功率。这些指标可通过 Prometheus 或类似工具采集,形成告警规则 —— 例如当某个模型的平均推理耗时超过预设阈值的 2 倍时触发告警,提示可能存在模型实例异常或排队延迟。

五、总结:参数清单与落地建议

综合上述分析,以下是 SSE 多模型流式输出的核心参数建议:

  • 心跳间隔:20 秒(代理层空闲超时 60 秒场景下)
  • 服务端硬超时:30 分钟
  • 默认 retry 间隔:5000 毫秒,可根据模型推理速度动态调整
  • 客户端最大重连次数:3 次
  • 客户端请求超时:5 分钟
  • 事件 ID 粒度:token 级别或子块级别,避免仅在完整响应块层面编号

在实现层面,需确保服务端正确处理 Last-Event-ID 请求头并在内存或持久化存储中维护推送进度;客户端需持久化 lastEventId 并在重连时正确恢复;代理层的超时配置需与心跳策略联动验证。建议在上线前通过 Chaos Engineering 手段模拟网络中断与代理超时,验证续传逻辑的完整性。


参考资料

  • Server-Sent Events 官方规范与错误处理实践(Mozilla Developer Network)[1]
  • 多模型 AI 流式输出中的 SSE 架构设计(Procedure Tech, 2025)[2]