实时语音 Agent 生产实战:用 Turn Detection、Two-Pass EOU 与 Barge-in 压缩对话空白时间
文本聊天里,100 毫秒级的差异通常不明显;语音对话不同。人类会把”对方说完以后多久开始回答”直接当成系统是否自然的一部分。
对一个典型的级联语音 Agent,可以把用户感知的首响应空白近似拆成:
T_gap ≈ T_endpoint + T_stt_finalize + T_llm_first_token + T_tts_first_audio + T_playback_buffer
很多团队只优化 T_llm_first_token,却留下了 500ms、800ms 甚至更长的静音确认窗口。结果是模型很快,产品听起来仍然迟钝。
真正的生产优化应该先回答三个问题:
- 什么时候可以认为用户这一轮真的说完了?
- 能不能在最终确认之前就提前启动下游计算?
- 用户在系统说话时重新开口,如何在几十到几百毫秒内让整个流水线一致地停下来?
这分别对应 Turn Detection、Two-Pass EOU 和 Barge-in。
核心原理一:Turn Detection 不是简单的静音计时器
VAD、STT Endpointing 和语义 Turn Detector 的区别
最简单的做法是 Voice Activity Detection:检测到连续一段静音,就判定用户结束发言。它成本低、响应快,但有一个天然缺陷:停顿不等于说完。
用户可能只是思考、换气,或者说出”我想查一下……嗯……上个月的账单”。如果系统在第一个停顿就抢答,体验反而比多等 200ms 更差。
生产系统通常存在三类方案:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| VAD-only | 成本低、响应快,但易误判停顿 | 追求极低延迟、语言覆盖优先 |
| STT Endpointing | 依赖 ASR 的 end-of-utterance 信号 | 常规对话流 |
| 语义 / 音频 Turn Detector | 结合声学甚至语义信息判断是否完成 | 多数语音 Agent 的推荐方案 |
LiveKit 当前文档明确区分了这些模式,并建议多数语音 Agent 使用 Turn Detector;其默认 endpointing 窗口在普通配置下为 0.5s ~ 3.0s,使用 audio turn detector 时默认可缩短到 0.3s ~ 2.5s。这些数字不是所有系统的最佳值,但说明了一个重要原则:结束判定需要下限,也需要兜底上限。
生产上不要只调一个 silence_ms
比固定静音阈值更可靠的配置方式,是把参数拆开:
turn_detection:
mode: semantic_audio
min_endpoint_delay_ms: 300
max_endpoint_delay_ms: 2500
vad_min_silence_ms: 300
force_commit_timeout_ms: 3000
这里的关键不是具体数字,而是四个边界:
- 太短,容易 false end-of-turn;
- 太长,增加首响应空白;
- 必须允许模型或 STT 提前给出高置信度结束信号;
- 必须有最大等待时间,不能让异常会话无限悬挂。
核心原理二:Two-Pass EOU 用”可取消的提前计算”换延迟
为什么需要两阶段结束判定
如果系统一定要等到”最终确认用户说完”以后才调用 LLM,就会天然损失整段确认窗口。
Two-Pass End of Utterance 的思路是:
- 第一阶段出现较短静音,产生 early EOU;
- 立即把当前 transcript 发给 LLM,开始推理;
- ASR 状态继续保留;
- 如果用户继续讲话,取消旧 LLM/TTS 任务,用新的 transcript 重启;
- 到达最终 EOU 后,当前轮次才正式提交。
NVIDIA Riva 的当前文档已经提供 Two-Pass EOU。其示例中,第一阶段 stop_history_eou 可设置在较短窗口,官方文档提到基于其测试建议值约为 240ms;最终 EOU 仍按更长窗口确认。这个机制本质上和 CPU 的 speculative execution 很像:先做可能有用的计算,但必须接受计算被作废。
不能只看首响应时间,还要看推理重启成本
提前触发一定会引入额外成本。用户一句话中如果有多次自然停顿,可能出现:
240ms silence -> LLM request A
user resumes -> cancel A
240ms silence -> LLM request B
user resumes -> cancel B
final EOU -> LLM request C
因此 Two-Pass EOU 的评估指标至少要包含:
early_eou_trigger_ratellm_restart_ratewasted_output_tokenscancel_latency_msfirst_agent_audio_after_final_speech_ms
NVIDIA ACE 的示例文档也特别提醒,提前 EOU 可能造成额外 LLM 调用和计算成本。生产环境不能只展示”首响降低了多少”,而隐藏了 GPU 成本放大。
核心原理三:Barge-in 是全链路取消,不是播放器暂停
用户重新开口时,系统真正要取消什么
很多 Demo 的 Barge-in 只做一件事:停止扬声器播放。这还不够。用户一旦开始插话,至少需要同步处理:
Speech start detected
|--> stop client playback buffer
|--> cancel TTS streaming
|--> cancel / suppress current LLM generation
|--> mark assistant turn as interrupted
|--> truncate conversation history to actually heard audio
|--> open new user turn
如果只停止播放器,LLM 和 TTS 后台仍然会继续消耗资源;更严重的是,系统可能把”用户根本没有听到的后半段回答”写入对话历史,导致下一轮模型误以为这些内容已经沟通过。
LiveKit 的 interruption 机制会在用户打断后停止 Agent Speech,并自动把历史截断到用户实际听到的部分。Deepgram 的 Twilio 示例则强调,服务端停止生成后,客户端仍需要清空 Twilio 已缓冲但尚未播放的音频,否则会出现”用户已经打断,旧声音还继续播放”的尾巴。
Barge-in 要有 generation id
推荐给每一轮生成分配单调递增的 generation_id:
interface VoiceTurn {
turnId: string;
generationId: number;
state: "listening" | "thinking" | "speaking" | "interrupted" | "done";
}
function onAudioChunk(chunk: AudioChunk, generationId: number) {
if (generationId !== session.activeGenerationId) return;
playback.enqueue(chunk);
}
这样即使取消信号和网络包乱序,旧 TTS 音频也不会在新一轮会话里”复活”。
工程落地:把语音会话建模成状态机
最容易出问题的实现,是把 ASR、LLM、TTS 三个 WebSocket 当成三个独立回调系统。生产版本应该显式维护会话状态:
LISTENING
| early EOU
v
SPECULATIVE_THINKING
| final EOU
+--------------------+
| |
| user resumes v
+----> CANCELLED <-- THINKING
|
v
SPEAKING
| user barge-in
+---------------+-------------+
| |
v v
INTERRUPTED IDLE
状态机至少负责:
- 哪个 transcript 是 provisional,哪个是 final;
- 当前允许哪个 generation 输出音频;
- 什么事件触发取消;
- 取消以后哪些 buffer 必须清空;
- 对话历史最终提交到哪个字符 / 时间戳;
- 网络重连后是否允许旧事件继续生效。
建议的生产延迟指标
不要只记录一个 voice_latency。至少按阶段拆分:
| 指标 | 含义 |
|---|---|
speech_end_to_turn_commit_ms | 用户最后有效语音到本轮提交 |
turn_commit_to_llm_first_token_ms | Turn commit 到 LLM 首 token |
llm_first_token_to_tts_first_audio_ms | 首 token 到首个可播放音频 |
speech_end_to_agent_audio_ms | 用户结束说话到听见 Agent 的总空白 |
barge_in_detect_ms | 用户重新开口到系统识别打断 |
barge_in_silence_ms | 打断信号到旧音频真正停止 |
false_end_rate | 误判用户说完的比例 |
restart_rate | 提前推理后被取消重启的比例 |
wasted_tokens_per_turn | 因取消浪费的 LLM 输出 token |
这里最值得盯的是 speech_end_to_agent_audio_ms 的 P95/P99。平均值很容易被大量短句掩盖,而真正让用户觉得”这个机器人不自然”的往往是少数长停顿。
一个可落地的调优顺序
第一阶段:先建立真实分段指标
先把 ASR、LLM、TTS、播放缓冲全部打时间戳,不要先改参数。否则只能靠主观听感优化。
第二阶段:压缩 Endpointing,但同步监控 false end
逐步缩短最小 endpoint delay,并按语言、口音、场景分桶观察:
- 短指令;
- 长句;
- 犹豫和停顿;
- 电话 8kHz 音频;
- 嘈杂环境;
- 中文夹英文、数字和专有名词。
任何”延迟下降”都必须同时看 false_end_rate。
第三阶段:再上 Two-Pass EOU
只有在取消机制可靠后,才允许 early EOU 触发 LLM。否则一个错误的提前判定会产生重复回答、旧音频串流和会话历史错乱。
第四阶段:最后优化 Barge-in
Barge-in 的目标不是检测越敏感越好,而是:
- 真插话能迅速停止;
- 咳嗽、键盘声、电视声不频繁误触发;
- 后台生成同步取消;
- 用户下一轮上下文保持一致。
Deepgram 当前文档也提醒,纯客户端能量型 VAD 容易被环境噪音触发;其建议在适用场景下使用模型级 speech/turn detection,以降低误打断。
适用场景
这套方法尤其适合:
- 电话客服 Agent:用户对长静音极其敏感,并且经常主动插话。
- 销售与外呼 Agent:自然轮次和打断能力直接影响对话完成率。
- 车载 / 设备语音助手:噪音高,固定静音阈值容易误判。
- 实时翻译与会议助手:需要尽早开始处理,但又不能过早提交错误分句。
- 游戏 NPC / 陪伴式语音应用:对节奏、抢话和回话时机要求更高。
对于离线录音转写、批量语音摘要等场景,则没必要承担复杂的 speculative turn control。
常见误区
误区一:把 VAD 参数调到极小就能得到最低延迟。 阈值过小会把正常思考停顿判成句号。最终结果往往是首响更快,但抢话更多、重启更多。
误区二:只测 LLM TTFT。 TTFT 很重要,但语音体验还包含 endpointing 和 TTS 首音频。一个 TTFT 150ms 的系统,也可能因为 800ms 静音确认而显得迟钝。
误区三:Two-Pass EOU 是免费优化。 提前计算可能被取消。必须把无效请求和浪费 token 计入容量规划。
误区四:Barge-in 等于 mute。 真正的 Barge-in 必须联动播放缓冲、TTS、LLM 和 conversation state。
误区五:所有语言共用一套 Endpointing。 语言节奏、停顿习惯、STT 能力不同。应至少按语言和音频通道做分桶配置与验证。
上线检查
- 已记录 speech-end、turn-commit、LLM-first-token、TTS-first-audio、playback-start 时间戳。
- 已建立
speech_end_to_agent_audio_ms的 P50/P95/P99。 - 已测试长停顿、犹豫、噪音、重口音和低采样率音频。
- 已统计 false-end 和 premature-response 比例。
- Two-Pass EOU 的提前推理可以在用户继续说话时可靠取消。
- 取消后旧 generation 的 token 和音频都不会重新进入输出流。
- Barge-in 后播放器缓冲会同步清空。
- 对话历史只保留用户实际听到的 Agent 内容。
- 已监控 speculative restart 带来的额外 token / GPU 成本。
- Endpointing 参数按语言、音频通道或业务场景支持独立配置。
- 已准备关闭 early EOU、退回 final-only 模式的快速开关。
参考资料
- LiveKit, Turns overview — https://docs.livekit.io/agents/logic/turns/
- LiveKit, Turn detector — https://docs.livekit.io/agents/logic/turns/turn-detector/
- LiveKit, Adaptive interruption handling — https://docs.livekit.io/agents/logic/turns/adaptive-interruption-handling/
- NVIDIA Riva, ASR Overview — https://docs.nvidia.com/deeplearning/riva/user-guide/docs/asr/asr-overview.html
- NVIDIA Riva, Building and Deploying ASR Pipelines — https://docs.nvidia.com/deeplearning/riva/user-guide/docs/public/asr/asr-pipeline-configuration.html
- Deepgram, Build a Flux-enabled Voice Agent — https://developers.deepgram.com/docs/flux/agent
- Deepgram, Audio Preprocessing & Barge-In — https://developers.deepgram.com/voice-agent/optimize/audio-preprocessing-barge-in
- Deepgram, Flux Quickstart — https://developers.deepgram.com/docs/flux/quickstart