主题模式
实时语音 Agent 深度解析:STT、LLM、TTS 全链路架构,从 Whisper 到 400ms 端到端延迟
你对着手机说"明天北京天气怎么样",不到一秒,语音回答就来了。这条链路上发生了什么?这篇文章拆解实时语音 Agent 的完整技术栈:Whisper 的架构原理、VAD 断句的艺术、TTS 的流派、打断(barge-in)处理、延迟预算分配,以及级联 vs 原生多模态两条路线的架构对决。读完你能搭建自己的语音 Agent。
发布日期:2026-10-09
语音 Agent:LLM 应用皇冠上的明珠
文本 Agent 已經卷成红海,但语音 Agent 才是交互革命的深水区:
| 场景 | 文本交互 | 语音交互 |
|---|---|---|
| 客服热线 | 按键菜单 + 排队 | 直接对话解决 60% 常见问题 |
| 驾驶/烹饪/健身 | 手和眼被占用,无法交互 | 免手免眼,唯一可行模态 |
| 老人/儿童/视障 | 打字门槛 | 零学习成本 |
| 情感陪伴 | 冷冰冰的文字 | 语气、停顿、笑声——共情的主要载体 |
语音的交互质量标准也完全不同。文本聊天 3 秒响应算快,语音对话超过 1 秒的沉默就让人不安(想想电话那头的人半天不说话)。人类对话的自然响应间隙是 200~500ms——这就是语音 Agent 的延迟金标准。
全链路架构:两条路线的战争
路线一:级联架构(Cascade)——拼装的艺术
用户语音 ──→ VAD ──→ STT ──→ LLM ──→ TTS ──→ 播放
(断句) (转文字) (思考) (合成) (音频流)
延迟: ~0ms 200-400ms 500ms+ 200-300ms = 累计 1s+每个环节独立选型、独立优化,组合自由度最高。缺点是延迟叠加、且每级转换都丢信息——语气词、情绪、停顿在 STT 转文本时就没了。
路线二:原生多模态(Speech-to-Speech)——端到端的诱惑
用户语音 ──→ 统一多模态模型 ──→ 语音直接输出
延迟: 300-800ms 端到端GPT-4o Realtime、Gemini Live 走的路线。语音直接进、语音直接出,没有中间表示——情绪、笑声、语气的保真度碾压级联。代价:贵、闭源、不可组合(你没法把"最好的 STT + 最强的 LLM + 最像人的 TTS"拼起来)。
现实的选择
2026 年的生产现状:验证期用级联(可控、便宜、能换件),体验天花板用原生(延迟和拟真度碾压)。多数团队的路径是级联起步,等 Realtime API 价格降到可接受再切换。本文两条线都讲透。
STT:语音识别的三代技术
Whisper:开源标杆的架构
OpenAI 的 Whisper(2022)至今仍是开源 STT 的基线。核心是 Encoder-Decoder Transformer:
30 秒音频
↓ Mel 频谱图(把声波转成"图像"——时间×频率的二维表示)
↓ Encoder(像 ViT 处理图片一样处理频谱块)
↓ Decoder(自回归生成文本 token,附带时间戳)
→ 文本 + 段级时间戳关键设计决策造就了它的统治力:
- 680,000 小时多语言弱监督数据——不靠精细标注,靠互联网规模。多语言(99 种)、多任务(转写+翻译+语言识别+断句)一体
- 鲁棒性换精度:标点、口语填充词(嗯、啊)处理宽容,真实场景抗噪远超学术 SOTA
- 多模型尺寸:tiny(39M,实时可跑)→ large-v3(1.5B,精度天花板),延迟-精度自由选
流式识别:从"录完再转"到"边说边转"
Whisper 原生是 30 秒整段的离线模型。实时场景需要流式方案:
| 方案 | 原理 | 延迟 |
|---|---|---|
| Whisper + 滑窗 | 滚动缓冲区反复重转 | 高,但实现简单 |
| faster-whisper | CTranslate2 重写,4 倍速 | 离线提速,非流式 |
| whisper-stream / whisper.cpp | 分段 + 增量解码 | 1-2s |
| 专用流式引擎(Deepgram、AssemblyAI、讯飞) | RNN-T/Conformer 流式架构 | <300ms,商用首选 |
关键指标是部分假设延迟(partial hypothesis latency):用户说到一半,屏幕上字幕跟着长出来的速度。商用流式引擎能到 200ms 级,这是级联架构里 STT 环节的预算上限。
中文特有难题
- 同音字消歧:"全部/全不"、"基石/即实"——上下文窗口越大越准
- 中英混说:"帮我 call 一下 Lisa 确认 meeting 时间"——Whisper 和商用引擎处理差异大,选型必测
- 方言与口音:方言能力基本被国产引擎(讯飞、FunASR)垄断
VAD:断句的艺术——最容易被低估的环节
语音 Agent 要回答,先得知道用户什么时候说完了。这就是 VAD(Voice Activity Detection,语音活动检测)的任务。
为什么 VAD 决定生死
- 断句太急(用户只是换气)→ Agent 抢答,用户体验灾难
- 断句太慢(等 2 秒确认沉默)→ 响应延迟感知翻倍
- 断句错误是语音 Agent 差评的第一大来源,超过识别错误本身
主流方案
经典能量阈值: 音量 < 阈值持续 N ms → 判定停顿
↑ 嘈杂环境下误触发满天飞, 只能算玩具
Silero VAD: 轻量神经网络(1MB), CPU 上实时跑
↑ 开源标配, 精度和速度平衡好
语义 VAD (Semantic VAD): LLM 判断"这句话语义上说完了吗"
"我想订一张明天去北京的..."(停顿) → 语义未完,继续等
"明天天气怎么样?"(停顿) → 语义完整,立即触发
↑ OpenAI Realtime 的方案, 是 VAD 的终极形态工程配置的核心参数是尾部静音阈值(tail padding):
- 对话式场景:500~700ms 静音判完成
- 指令式场景(用户念指令给设备):300ms 即可
- 这个值没有银弹,必须按自己用户群的实际语速 A/B 测试
讲话人分离(Diarization)
客服场景双方都在说话,"谁说的哪句"要靠 diarization(如 pyannote)。纯 VAD 无法区分讲话人,多角色场景(会议纪要)这是必选项。
TTS:从"机器味"到"以假乱真"的五年
技术流派演进
拼接式(2000s): 录音库切片拼接, 导航语音的机械感来源
参数式(2010s): HMM 声学模型 + 声码器, 略自然但呆板
神经声码器(2017+): WaveNet 开启, 逐样本生成, 惊人自然度
↓
当前主流两代:
两阶段: 文本 → 中间表示(梅尔谱) → 声码器 → 波形 (VITS, FastSpeech2)
端到端: 文本 → 直接语音 token 自回归生成 (VALL-E, GPT-4o voice)流式合成:首包延迟是关键
TTS 的延迟指标不是"整句合成完的时间",而是首包延迟(time to first audio chunk)——听到第一个字的时间。现代引擎(ElevenLabs、Cartesia、火山引擎)都是分块流式输出:LLM 逐 token 生成,TTS 逐句甚至逐短语合成播放,边生成边播放。
LLM 流式输出 + TTS 流式合成 = 流水线并行,这是级联架构压延迟的核心手段:
串行(慢): LLM 生成完整回复(2s) → TTS 合成(1s) → 播放 首响 3s
流水线(快): LLM 出第一个短语(300ms) → TTS 立即合成(150ms) → 播放
同时 LLM 继续生成后续内容... 首响 450ms声音克隆与情感控制
- 零样本克隆:3~10 秒参考音频复刻音色(开源 XTTS、商用 ElevenLabs)
- SSML 与情感标签:
<break time="500ms"/>、[excited]控制停顿与语气 - 中文选型:国产引擎(火山、讯飞、MiniMax)中文自然度和英文厂商不在一个次元;反之英文场景别用国产
合规红线:克隆他人声音需授权。各国已立法(中国《生成式AI管理办法》、美国 NO FAKES Act),TTS 厂商都被要求留 watermark 追溯。
打断(Barge-in):语音 Agent 的成人礼
人类对话是全双工的——对方说错,你随时插话。语音 Agent 必须支持打断,否则就是倒退回按键菜单时代。
实现难度在哪
打断要求 Agent 在自己说话的同时监听用户:
1. TTS 正在播放
2. 用户开口说话(VAD 检测到人声)
3. 系统必须瞬间:
a. 停止播放(切断音频流, 已缓冲的几百 ms 也要清掉)
b. 丢弃 LLM 未完成的生成(省 token)
c. 清空对话状态中"被念了一半"的回复
d. 开始处理用户的新语音回声消除(AEC):技术上的隐藏 Boss
Agent 从扬声器播放的声音,会被麦克风重新采集——不做处理,Agent 会听到自己说话并把自己打断。解决方案是 AEC(Acoustic Echo Cancellation):
- 浏览器端:
getUserMedia的echoCancellation: true(WebRTC 自带) - 服务端混音场景:WebRTC 的 AEC3 或 Speex
- 手机扬声器外放场景最难(声学耦合强),是硬件级难题
在浏览器里做语音 Agent,WebRTC 是正解(呼应 WebRTC 篇):AEC、降噪、自动增益全部内置, getUserMedia 一行开启。
打断的体验调优
不是所有人声都该触发打断。防误触的过滤层次:
1. 时长过滤: <200ms 的声音突发(咳嗽/碰桌子)不打断
2. 音量过滤: 远低于当前说话能量的声音不打断
3. 语义过滤(高级): ASR 转写后判断是否有意义("等等"→打断, "嗯嗯"→不打断)OpenAI Realtime 的做法是把打断决策交给语义 VAD:用户发声且语义上构成干预,才算打断。
延迟预算:一场毫秒级的战争
把级联架构的端到端延迟拆开,逐环节分配预算(目标:800ms 首响):
┌──────────────────────────────────────────────────┐
│ 环节 预算 优化手段 │
├──────────────────────────────────────────────────┤
│ 音频采集+VAD ~100ms 客户端本地 VAD │
│ 上行传输 ~50ms WebRTC/UDP, 同机房 │
│ STT ~250ms 流式引擎, partial 复用 │
│ LLM 首 token ~300ms 小模型路由+prompt缓存 │
│ TTS 首包 ~150ms 流式合成 │
│ 下行传输+起播 ~50ms 分块下发 │
├──────────────────────────────────────────────────┤
│ 合计 ~900ms 已接近电话级体验 │
└──────────────────────────────────────────────────┘各环节的杀手锏:
- STT:别等 VAD 确认说完才转写——边说边转,VAD 只负责"定稿"
- LLM:意图路由用小模型(fast 档),闲聊快速通道,复杂任务再上大模型(呼应 Model Routing 篇);系统提示词启用 prompt caching
- TTS:首短语(甚至首词)即合成;预合成高频应答("好的,正在为您查询"这类填充语可以预录)——填充语是延迟的心理安慰剂,为你争取 500ms
- 传输:同机房部署全套(STT/LLM/TTS 越近越好),公网往返是延迟刺客
实战:级联架构最小可用实现
服务端(Node.js)核心骨架
typescript
import { WebSocketServer } from "ws";
import { createReadStream } from "fs";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
let sttStream = createSttStream(); // 流式 STT (Deepgram/AssemblyAI)
let ttsPlayer = null; // 当前播放任务(可打断)
let interruptCount = 0;
// 客户端二进制帧: 浏览器录音分块
ws.on("message", async (data, isBinary) => {
if (isBinary) {
sttStream.send(data); // 音频流式喂给 STT
return;
}
const msg = JSON.parse(data.toString());
if (msg.type === "interrupt") {
// 客户端本地 VAD 检测到用户插话 → 立即打断
ttsPlayer?.abort(); // 停止下发 TTS 音频
interruptCount++;
return;
}
});
// STT 定稿(用户说完一句)
sttStream.on("final", async (text) => {
// LLM 流式生成
const llmStream = await llm.chatStream([
...history,
{ role: "user", content: text },
]);
// 流水线: 按短语聚合, 逐短语 TTS
let phraseBuf = "";
for await (const chunk of llmStream) {
phraseBuf += chunk;
if (isPhraseBoundary(phraseBuf)) { // 遇到 , 。 ? 等
ttsPlayer = new TtsSession(ws);
await ttsPlayer.speak(phraseBuf); // 分块下发, 立即返回
phraseBuf = "";
}
}
if (phraseBuf) await ttsPlayer.speak(phraseBuf);
});
});
function isPhraseBoundary(s: string): boolean {
return /[。!?,,.!?]\s*$/.test(s);
}浏览器端:录音 + 本地 VAD
javascript
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true, // AEC: 防止听到自己的声音
noiseSuppression: true,
autoGainControl: true,
},
});
// 方案A: AudioWorklet 本地 VAD(毫秒级打断感知)
const vad = await MicVAD.new({
stream,
positiveSpeechThreshold: 0.5,
negativeSpeechThreshold: 0.35,
redemptionFrames: 8, // 防抖: 连续几帧无声才判定停顿
onSpeechStart: () => {
// 用户开口 → 若 Agent 正在说话, 发送打断信号
if (agentSpeaking) ws.send(JSON.stringify({ type: "interrupt" }));
},
onFrameProcessed: (probs) => {
// 语音帧实时转发服务端(含置信度)
},
});三个工程要点:
- 打断决策放客户端——本地 VAD 检测比服务端往返快 200ms+,这是打断体验的分水岭
- 短语级流水线——
isPhraseBoundary按标点切分 LLM 输出,TTS 不等完整回复 - AEC 依赖浏览器——自己写 AEC 是无底洞,交给 WebRTC 栈
原生路线:OpenAI Realtime 一行接入
typescript
const session = await client.beta.realtime.sessions.create({
model: "gpt-4o-realtime",
voice: "alloy",
instructions: "你是旅行助手,回答保持口语化、简短。",
turn_detection: { type: "server_vad" }, // 语义 VAD + 自动打断,全托管
});
// 之后双向音频流走 WebSocket, 模型自己处理: 断句/思考/合成/被打断原生路线把 VAD、打断、TTS 全部托管,代码量是级联的 1/10——代价是每个环节都不可换、不可调。
评估:语音 Agent 的质量体系
| 层 | 指标 | 说明 |
|---|---|---|
| STT | WER(词错误率) | 中文 <5% 可用,❤️% 优秀;必测自己的口音分布 |
| TTS | MOS(平均意见分)/ 自然度 | 人耳盲测 1-5 分;或用 UTMOS 自动评估 |
| 端到端 | 首响延迟 P95 | <800ms 电话级,<1.5s 可接受 |
| 端到端 | 打断成功率 / 误打断率 | 两个都要看,单看一个会被骗 |
| 任务 | 意图识别准确率 / 任务完成率 | 语音入口的最终商业指标 |
| 稳健性 | 噪声/方言/中英混说通过率 | 实验室数据在真实环境会腰斩 |
十大坑
- 断句阈值拍脑袋——500ms 不是银弹,用真实用户录音调,不同人群语速差异巨大
- 不做 AEC 上线——Agent 自己打断自己,用户以为闹鬼
- 打断只停播放不清状态——被念了一半的回复留在上下文里,模型后续逻辑错乱
- TTS 等完整回复——首响延迟直接 2s+,流水线是必选项不是优化项
- 忽视部分假设——等 STT final 才开始处理,浪费了 300ms 的流式红利
- 填充语歧视——"正在为您查询"这类预录应答是延迟伪装的大杀器,别嫌它low
- Whisper 跑实时——它是离线模型,流式场景用 faster-whisper 或商用流式引擎
- 声音克隆无授权——法律红线,商用必须拿到声音本人书面授权
- 只测安静环境——餐厅、地铁、车载的真实信噪比下,WER 翻倍是常态
- 盲目追原生多模态——Realtime API 按音频 token 计费,闲聊场景成本可能是级联的 5 倍;先算账
总结
实时语音 Agent 的技术栈,可以浓缩为一张延迟地图:
用户开口 ──VAD──> STT流式 ──> LLM首token ──> TTS首包 ──> 用户听到
0ms ~100ms ~350ms ~650ms ~800ms
↑ ↑ ↑
断句的艺术 模型路由 流水线并行
全程并行运行: 打断检测(AEC+VAD) ← 这是体验的成人礼两条路线的取舍终局判断:级联是工程,拼装与优化自由;原生是体验,拟真与延迟碾压。 2026 年的最优解是级联打底 + 高价值场景上原生。
至此,博客的交互版图补上了最后一块模态:文本(Tokenizer/Streaming)→ 图像(VLM)→ 语音(本文)。加上 WebRTC(传输)、Function Calling(工具)、Agent Memory(记忆),搭建一个"听得见、看得着、记得住、做得到"的完整 Agent 所需的全部拼图都齐了。
本文由小虾子 🦐 撰写