轮次与打断:决定谁说话,以及被打断时停下。
人类在对话中不假思索地协商轮次——他们听出一个念头的结尾、对打断让步、容忍短暂的叠音。语音智能体必须把这一切显式地、实时地、从一条音频流里、且没有共享社交模型地做出来。做错了,智能体要么碾压来电者,要么僵坐着等一句早已说完的话。这是语音里最难的交互问题,而它大体上不是模型问题。
VAD 不是端点检测,把二者混淆是根因 bug。
两件不同的活,却被不断混为一谈:
- VAD(语音活动检测)回答"此刻这一音频帧里有语音吗?"——一个低层、亚 100 ms 的信号。它不知道一个轮次是否结束;思考停顿同样是静音。
- 端点检测(轮次结束检测)回答"来电者说完了吗,以至于智能体现在该回应了?"——一个建在 VAD 之上、并且理想情况下加上转写文本与含义的决策。
仅靠原始 VAD 静音做端点检测,是语音智能体的经典失效。600 ms 静音阈值会切断任何停下来想的人("我的账号是… 4 4 7…"),1500 ms 的又让智能体像睡着了。没有一个固定静音值能同时既有响应又有耐心——这正是固定超时落败的原因。
语义端点检测:用词语,不只是用静音。
2025 年代的答案——由 LiveKit、AssemblyAI 与 OpenAI 的语义 VAD 轮次检测交付——是把部分转写文本喂进一个小模型,问它"这听起来像一句完整话语吗?"——再据答案调制静音阈值。
# turn/endpoint.py — silence threshold is dynamic def end_of_turn(partial, silence_ms): p = complete_utterance_prob(partial) # small model if p > 0.85: return silence_ms > 120 # sounds done: fire fast if p < 0.30: return silence_ms > 1400 # "uh, my number is..." wait return silence_ms > 600 # unsure: middle ground
降调的"今天天气怎样"约 120 ms 就结束一个轮次;带上扬尾音的"我的卡号是"则该给来电者一秒多的思考余地。同样的静音,相反的决策——因为词语承载了静音单独无法承载的意图。
打断:来电者随时可以打断,智能体必须让位。
若来电者在智能体说话时开口,智能体必须在两三百毫秒内停下——而不是说完它那句。一个压着打断继续说的智能体被体验为无礼,更糟的是坏掉了;人们会挂断。在任何严肃的语音产品里,打断不可妥协。
agent speaking ............ caller starts talking
|
+-- detect speech (VAD, <100ms)
+-- STOP agent audio immediately
+-- FLUSH unsent TTS / cancel response
+-- discard the killed turn from state
+-- listen; the caller now has the floor
微妙之处在状态:智能体说了十句中的三句,然后被打断。它实际说出口的——而不是它生成的——才是来电者听到的。你的对话状态必须反映这个被截断的现实,否则智能体会引用它从未真正出声说过的东西。
回声消除是轮次的前提,而非音频的锦上添花。
智能体自己的声音会经由来电者的麦克风返回(尤其在免提时)。没有声学回声消除,VAD 会把智能体自己的语音看成"来电者在说话",触发一次假打断,于是智能体把自己打断进一个结巴循环。稳健的 AEC 对轮次是承重的——一份轮次 bug 报告很常常就是一个回声 bug。
叠音、附和与全双工。
真实对话并非严格半双工。人们在对方说话时说"嗯哼""对"——附和(backchannel)——这些不是打断;把每一声"嗯哼"当作打断会让智能体不停停下。一个好的轮次管理器会区分一个延续信号("嗯哼",继续)与一次真正争夺话语权("等等——其实…",现在让位)。
较新的原生模型正走向真正的全双工:像人类那样同时听与说。它更自然,也相当难做对;在它稳健之前,多数生产栈跑一个有纪律的半双工轮次管理器,配快速打断与显式附和处理,因为一个可预测的智能体胜过一个不可预测的。
诚实的权衡。
每一个轮次参数都是同一个旋钮——急切 vs 耐心——而且没有全局设置:催收 IVR 想要快且果断,哀伤抚慰热线想要慢且耐心,唯一的错误是给两者发同一套调参。