流式与实时链路:从 SSE 的坑到语音的 200 毫秒

T6-2模块 6 · 部署与成本面试权重 更新于 2026-08-19
关联题目Q6-04Q6-05

这篇学完你能回答什么

  1. 流式输出到底怎么实现的?SSE 和 WebSocket 各自的边界在哪,什么时候必须上 WebSocket?
  2. 流式一旦上线,会连带毁掉哪些东西——结构化输出、内容审核、限流精度、计费口径?
  3. 实时语音链路怎么搭?级联 ASR→LLM→TTS 与端到端语音模型各自的代价是什么?

从一个真实故障讲起

一个对话产品,本地开发时流式很顺:敲完回车,字一个个往外蹦;预发环境也正常。上线第二天用户反馈"AI 变卡了"——具体表现是转圈 8 秒,然后整段文字一次性砸出来

团队查了三天:先怀疑模型慢了,拉日志发现首字延迟稳定在 380 毫秒,正常;再怀疑前端渲染,改了状态更新方式,没用;又回滚了两个 SDK 版本,还是一样。最后有人用 curl 直接打后端服务,流是正常的;打生产域名,就是攒着一次性出

真因是生产链路上多了一层 nginx,而 proxy_buffering 默认就是 on——它把上游响应先攒进缓冲区,攒够或流结束才往下发。开发和预发直连服务,只有生产走网关,所以三个环境行为不一样。

修复只有一行配置。但故障留下了更值钱的东西:团队从此把 TTFT 拆成两个指标——

原文示意
Pipeline TTFT :第一个字节到达客户端   ← 对外 SLO 用这个
Model TTFT    :推理返回第一个 delta   ← 隔离模型本身

两者背离时,问题一定在"非模型环节":网关缓冲、检索、上下文拼装、鉴权

这正是第 5 章那把刀的又一形态:固定住一个变量,把问题一分为二。 Q2-17 固定文档切检索侧与生成侧,金丝雀集固定输入切流量侧与系统侧,这里固定"模型"这一段切模型侧与链路侧。能主动把 TTFT 拆成两个的人,一定排过障。


查了三天,答案在网关的默认值里
本地和预发都正常,只有生产转圈 8 秒后一次性出字 —— 三个环境行为不一样

  1. 用户反馈「AI 变卡了」

    转圈 8 秒,然后整段一次性砸出

  2. 先怀疑模型慢了

    拉日志:首字延迟稳定在 380 毫秒

  3. 改前端、回滚 SDK

    都没用,前后一共查了三天

  4. curl 直打后端服务

    流是正常的;打生产域名就攒着出

  5. 生产多一层 nginx断点

    proxy_buffering 默认就是 on

Model TTFT380 毫秒一直正常 —— 模型侧没有问题
用户看到的本地:字一个个往外蹦生产:转圈 8 秒后整段砸出
排查 / 修复成本查了三天改一行配置
留下的资产一个 TTFT 指标拆成 Pipeline 与 Model 两个
修复只有一行配置,值钱的是那把刀:固定住一个变量,把问题一分为二curl 直连固定了「模型」这一段,链路侧与模型侧就此切开 —— 两个 TTFT 一背离,问题一定不在模型。

核心概念:先打比方,再给定义

SSE(Server-Sent Events,服务器推送事件)。它不是新协议,就是一次普通的 HTTP 响应,只不过响应体是长连接、按约定格式一帧一帧发。SSE 像单向广播电台——调到频道就一直听,但没法对着收音机说话;WebSocket 像对讲机,两头都能说。

流式帧格式。一帧就是几行文本,空行结束:

原文示意
event: content_block_delta
data: {"type":"text_delta","text":"你"}
                              ← 空行 = 一帧结束
: 注释行,常用作心跳

WebSocket。HTTP 握手后把连接升级成全双工长连接,两端随时互发文本或二进制帧。一句人话:它买的是"双向"和"二进制",卖的是"有状态"。

barge-in(打断)。用户在 AI 说话时插话,系统要立刻停下来听——这是实时语音区别于"语音版聊天机器人"的分水岭。

VAD(Voice Activity Detection,语音活动检测)。判断用户在不在说话、说完了没有。server VAD 靠静音时长做声学切分,semantic VAD 用分类器读已说出的语义内容判断是否说完。一句人话:前者听"有没有声音",后者听"这句话完整了吗"。


双向不是免费的,付的是有状态
SSE 不是新协议,就是一次普通 HTTP 响应,只不过响应体按约定一帧一帧发

单向广播电台

调到频道就一直听

但你没法对着收音机说话

对应
SSE · 长连接的 HTTP 响应体

一帧就是几行文本,空行结束

: 开头是注释行,常用作心跳;规范只在网络错误时重连,非 200 或 Content-Type 不对属永久失败

event: / data:Last-Event-ID 续传HTTP/1.1 同域限 6 连接
对讲机

两头随时都能说

代价是这条线得一直占着,换个人接就得重来

对应
WebSocket · 握手后升级

买的是双向和二进制

卖的是有状态:绑死单实例,滚动发布踢掉活跃会话,断线补发没有对等机制

真双向二进制音视频帧单连接多路复用
OpenAI、Anthropic、Gemini 的文本推理流式全是 SSE over HTTP POST,没有一家用 WebSocket(截至 2026-08)—— 真双向、二进制帧、多路复用这三条,文本推理一条都不满足。

原理拆解

分野在有没有中间文本态
人类轮次间隔约 200 毫秒,可说一个词的言语产出准备就要 600 毫秒

用户开口
麦克风
两条路从这里分开
ASR · 转成文本级联第 1 段
一个多模态模型端到端:直接吃音频
中间态是不是文本
LLM · 瓶颈在这段可审可评可控
没有中间文本态审核无法前置
出声
TTS · 转成音频级联第 3 段
直接吐音频文本与音频对齐不准
到耳朵
扬声器人类基线 200 毫秒
200 毫秒是怎么来的(同一份研究的三个数)
量的是什么数值读法
人类对话的轮次间隔约 200 毫秒实时语音要对齐的目标基线
说一个词的言语产出准备600 毫秒已经比轮次间隔本身还长
说一个短句的准备1500 毫秒是那 200 毫秒的 7.5 倍
那人是怎么做到的边听边预测不是反应快 —— semantic VAD 想模仿的正是这个

打断答到「用 VAD 检测」只是及格,完整链路三层:① 检测层,server VAD 听声音,semantic VAD 读语义判断说完没;② 执行层,必须发「截断」指令,把未播放的部分从服务端历史删掉,否则每一轮的上下文都是错的;③ 传输层,WebRTC 自动处理,裸 WebSocket 自己管。

浏览器端一定要有一个自建 AppServer 做 signaling 与临时凭证签发:原生 WebSocket 设不了 Authorization 头,WebRTC 的 SDP 交换又受 CORS 限制。声称「纯前端直连」的方案,一定在某处泄露了长期密钥。

「端到端一定更快」不要背。各家给的数字互相矛盾,因为级联各段在流式下是重叠的,名义串行加总本就不成立。级联的瓶颈从来在 LLM 那一段,杠杆在模型选型和流式重叠,不在换架构。

一、三家 API 的流式:协议同构,语义完全不兼容

截至 2026-08,OpenAI、Anthropic、Gemini 的文本推理流式全部是 SSE over HTTP POST,没有一家用 WebSocket,但事件语义互不兼容:

厂商 事件形态 结束标志
Anthropic 块级状态机:message_startcontent_block_start → 多个 content_block_deltacontent_block_stopmessage_deltamessage_stop 没有 [DONE],以 message_stop 结束
OpenAI Chat Completions delta 增量:chunk.choices[0].delta.content data: [DONE]
OpenAI Responses / Gemini Interactions 语义事件流:response.output_text.delta 语义完成事件

推论:所谓"换个 base_url 就能切模型"在流式场景是假的。 网关必须写一层事件翻译,这是 T6-3 里"跨厂商 fallback 没那么便宜"的第一条证据。Anthropic 的 delta 还分四种:text_deltainput_json_delta(工具入参的部分 JSON)、thinking_deltasignature_delta——能说出 input_json_delta 的人一定接过工具流式,因为那是坑最多的地方。

二、SSE 的五个坑(都写在规范或官方文档里)

坑 1:EventSource 不能设请求头。 它的构造器只接受 url{ withCredentials }没有 header 参数,所以带 Authorization 的 API 永远不能用它直连。实践上一律改成 fetch() + ReadableStream 自己解帧,代价是规范白送的自动重连、Last-Event-ID 续传、retry: 退避全要自己重写。

坑 2:代理缓冲。 就是开头那个故障。nginx 的 proxy_buffering 默认 on;上游可发 X-Accel-Buffering: no 关掉,但这个头可能被网关的 proxy_ignore_headers 吃掉

坑 3:空闲超时。 nginx 的 proxy_read_timeout 默认 60 秒,算的是"两次成功读取之间"。模型长时间思考不吐字,60 秒后连接就被切——表现是"流到一半断"。规范给的解法很明白:每 15 秒左右发一个注释行当心跳。

坑 4:HTTP/1.1 的 6 连接限制。 浏览器对同一域名最多开 6 个连接,SSE 长期占用一个;开到第 7 个标签页就永远连不上。Chrome 和 Firefox 都标成 Won't fix。HTTP/2 下不再是问题——变成协商的并发流,默认上限 100。

坑 5:失败与重连的区别。 规范规定网络错误才自动重连;状态码不是 200、或 Content-Type 不是 text/event-stream,属永久失败,不重连。所以后端返回 500 时 SSE 是"静默死掉"的,前端必须自己兜。

三、WebSocket 什么时候才必要

只有满足任一条时才值得付出代价:真双向(客户端要在响应过程中持续发指令)、二进制音视频帧单连接多路复用。文本推理三条都不满足——所以三家都没用它。代价这一侧同样清楚:

  • 浏览器原生 WebSocket 同样不能设 Authorization(构造器只有 urlprotocols),浏览器端一律要走临时凭证;
  • 连接有状态,绑死单实例,滚动发布会踢掉活跃会话;SSE 每次是无状态 HTTP,重连即换实例;
  • 断线补发要自己做——SSE 有规范级的 Last-Event-ID,WebSocket 没有对等机制。

一个 2026 的现成案例(截至 2026-08):MCP 规范在 2026-07-28 那版把传输层进一步去状态化——移除协议级 session 与握手,也移除了 SSE 流的续传与消息补发(断流就重发新请求)。这是"有状态可靠传输"的代价被公开承认的一次:粘连、无法水平扩展、CDN 与网关不友好,最后被无状态 + 客户端重试换掉了。

四、流式的四个连带后果

① 和结构化输出的冲突不在文本,在工具入参。 结构化输出本身与流式兼容。真正的坑是工具参数:Anthropic 官方警告 API 不会在流式过程中缓冲或校验工具入参,你可能收到"部分或非法的 JSON",且 max_tokens 截断会把参数切一半。两条路二选一:攒到 content_block_stop 再解析(牺牲 TTFT),或用容错解析器边流边补括号(牺牲正确性)。

② 响应侧护栏与流式架构性互斥。 Kong 官方原话是无法"对每个响应 token 查一次外部系统"。NeMo Guardrails 把取舍做成了参数:按 token 块检查(默认 200),stream_first 默认为 True——先吐给用户再检查。违规内容已经在用户屏幕上了,事后只能撤回 UI、撤不回截图;设成 False 则退化成每 200 token 一次审核延迟。

③ 限流精度必然下降。 Azure API Management 文档写得最直白:stream: true输入 token 强制估算(忽略配置),输出 token 也是估算——流式下要等流完才知道真实用量。

④ 计费与中断口径要自己实测。 三家官方文档都没明文说"客户端断开后上游会不会停止生成、按已生成还是已传输计费",不要假设。另外 OpenAI 流式默认不返回用量,要显式打开 stream_options.include_usage,且只在最后一个 chunk 返回。

五、实时语音:两条路的真实取舍

原文示意
级联                 麦克风 →[ASR]→ 文本 →[LLM]→ 文本 →[TTS]→ 音频
                              ↑ 每一段都有中间文本态,可审可评可控

端到端(S2S)        麦克风 →[   一个多模态模型   ]→ 音频
                              ↑ 没有中间文本态

人类对话的基线是轮次之间约 200 毫秒。但同一份研究给了更有说服力的对照:说一个词的言语产出准备要 600 毫秒,短句要 1500 毫秒——人类是靠"边听边预测"做到 200 毫秒的,不是靠反应快。这正是 semantic VAD 想模仿的东西。

不要背"端到端一定更快"。 各家博客给的总延迟互相矛盾(有说级联 2 秒、端到端 200–300 毫秒的,也有厂商反过来说"工程良好的级联比部分端到端模型更快"的),原因是级联各段在流式下是重叠的,名义串行加总本就不成立。可靠的说法是:级联的瓶颈从来在 LLM 那一段,不在 ASR/TTS,所以杠杆在模型选型和流式重叠,不在换架构。

端到端确定的代价有五条,每条都有出处:

  1. 上下文窄一个量级:实时语音模型多在 32K–128K,同期文本模型是 200K–1M。
  2. 音频 token 贵得多(截至 2026-08):OpenAI 音频输入是文本的 8 倍、输出 2.7 倍;百炼 omni-realtime 音频输入约 8.2 倍、含音频输出约 5.4 倍。
  3. 没有精确的文本-音频对齐——OpenAI 官方自己写的:打断截断时"会同时删掉未播放的音频和它对应的文本"。对齐不准 ⇒ transcript 不可靠 ⇒ 评测与合规审计都难做。
  4. 指令跟随会退化:有学术评测量化过语音模型相对其纯文本底座的"遗忘率",最差超过 −50%。
  5. 审核无法前置:级联能在用户听到之前过滤,端到端做不到——音频已经在扬声器上了。

打断答到"用 VAD 检测"只是及格。 完整链路是三层:

原文示意
① 检测层:server VAD(静音时长)vs semantic VAD(读语义判断说完没)
           后者才能正确处理"嗯…让我想一下""对对对"这类不该抢话的停顿

② 执行层:插话 → 取消进行中的响应 → 客户端必须发"截断"指令,
           把【未播放的部分】从服务端会话历史里删掉。
           不做:模型以为自己说完了整段,用户只听到一半,
           此后所有轮次的上下文都是错的  ← 这个 bug 不上生产遇不到

③ 传输层:WebRTC / SIP 自动处理;裸 WebSocket 下播放与截断全靠自己管

六、浏览器端为什么绕不开一个自建后端

这是 Q6-05 最锋利的一刀,三家殊途同归:

  • 浏览器原生 WebSocket 设不了 Authorization(MDN 构造器签名摆在那),不能拿长期密钥直连;
  • WebRTC 的 SDP 交换受 CORS 限制,也不能直接 POST 给厂商服务端——阿里云百炼官方文档逐字写了这条,并明确"正式使用时由业务 AppServer 代理完成";
  • OpenAI 的 client_secrets、Google 的 ephemeral token,都是同一问题的另一种答法。

结论:浏览器端实时语音一定要有一个自建 AppServer 做 signaling 与临时凭证签发。任何声称"纯前端直连"的方案,一定在某处泄露了长期密钥。

至于 WebRTC 为什么比 WebSocket 适合语音:TCP 的队头阻塞会让丢包变成"先静音一段、再一口气爆出积压音频",而丢一个 20 毫秒的音频帧几乎听不出来,等 200 毫秒重传却非常明显。WebRTC 还自带抖动缓冲和媒体专用拥塞控制。好消息是接大厂 Realtime API 时 STUN/TURN 通常由厂商兜住(百炼文档明确写"无需配置 ICE 服务器")——只有自建 SFU、多方会议或私有化部署时,这笔成本才回到你头上


工程实践(截至 2026-08)

选型判据

场景 选什么 理由
LLM 文本流式输出 SSEfetch + ReadableStream,不用 EventSource 单向够用,无状态,重连即换实例
客户端要在生成中持续发指令 WebSocket 唯一真双向的选项
浏览器端实时语音 WebRTC + 自建 AppServer 做 signaling UDP 抗丢包、自带回声消除与抖动缓冲
服务端已有音频管道(电话/IVR) WebSocket 或 SIP 服务端无 CORS 与 header 限制
强可控 / 强合规的语音场景 级联 有中间文本态:审核可前置、可评测、可审计
追求自然感与情绪 端到端 接受上下文窄、音频贵、对齐不准

三家实时语音现状(截至 2026-08,易变,以官方为准)

厂商 接入方式 会话上限 备注
OpenAI Realtime WebRTC / WebSocket / SIP,浏览器官方推荐 WebRTC 60 分钟(Azure 30 分钟) 已 GA;支持 function calling 与 effort
Google Gemini Live WebSocket 纯音频 15 分钟 Preview;官方已下线 half-cascade,只剩原生音频
阿里云百炼 Qwen-Omni WebSocket / WebRTC / AOQ 120 分钟 官方推荐 semantic_vad;WebRTC 内置回声消除

一个值得咀嚼的现实:Google 下线 half-cascade 后,官方论坛出现了持续数月的请愿——开发者反馈半级联版本工具调用成功率明显更高,而原生音频版本强制产出音频,纯文本场景被迫白付音频 token。"端到端"不是免费升级。

避坑清单

  • 网关三件套先配好proxy_buffering off、15 秒心跳、proxy_read_timeout 调大。
  • TTFT 拆两个指标上报;语音场景另测"首个音频块"。
  • 工具入参流式默认不开,除非已有容错 JSON 解析器且处理了截断。
  • 上了流式别指望响应侧同步护栏:要么接受"先出后审"并做撤回 UI,要么关键场景关流式。
  • 语音打断一定要做"截断已播放位置",否则会话历史会静默漂移。

上 WebSocket 要有理由,SSE 不用
只有真双向、二进制音视频帧、单连接多路复用,才值得付有状态那笔代价

选型判据(截至 2026-08)
场景选什么理由
LLM 文本流式输出默认起点SSEfetch + ReadableStream,不用 EventSource单向够用,无状态,重连即换实例
客户端要在生成中持续发指令WebSocket唯一真双向的选项
浏览器端实时语音WebRTC + 自建 AppServer 做 signalingUDP 抗丢包、自带回声消除与抖动缓冲
服务端已有音频管道(电话 / IVR)WebSocket 或 SIP服务端没有 CORS 与 header 限制
强可控 / 强合规的语音场景级联有中间文本态:审核可前置、可评测、可审计
追求自然感与情绪端到端接受上下文窄、音频贵、对齐不准
现状与网关经验(截至 2026-08,易变,以官方为准)
网关三件套
proxy_buffering off、每 15 秒发一个注释行当心跳、proxy_read_timeout 调大
三家事件语义
Anthropic 块级状态机、没有 [DONE];OpenAI Chat 是 delta.content + [DONE]
流式的四处连带损伤
限流精度降为估算、响应侧护栏架构性互斥、工具入参可能非法、计费口径要自己实测
OpenAI Realtime
WebRTC / WebSocket / SIP,浏览器官方推荐 WebRTC;60 分钟(Azure 30 分钟),已 GA
Google Gemini Live
WebSocket,纯音频 15 分钟,Preview;官方已下线 half-cascade,只剩原生音频
阿里云百炼 Qwen-Omni
WebSocket / WebRTC / AOQ,120 分钟;官方推荐 semantic_vad,WebRTC 内置回声消除
SSE 不是「简单版 WebSocket」,它是一个自带自动重连、Last-Event-ID 续传与 retry: 退避的规范。换成 fetch 手工解帧是在放弃这些能力 —— 这笔代价要算进选型里。

面试视角

概念背得再顺,也倒在追问那一句
「本地好好的,上线后不流式了,你怎么查」—— 从这一问开始分人

  1. 先定性SSE 不是新协议,是 HTTP 响应体的约定格式,单向、自带重连
  2. 再给判据真双向 / 二进制 / 多路复用,三条才值得上 WebSocket
  3. 主动抛一个坑代理缓冲,或 EventSource 根本不能设请求头
  4. 语音题走五步澄清 → 基线走级联 → 加挂端到端并说代价 → 评测 → 演进
  5. 用打断收尾截断要把未播放的部分从服务端历史里删掉
只读过:这些回答会暴露你
  • 只会说「SSE 单向、WebSocket 双向,看场景」,报不出一个事件名
  • 「上线后不流式了」先怀疑模型和前端,不问中间过了几层代理
  • 「前端用 EventSource 就行」—— 不知道它带不了鉴权头
  • 背「端到端更快」却说不出代价;打断只答到「用 VAD 检测」
  • 认为浏览器可以直连厂商的实时语音服务
真做过:这些细节骗不了人
  • 随口说出 Anthropic 没有 [DONE],并补一句网关得写事件翻译层
  • 主动把 TTFT 拆成 Pipeline 与 Model,背离就说明问题在非模型环节
  • 知道 X-Accel-Buffering: no 可能被 proxy_ignore_headers 吃掉
  • 打断答到「把未播放部分从服务端历史删掉」,且知道 WebRTC 自动处理
  • 一句点破浏览器端必须有自建 AppServer,并说得出那两条约束
Q6-05 真正的考点在那个反问上:端到端更快,为什么不全用它。五条代价每条都有出处 —— 上下文只有 32K–128K、音频输入贵 8 倍、对齐不准、指令跟随遗忘率最差超 −50%、审核不能前置。

面试官怎么问

Q6-04 是一面的工程基础题,一句"你们流式怎么做的"就能问出来,分水岭在追问:"本地好好的,上线后不流式了,你怎么查?" Q6-05 是进阶题,多出现在做语音的团队,真正的考点在"端到端更快,为什么不全用它"这个反问上

答题结构建议

Q6-04:先定性——"SSE 不是新协议,是 HTTP 响应体的约定格式,单向、自带重连";再给判据"只有真双向、二进制、多路复用才值得上 WebSocket";最后主动抛一个坑(代理缓冲或 EventSource 不能设 header),把话题从概念拉到工程。

Q6-05:用 Q2-25 的五步结构。澄清(浏览器端还是服务端?延迟预算?要不要审核前置?)→ 基线(级联,因为有中间文本态)→ 加挂并说代价(端到端换自然感,代价是上下文窄、音频贵、对齐不准、指令跟随退化、审核不能前置)→ 评测(打断成功率、误打断率、对齐质量)→ 演进(先级联跑通业务,再对高价值场景切端到端)。

分水岭信号

暴露只读过:

  • 只会说"SSE 单向、WebSocket 双向,看场景",报不出任何一个具体事件名
  • 遇到"上线后不流式了"先怀疑模型和前端,不问"中间过了几层代理"
  • 说"前端用 EventSource 就行"——不知道它不能带鉴权头
  • 背"端到端更快"却说不出它的代价;打断只答到"用 VAD 检测"
  • 认为浏览器可以直连厂商的实时语音服务

证明真做过:

  • 随口说出 Anthropic 没有 [DONE]、OpenAI Chat 有,并补一句"所以网关得写事件翻译层"
  • 主动把 TTFT 拆成 Pipeline 与 Model 两个指标,并说"两者背离就说明问题在非模型环节"
  • 知道 X-Accel-Buffering: no 可能被 proxy_ignore_headers 吃掉;知道换 fetch 手工解帧的代价是重连与续传全得自己写
  • 说得出流式会同时让限流精度、响应侧护栏、结构化输出三件事降级
  • 打断答到"必须把未播放部分从服务端历史里删掉",并知道 WebRTC 自动处理、裸 WebSocket 要自己管
  • 一句话点破"浏览器端必须有自建 AppServer",并说得出两条约束(设不了 header、SDP 受 CORS 限制)

小结与延伸

  • SSE 不是"简单版 WebSocket",是一个自带重连与续传语义的规范。 换成 fetch 手工解帧是在放弃这些能力。

  • 流式的成本不在实现,在它连带毁掉的东西:限流变估算、响应侧护栏失效、工具入参可能非法、计费口径变模糊。这四条才是这道题的真正深度。

  • 实时语音的取舍不是"快不快",是"有没有中间文本态"。 有就能审核前置、能评测、能审计;没有,换来的是自然感和情绪。

  • 浏览器端一定要有自建后端,这是 Web 平台的既有约束,不是厂商懒。

  • 上游:T0-3(消息角色)、T1-2(结构化输出与三层校验)|平级:T6-1T6-3

  • 关联题目:Q6-04Q6-05;护栏分层见 Q3-18,流式下的结构化输出见 Q1-10

继续深入

本篇归属第 6 章「部署与成本」,去做这一章的题