语音 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: