- 作者
-
-
- 姓名
- Amit Shekhar
- 发布于
-
语音 AI Agent 中的插话(barge-in)是指用户能够在 Agent 说话时打断它,而 Agent 能够立即停止说话并开始倾听的能力。
在这篇博客中,我们将学习语音 AI Agent 中的插话。我们还会了解这个词的来源、为什么机器在说话时很难听到用户的声音、Agent 如何区分真正的打断与噪音和“嗯嗯”、用户插话的那一刻 Agent 必须做什么、它如何记住用户实际听到了什么,以及为什么这一切都必须快速完成。
我们将涵盖以下内容:
- 什么是插话(barge-in)?
- barge-in 一词的来源
- 语音 AI Agent 如何说话和倾听
- 问题 1:Agent 听到了自己的声音
- 问题 2:并非每个声音都是打断
- Agent 如何检测插话
- 插话发生后 Agent 做什么
- 记住用户实际听到了什么
- 为什么插话必须快速
- 完整流程演示
我是 Amit Shekhar,Outcome School 的创始人,我曾教导和指导过许多开发者,他们的努力让他们获得了高薪的技术工作,帮助许多科技公司解决了他们的独特问题,并创建了许多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。
我在 Outcome School 教授 AI 和机器学习。
让我们开始吧。
什么是插话(barge-in)?
插话是指用户在语音 AI Agent 还在说话时开始说话,而 Agent 停止自己的语音以便倾听用户。
语音 AI Agent 是一种通过语音与我们交流的系统。我们说话,它倾听,它思考,然后它回应。客服电话机器人和语音助手都是语音 AI Agent。
学习这个概念的最好方法是通过一个例子。
假设我们拨打一个外卖客服热线,Agent 开始说话:
Agent: Thank you for calling. I can help you with new orders,
order tracking, refunds, address changes, and...
User: Refund.
Agent: Sure. Let's start your refund. What is your order number?在这里,用户已经知道自己想要什么,在列表念到一半时说了“退款”。Agent 停了下来,倾听,然后转向退款处理。
这就是插话。
人类一直在这样做。当朋友给我们指路而我们已经知道那个地方时,我们会说:“是的,我知道。”朋友就会停下来。一个好的语音 AI Agent 必须表现得一样。
插话是称为话轮转换(turn-taking)这一更大任务的一部分。我们有一篇关于语音 AI Agent 中的话轮转换的详细博客,解释了 Agent 如何决定轮到谁说话。
barge-in 一词的来源
“Barge in”的意思是突然闯入并打断。用户在 Agent 的发言回合中“闯入”。
这个词来自老式电话系统。我们都遇到过那种电话菜单:“按 1 查询账单,按 2 转接客服。”这类系统被称为 IVR 系统。IVR 是 Interactive Voice Response(交互式语音应答)的缩写。在早期的 IVR 系统中,我们必须等完整菜单播完才能按键。后来,IVR 系统允许来电者在消息播放中途按键或说话,系统会停止播放。这个功能被称为 barge-in(插话)。
现代语音 AI Agent 用同一个词表达同样的概念。区别在于,Agent 现在说出的是由 AI 模型生成的句子,而不是固定的录音菜单。
语音 AI Agent 如何说话和倾听
在深入讲解 barge-in 的工作原理之前,我们必须先了解语音 AI Agent 是如何构建的。
大多数语音 AI Agent 遵循如下所示的流水线:
+-------------------+
| User speaks |
+-------------------+
↓
+-------------------+
| STT |
| (speech to text) |
+-------------------+
↓
+-------------------+
| LLM |
| (writes reply) |
+-------------------+
↓
+-------------------+
| TTS |
| (text to speech) |
+-------------------+
↓
+-------------------+
| Speaker plays |
| the reply |
+-------------------+其中包括:
- STT(语音转文本): 将用户的语音转换为文本。
- LLM(大语言模型): 读取文本并生成回复。
- TTS(文本转语音): 将回复文本转换为语音。
还有一个与 barge-in 相关的重要部分,叫做 VAD,即 Voice Activity Detection(语音活动检测)。VAD 以小块为单位监听麦克风音频,例如每 20 或 30 毫秒一次,并且只回答一个问题:“现在有人在说话吗,是还是否?”
在简单的 Agent 中,当 Agent 正在说话时,麦克风会被关闭或忽略。Agent 先说话,然后倾听,然后再说话。这就像对讲机一样,同一时间只能有一方说话。
这样的 Agent 无法实现 barge-in。因此,要支持 barge-in,Agent 必须在说话的同时保持倾听。也就是说,麦克风、VAD 和 STT 在 Agent 说话期间要持续运行。
但随之而来会出现以下问题。
问题 1:Agent 听到了自己的声音
假设我们在手机上使用语音 Agent,并且扬声器处于开启状态。麦克风距离扬声器只有几厘米。因此,麦克风会拾取 Agent 自己的声音。
此时,VAD 听到了人声。这其实是 Agent 自己的声音,但 VAD 并不知道这一点。它会说:“是的,有人在说话。”于是 Agent 以为用户插话了,便停止了说话。
结果,Agent 不断地打断自己。这被称为 self-interruption(自我打断)。
解决这个问题的方法是声学回声消除(Acoustic Echo Cancellation),通常称为 AEC。
Agent 确切地知道自己发送到扬声器的音频是什么,因为那段音频就是它自己生成的。因此,AEC 会获取这段已知的音频,预测它回传到麦克风时的声音,然后从麦克风信号中将其减去。
+--------------------------+
| Microphone audio |
| (user + agent's echo) |
+--------------------------+
↓
+--------------------------+
| AEC subtracts the |
| agent's own audio |
+--------------------------+
↓
+--------------------------+
| Clean audio |
| (mostly the user) |
+--------------------------+
↓
+--------------------------+
| VAD |
+--------------------------+由此可以看到,Agent 自己的声音在音频到达 VAD 之前就被移除了。因此,VAD 听到的基本上就只有用户的声音了。
手机、浏览器和操作系统通常都提供回声消除(AEC)。但它从来都不是完美的。大音量的扬声器、空旷的大房间,或者质量较差的手机,都可能让一些回声泄漏进来。因此,一个好的 Agent 不会把单次短促的声音当作打断(barge-in)。这就引出了下一个问题。
问题 2:并非每个声音都是打断
即使音频很干净,也不是每一个到达麦克风的声音都意味着“别说了”。
我们来看几种情况:
- 附和语(backchannel): 人们在倾听时会发出一些简短的声音,比如“嗯”“好的”“对”或“唔”。这些表示“我在听,请继续”,而不是“停下”。
- 背景噪音: 关门声、狗叫声、房间里开着的电视,或者汽车喇叭声。
- 咳嗽和呼吸声: 用户咳嗽或清嗓子。
- 其他人: 房间里的另一个人在与用户交谈。
如果 Agent 对每一种声音都停下来,它就会不断打断自己的句子。当 Agent 因为一个并非真正打断的声音而停下来时,这被称为误打断(false barge-in)。
因此,Agent 需要避免两种相反的错误:
| 错误 | 发生的情况 | 用户的感受 |
|---|---|---|
| 误打断 | Agent 因为噪音、咳嗽或“嗯”而停下来 | Agent 不断打断自己 |
| 漏打断 | 用户想说话时,Agent 却继续说个不停 | Agent 不倾听用户 |
计划转型进入 AI 领域?
无论你今天在哪个技术领域工作,只要付出努力,你都可以转型到以下这些角色:
- AI 工程师
- 前沿部署工程师
- AI Agent 工程师
- Harness 工程师
- 语音 AI 工程师
- AI 架构师
- LLM 工程师
- LLM 推理工程师
- 生成式 AI 工程师
- MLOps 工程师
- 机器学习工程师
- 深度学习工程师
Outcome School 的 AI 与机器学习课程将带你实现这一目标。
Agent 如何检测插话
我们从最简单到最完整,逐一介绍这些方法。
方法 1:听到任何语音就停止
只要 VAD 判定“有人在说话”,Agent 就立即停止,即使声音只持续了不到一秒。
优点: 反应非常快。用户能立刻被听到。
缺点: 它会因为咳嗽、“嗯”和泄漏的回声而停止。误打断非常常见。
这种方法的问题在于它会对每一个细小的声音都做出反应。我们来看看下一种方法如何解决这个问题。
方法 2:等待足够的语音
只有当用户说话达到最短时长(例如半秒),或说出最少数量的词(例如两三个词)时,Agent 才停止。词数来自 STT,即使 Agent 正在说话,STT 也在持续把用户的语音转成文字。
因此,一声短促的咳嗽或一个快速的“唔”不会超过阈值。而像“等等,我想修改我的地址”这样一句真正的话会很快超过阈值。
优点: 误判插话的情况要少得多。
缺点: 它会在 Agent 停止前增加一点延迟。而且像“好的,好的,是的”这样较长的附和仍可能超过限制。
这种方法的问题在于,它只衡量用户说了多少,而不是用户想表达什么。让我们看看下一种方法如何解决这个问题。
方法 3:理解语义
Agent 会查看用户说的话,并判断这些话是插话还是附和。
- “嗯”、“好的”、“对”、“是的,继续” -> 附和,继续说话
- “等等”、“停下”、“不,这不对”、“其实我想要退款” -> 插话,停下并倾听
这可以通过一个简单的附和词列表来实现,也可以用一个经过训练的小模型将用户的语音标记为“插话”或“继续”。一些较新的系统会使用同时分析音频和文字的模型。
优点: 它能正确处理附和。
缺点: 它需要先获取文字,所以比单独使用 VAD 稍慢一些。
在实践中,Agent 会将这些方法结合起来。VAD 提供快速的初步信号,Agent 随即准备停止。然后,最小语音检查和语义检查会确认这是否是一次真正的插话。语义检查还允许像“停”这样的单个词通过,即使它比最小长度更短。
给你的小提示
无论你在哪个技术领域工作,都要熟悉这些主题:
- LLM
- RAG
- MCP
- Agent
- 微调
- 量化
我们把它们整合在一个视频中:
AI 工程详解:LLM、RAG、MCP、Agent、微调与量化
不必中断阅读——先收藏起来,有空时再看。未来的你会感谢现在的你。
现在,让我们回到正题。
插话发生后 Agent 做什么
现在,假设 Agent 已确认用户插话了。此时 Agent 必须停止一切正在生成旧回复的工作。
问题在于,旧回复并不存在于某一个地方,而是分布在整个流水线中:
- LLM 可能仍在生成回复的剩余部分。
- TTS 可能仍在将文本转换为音频。
- 已生成的音频可能正在缓冲区中等待,缓冲区是存放待播放音频的小型等待区域。
- 扬声器可能正在播放一段音频。
如果 Agent 只停止 LLM,已缓冲的音频仍会播放。用户会在说“停”之后听到 Agent 继续说话。因此,Agent 必须把它们全部停止:先停扬声器,再停缓冲区,然后停 TTS,最后停 LLM。之后,它会听完用户的完整句子,再开始新的回复。
我们有一篇关于句子分块如何更快地将 LLM 输出送入 TTS的详细博客,逐步讲解了回复文本如何从 LLM 传输到 TTS。
我们可以把逻辑写成如下形式:
def on_user_started_speaking(agent):
if not agent.is_speaking:
return # normal turn, nothing to interrupt
if not is_real_interruption(agent.user_audio, agent.user_text):
return # cough, noise, or "uh-huh", keep talking
agent.speaker.stop() # stop the sound right away
agent.audio_buffer.clear() # drop audio waiting to play
agent.tts.cancel() # stop making new audio
agent.llm.cancel() # stop writing the old reply
agent.mark_heard_part() # save what the user actually heard
agent.is_speaking = False
agent.start_listening()其中包括:
- agent.is_speaking 告诉我们 Agent 当前是否正在说话。如果不是,那么这就是一个正常的用户轮次。
- is_real_interruption 是上一节中的检查。对于噪音和附和声,它返回 False。
- 我们先停止扬声器,让用户尽快听不到 Agent 的声音。
- agent.mark_heard_part() 保存用户实际听到的回复内容。我们将在下一节中学习这个。
- 最后,agent.start_listening() 让 Agent 听到用户的完整句子。
注意: 这段代码只是为了便于理解。真实的 Agent 会在独立的任务中同时运行这些部分,并使用其框架自带的取消函数。
保持更新: 订阅我们的新闻通讯,将我们最新的 AI 和机器学习博客直接发送到您的收件箱。
记住用户实际听到了什么
现在,下一个重要问题是:在插话之后,Agent 对自己的回复记住了什么?
假设 LLM 写下了这个完整回复:
Your order will arrive at 7 PM. The delivery partner is Ravi. You can track the order in the app. Your total is 450 rupees.
LLM 生成文本的速度通常远快于扬声器将其播放为音频的速度。因此,完整文本往往在音频播放结束之前很久就已就绪。但用户在只听到以下内容后就插话了:
Your order will arrive at 7 PM. The delivery partner is...
现在,如果 Agent 将完整回复保存到对话历史中,LLM 会认为用户知道配送伙伴的名字和总金额。之后,用户问:“多少钱?”而 Agent 回答:“正如我告诉你的,是 450 卢比。”但用户从未听到过这句话。
因此,Agent 必须将其保存的回复裁剪到实际播放的部分。
+---------------------------------------+
| Full reply written by the LLM |
| (4 sentences) |
+---------------------------------------+
↓
+---------------------------------------+
| Part played before barge-in |
| (1.5 sentences) |
+---------------------------------------+
↓
+---------------------------------------+
| Saved in conversation history |
| "Your order will arrive at 7 PM. |
| The delivery partner is..." |
+---------------------------------------+Agent 如何知道哪部分被播放了?答案是时间。Agent 知道音频何时开始播放以及何时停止。一些 TTS 系统还会返回词级时间戳,告知每个词被说出的时间。这样,Agent 就可以将停止时间匹配到最后一个被说出的词。
例如,OpenAI Realtime API 提供了
conversation.item.truncate
事件。Agent 发送其音频被播放了多少毫秒,API 会裁剪该回复的其余部分,包括未播放部分的文本。
一些 Agent 还会在历史中添加一条小注释,比如“用户在此处打断”,以便 LLM 理解为什么它的句子不完整。
要学习 Agent 中的记忆、Agent 架构,以及如何设计实时语音 AI Agent,请查看 Outcome School 的 AI 和机器学习课程。
为什么插话必须快速
当我们说“等一下”之后,对方还继续说了两秒钟,我们会感到被忽视。语音 AI Agent 也是如此。从用户开始说话到 Agent 安静下来之间的时间必须很短。但在这个链条中,许多小的延迟会累积起来:
- VAD 需要几个小的音频片段才能确定确实有语音。
- 确认检查需要一些时间,尤其是在等待词语识别时。
- 已经发送到电话网络或扬声器的音频仍可能继续播放片刻。
因此,一个好的 Agent 会保持这条链路的每个环节都尽量短。它使用较小的音频缓冲区,让等待播放的音频非常少。它还可以在 VAD 触发时立即降低音量,在打断确认后完全停止,如果是误报则恢复正常音量。
这里我们可以看到一个权衡。停止得越快,误报的打断就越多。停止前等待得越久,误报的打断就越少,但响应会更慢。我们必须根据使用场景来调整。背景噪音较多的电话线路需要等待更久。安静的应用内助手则可以更快停止。
完整流程演示
现在,让我们用一个完整的示例把所有内容整合起来。
用户致电一个外卖配送 Agent,问道:“我的订单在哪里?”
第 1 步: Agent 的 LLM 生成回复,TTS 开始播报:“您的订单将于晚上 7 点送达。配送员是 Ravi。您可以在应用中跟踪订单……”
第 2 步: 麦克风、VAD 和 STT 持续运行。AEC 从麦克风音频中去除 Agent 自己的声音。
第 3 步: 用户在第一句话之后说了“嗯哼”。VAD 触发,但检查发现这是一个简短的附和。Agent 继续讲话。
第 4 步: 用户接着说:“等等,我能改一下地址吗?”VAD 触发,语音足够长,而且“等等”和“改地址”这些词表明这是一次打断。打断得到确认。
第 5 步: Agent 停止扬声器,清空音频缓冲区,取消 TTS,并取消 LLM。
第 6 步: Agent 只保存用户已经听到的部分:“您的订单将于晚上 7 点送达。配送员是 Ravi。您可以在应用中跟踪……”
第 7 步: STT 将用户的完整句子转换为文本。LLM 获得更新后的历史记录并回复:“好的。请问新地址是什么?”
现在我们应该已经理解了语音 AI Agent 中的打断是什么、Agent 如何检测它,以及用户打断时 Agent 会做什么。
常见问题
为什么简单的语音 AI Agent 无法支持打断?
简单的语音 AI Agent 在自己说话时会关闭或忽略麦克风,就像对讲机一样,同一时间只能一方讲话。因此,它在说话期间听不到用户。要支持打断,麦克风、VAD 和 STT 必须在 Agent 说话时也持续运行。
什么是误报打断?
误报打断是指 Agent 因为一个并非真正打断的声音而停止讲话。这可能是一个像“嗯哼”这样的附和、一声咳嗽、背景噪音、房间里的其他人,或者 Agent 自己的回声。误报打断太多会让 Agent 不断打断自己的句子。
当用户说“嗯哼”时,语音 AI Agent 应该停止讲话吗?
不是。像“嗯”、“好的”、“对”这样的词属于附和语。它们的意思是“我在听,请继续”。Agent 会检查用户说的话,使用一个附和语词表或一个小模型,遇到附和语时继续说话,而在遇到真正的打断(如“等等”或“停下”)时才停止。
为什么 Agent 在取消 LLM 之前先停止扬声器?
Agent 先停止扬声器,是为了让用户尽可能快地不再听到它的声音。如果 Agent 只停止了 LLM,缓冲区中已经等待播放的音频仍会继续播放,用户会在说完“停下”之后还听到 Agent 继续说话。所以,Agent 会先停止扬声器,然后是缓冲区,接着是 TTS,最后才是 LLM。
在 OpenAI Realtime API 中,conversation.item.truncate 是做什么的?
该
conversation.item.truncate
事件让 Agent 告知 API 它的音频实际播放了多少毫秒。API 随后会截断该回复的剩余部分,包括未播放部分的文本。这样可以让对话历史与用户实际听到的内容保持一致。
本次就到这里。
谢谢
Amit Shekhar Outcome School 创始人
你可以通过以下方式与我联系:
关注 Outcome School: