开发者将语音代理响应时间从4247ms降至780ms,最大收益不是优化LLM,而是流式输出首句和自适应结束检测。固定1.2秒沉默窗口比模型推理更耗时。
语音 AI 延迟从 4.2 秒降至 780ms:Deepgram + ElevenLabs 实战
我的语音助手第一版回答一个问题需要 4.2 秒。这不是"感觉慢",而是实打实测出来的:人类说完最后一个音节,到系统吐出第一个音节,中位延迟 4,247ms。语音 AI 的延迟是那种用户不需要看仪表盘就能察觉异常的指标——他们只会对着沉默说"喂?",然后开始重复,而重复的内容会产生第二条转写,机器人又会再回答一遍。
我花了大约三周时间把这个数字拆解清楚。下面是真实的水瀑布图,每个优化手段实际省下了多少毫秒,以及变快之后出现的三类新问题。
中位往返延迟从 4,247ms 降到了 780ms。LLM 从来不是主要问题。
最大的单项优化是不等完整回复就出声:从第一个句子就开始流式 Text-to-Speech。仅此一项就省了约 1,430ms。
第二大优化是端点检测,而非推理速度。固定的 1,200ms 静默窗口消耗的时间比模型推理还多。
将静默窗口压缩到固定的 250ms 会让它变快但体验很差:31% 的回合会把用户截断。自适应端点检测把这个比例降到了 6%。
p95 仍然有 1.6s,几乎全部是 prompt 缓存未命中——当有人安静思考了好几分钟后才开口。最慢的回复恰恰给了暂停最久的人,这完全搞反了。
语音 AI 延迟的真正成因是什么?
语音 AI 延迟是六个串行等待叠加在一起,其中大部分都不是模型推理本身。以下是我在 40 个录制会话中测得的中位基准:
看这张表告诉我们的信息。推理耗时 2,290ms,总延迟 4,247ms。剩下的 1,957ms 是我故意在等:等静默来"确认"对话轮次结束,等一个完整的 JSON 对象,等一个完整的音频文件。这些等待每一个都是我从未主动选择过的默认行为。
如何真实地测量语音助手延迟而不自欺欺人?
在扬声器端测量,而不是在服务器端测量。我最早的插桩方式是在请求到达后端的地方打时间戳,报告的数字比实际好约 200ms。浏览器的音频采集缓冲区和上传链路在那儿是不可见的,而那也是用户感知到的延迟。
我最终的做法:
按会话录制原始输入轨道。
对该录制离线运行语音活动检测,找到 t_last_speech,即人类最后一个音节的真实结束时间。
在客户端为第一个可听输出帧打时间戳,而不是在服务器端标记"响应已发送"。
每个阶段事件都计入同一个单调时钟。跨两台机器混用 Date.now() 会导致某个阶段显示花费了负时间——我就是通过这个发现了那个 bug。
整个系统运行在 Preterview 上,这是我构建并运营的一个平台,运行真实感的语音面试,提供三种面试官风格并返回评分书面报告(完整披露:这是我建的,preterview.com/en)。这是一个对延迟优化很有用的测试场景,因为失败非常明显:人类在压力下与它对话,在聊天 UI 中可以接受的停顿在语音场景下会让人觉得机器坏了。本文中的大部分数据都来自生产流量,而非合成测试循环。

真正削减延迟的手段,按节省毫秒数排序
1. 从第一个句子边界就开始流式 TTS(-1,430ms)
原代码逻辑是 await llm.complete() 然后 await tts.generate() 然后播放。没有任何一步是必要的。当 token 流产生第一个完整句子时,立即发送给 ElevenLabs 并持续喂入,用户就能在模型还在输出的同时听到音频。这一步删掉了 1,240ms"等待剩余输出"的等待,并将 380ms 的完整生成替换为约 90ms 的首个 chunk。
2. 系统提示词和会话上下文启用 Prompt Caching(-740ms)
我的系统提示词加上运行中的面试状态约 6.8k tokens,每轮对话完全相同。未缓存的首 token 耗时 1,050ms。缓存后,310ms。这是整篇文章里性价比最高的优化:只需要在一个从不变化的 prefix 上加一个 cache-control 标记。
3. 自适应端点检测(-920ms)
固定的 1,200ms 静默窗口假设每次停顿含义相同。实际上并不相同。如果转写以完整从句结尾("...so that's how I handled the migration"),我等 220ms。如果以填充词或悬垂连词结尾("...and then, um"),我等 900ms。真实对话的中位值:280ms。
4. 语音路径使用纯文本(-180ms)
对话回复是 JSON 格式,因为评分管道需要结构化字段。但评分不必在用户等待的那个请求里完成。我做了拆分:口头回复是纯文本,结构化抽取放在后续的非阻塞调用中,在音频已经开始播放之后。约束解码也让首 token 耗时变得更不可预测,而当用户盯着麦克风时,尾部延迟比中位数更重要。
最终实测预算,相同方法论:
变快之后出现了什么问题?
打断从 bug 变成了功能需求。 在固定的 250ms 端点下,我手工计时了 12 个会话中共 418 个对话轮次。机器人有 31% 的概率开始抢话。人类会在说到一半时停下来思考,而思考停顿和说完停顿在静默计时器看来是完全一样的。自适应端点检测把它降到了 6%。不是零。仍有 6% 的轮次被截断,而我找不到一个不让我多花 400ms 的干净解决方案。
句子分割不是 split('.')'。 我的第一个分块器把"3.5 years"拆成了"3."和"5 years at a startup",对"Node.js."也做了同样的处理。音频在一个数字中间突然停顿。解决方案很无聊:要求句号 + 空格 + 大写字母,并在发送到 TTS 前强制要求至少 60 个字符的最小 chunk。
流式输出会浪费用户没听到的音频。 当用户打断时,TTS 流已经领先两个句子。有一次 18% 生成的语音字符是无人聆听的废弃音频。将 lookahead 限制在两个句子后降到了约 7%。快速版本的管道实际上比慢速版本每会话成本更高,这是我没有预料到的方向。
p95 属于沉默的人。 p95 是 1.6s,原因几乎完全是 prompt 缓存过期。缓存条目有 TTL。候选人大段沉默组织思路后再开口,会遇到冷 prefix 并支付全额未缓存 TTFT。最需要一个快速、鼓励性回复的人反而得到了最慢的回复。我现在在长静默期间发送一个廉价的 keepalive 请求来缓解这个问题,而且我宁愿报告这个丑陋的尾部数据,也不愿用一个美化の中位数。
所以语音 AI 延迟的真正答案是什么?
如果你的语音助手感觉慢,模型很可能不是瓶颈。从人类最后一个音节到第一个可听输出帧,对管道进行端到端插桩,你通常会发现大部分时钟时间都是有意在等:一个固定的静默窗口才肯承认轮次结束,等完整回复才肯开口,等完整音频文件才肯开始播放。从第一个句子边界开始流式 Text-to-Speech,对静态 prefix 启用 prompt caching,以及让端点阈值根据句子结束方式自适应——这三招把我自己的系统从 4,247ms 压到了 780ms 中位延迟,模型都没换。然后为后果做好预算:更快的轮次检测意味着更多打断,流式输出意味着为没人听到的语音付费。
Written by the developer behind Preterview, an interview prep platform.