语音 Agent 掩盖缓慢工具调用延迟的方法,就是绝不让线路陷入沉默。它在工具运行时先说一句简短的话,在长时间等待期间提供进度更新,在工具于后台工作的同时保持对话继续,并尽早启动工具,从而减少需要掩盖的延迟。

在这篇博客中,我们将学习语音 Agent 如何掩盖缓慢工具调用的延迟。我们还将了解为什么缓慢的工具调用在语音通话中危害如此之大,Agent 如何立即说一句简短的话,如何在长时间等待期间提供进度更新,如何在工具于后台运行时保持交谈,如何尽早启动工具以从源头减少延迟,以及当工具耗时过长时它会说些什么。

我们将涵盖以下内容:

  • 问题所在:语音通话中缓慢的工具调用
  • 什么是语音 Agent 中的工具调用?
  • 技巧 1:立即说一句简短的话
  • 技巧 2:在长时间等待期间提供进度更新
  • 技巧 3:在工具于后台运行时保持交谈
  • 技巧 4:尽早启动工具
  • 技巧 5:让工具本身更快
  • 如果工具耗时过长怎么办?
  • 用代码将所有内容整合起来

我是 Amit Shekhar,Outcome School 的创始人。我教导和指导过许多开发者,他们的努力为他们赢得了高薪的技术工作,帮助许多科技公司解决了他们的独特问题,并创建了许多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。

我在 Outcome School 教授 AI 和机器学习。

让我们开始吧。

问题所在:语音通话中缓慢的工具调用

语音 Agent 是一种我们用声音与之交谈、它也用声音回应的 AI Agent。许多语音 Agent 以三步流水线的方式工作:

  • 语音转文本(STT): 将用户所说的话转换为文本。
  • LLM(大语言模型): 读取该文本并决定回复内容。
  • 文本转语音(TTS): 将回复文本转换回语音。

一个优秀的语音 Agent 通常力求在大约一秒内回复。人们在对话中习惯了快速回应,所以每多等一秒都很容易被察觉。

现在,假设用户问道:

“能帮我查一下我飞往德里的 AI 101 航班准点吗?”

Agent 无法凭记忆回答这个问题。它必须查询航空公司的系统。这个查询可能需要两秒、三秒,甚至十秒。

在聊天界面上,等待几秒钟没问题,因为我们可以看到加载动画。但在语音通话中,没有加载动画,只有沉默。

而电话中的沉默对我们来说意味着一些事情。沉默两三秒后,用户就会开始想:

  • “通话是不是断了?”
  • “它听到我说话了吗?”

所以,当用户说“喂?”或重复问题时,Agent 听到这段新语音就会困惑,整个对话就崩了。

所以,真正的问题不在于延迟本身,而在于延迟所造成的沉默。

什么是语音 Agent 中的工具调用?

在深入技巧之前,我们必须先了解什么是工具调用。

工具调用是指 Agent 在 LLM 之外运行一些代码,比如查询数据库或调用另一个服务,以获取信息或执行某个操作。

LLM 从不亲自运行工具。LLM 只会推荐使用哪个工具以及输入什么。我们的代码(也就是我们所说的 Agent)负责运行工具,并把结果返回给 LLM。然后,LLM 利用该结果来生成最终回复。

以我们的航班示例为例,流程如下所示:

User: "Is my flight AI 101 to Delhi on time?"
        ↓
LLM recommends: check_flight_status(flight="AI 101")
        ↓
Agent runs the tool (this takes 3 seconds)
        ↓
Tool result: { "status": "delayed", "new_time": "6:40 PM" }
        ↓
LLM writes: "Your flight is delayed. It now leaves at 6:40 PM."
        ↓
TTS speaks the reply

在这里可以看到,工具的 3 秒耗时叠加在 STT、LLM 和 TTS 的正常延迟之上。这正是我们需要隐藏的延迟。

现在,让我们逐一学习这些技巧。

技巧 1:立即说一句简短的话

第一个也是最简单的技巧是,在 Agent 一知道需要调用工具时就立刻说点什么。

以服务台的工作人员为例。当我们询问航班信息时,他们不会一边打字一边沉默地盯着我们。他们会说“好的,我帮您查一下”,然后才开始打字。这几句话告诉我们,他们听到了我们的问题,并且正在处理。

语音 Agent 也可以做到完全一样。有两种常见的实现方式。

方式一:LLM 在调用工具的同时写一句简短的话。

我们在 LLM 的指令中告诉它:“在调用任何工具之前,先对用户说一句简短的话。”这样,在同一次回复中,LLM 会生成一句简短的话,比如“好的,我帮您查一下航班。”,同时生成工具调用。Agent 立即将这句话发送给 TTS,并同时运行工具。

优点: 这句话与请求内容相符。对于航班,它会说“我帮您查一下航班”。对于退款,它会说“我帮您看一下退款”。

缺点: 我们要等到 LLM 开始生成并且 TTS 将其转换为语音后才能听到这句话,所以它比现成的话要晚一点到达。

方式二:Agent 播放现成的话。

Agent 保存一份现成话术的小列表,比如“请稍等。”或“我帮您查一下。”这些话的音频已经提前做好,所以只要 LLM 推荐了某个工具,Agent 就能立即播放其中一句。

优点: 这是最快的方式,因为那一刻不需要额外的 LLM 或 TTS 处理。

缺点: 同样的几句话会反复出现,时间久了听起来可能很机械。所以我们必须在多句话之间轮换。

无论采用哪种方式,用户都能很快听到声音,沉默就消失了。

注意: 如果某个工具通常在不到一秒内就返回,Agent 可以跳过这句简短的话。否则,用户会先听到“请稍等”,紧接着就是答案,听起来会很奇怪。

我们有一篇关于 语音 Agent 中填充语如何掩盖延迟 的详细博客,深入解释了这一点。

计划转型进入 AI 领域?

无论你今天在哪个技术领域工作,只要付出努力,你都可以转型到以下这些角色:

  • AI 工程师
  • 前沿部署工程师
  • AI Agent 工程师
  • Harness 工程师
  • 语音 AI 工程师
  • AI 架构师
  • LLM 工程师
  • LLM 推理工程师
  • 生成式 AI 工程师
  • MLOps 工程师
  • 机器学习工程师
  • 深度学习工程师

Outcome School 的 AI 与机器学习课程 能带你到达那里。

技巧 2:在长时间等待期间提供进度更新

当工具只需 2 到 3 秒时,技巧 1 效果很好。但有些工具耗时更长。例如,预订机票或查询一长串记录可能需要 10 秒甚至更久。

只说一句简短的话的问题在于,在“让我帮你查一下”之后,可能还有 8 秒的沉默。沉默问题又回来了。

于是,进度更新就来救场了。

进度更新是 Agent 在长时间等待期间按固定时间点说出的简短话语,用来告诉用户工作仍在进行中。

Agent 使用一个简单的计时器。思路如下:

工具启动后经过的时间Agent 说的话
0 秒“好的,我来查一下您的航班。”
4 秒“我还在查询,需要一点时间。”
8 秒“马上就好,感谢您的耐心等待。”

这里可以注意到,每一句话都不同。如果 Agent 反复说同一句话,听起来会很重复。

如果工具在 5 秒时完成,Agent 会停止计时器并说出真正的答案。后面的那些话就不会被说出来。

一些语音 Agent 还会在等待期间播放轻柔的背景音,比如轻微的键盘敲击声。这就像等待音乐一样,告诉用户线路仍然保持连接。但它必须安静而简单,否则会变得令人烦躁。

注意: 如果 Agent 事先知道某个工具总是很慢,它可以在一开始就说:“这通常需要大约 10 秒。”当用户知道要等多久时,等待往往会感觉更短。

给你的一个快速提示

无论你在哪个技术领域工作,都要熟悉这些主题:

  • LLM
  • RAG
  • MCP
  • Agent
  • 微调
  • 量化

我们把它们全部整合在一个视频中:

AI 工程详解:LLM、RAG、MCP、Agent、微调与量化

不用中断阅读——先收藏起来,等有空的时候再看。未来的你会感谢现在的你。

现在,让我们回到主题。

技巧 3:在工具于后台运行时保持交谈

在前两种技术中,Agent 在等待时会说话,但对话仍处于暂停状态。在工具完成之前,用户无法提出其他问题。

现在,让我们来了解一种更好的方法。在这种方法中,Agent 在后台启动工具,并继续与用户交谈。当结果到达时,Agent 会将其带入对话中。

让我们看一个例子:

User:  "Book a table for two at 8 PM."
Agent: "Sure, I am booking your table now."
        ↓
(booking tool starts in the background, it takes 10 seconds)
        ↓
Agent: "While I do that, should I text you the confirmation?"
User:  "Yes, please."
Agent: "Sure, I will text it to you."
        ↓
(booking tool finishes)
        ↓
Agent: "Your table for two at 8 PM is booked. The text is on its way."

在这里,我们可以看到用户从不处于沉默等待状态。预订的 10 秒钟被一个有用的问答填满,而不是等待。

要实现这一点,Agent 必须做两件事:

  • 它不能在工具运行时阻塞对话。我们的代码将工具作为单独的任务启动,并让 LLM 继续回复用户。
  • 当结果到达时,它必须将结果添加到对话中。然后,LLM 会在合适的时机——即 Agent 说完当前句子之后——告知用户。

一些实时语音模型 API 直接支持这一点。模型可以在工具调用仍在等待结果的同时继续与用户交谈。

优点: 这是最自然的体验,因为对话从不中断。

缺点: 构建难度更大。Agent 必须记住哪些工具仍在运行,并且 LLM 不能编造尚未到达的结果。如果用户提前询问,LLM 必须说明仍在等待中。

技巧 4:尽早启动工具

到目前为止,所有技术都是在隐藏延迟。现在,让我们看看如何让延迟本身变得更小。

通常情况下,Agent 会等待用户说完。然后,STT 给出完整文本,LLM 读取它,之后工具才会启动。

但很多时候,早在用户说完之前,就已经清楚需要哪个工具了。

假设用户说:

“你能帮我查一下我的航班 AI 101 吗……我记得大概六点左右起飞……”

在用户还在说话时,STT 会一点一点地把文本交给 Agent。Agent 可以将这段部分文本展示给 LLM。当用户说到“查一下我的航班 AI 101”时,LLM 已经知道它需要航班状态工具,输入为“AI 101”。句子的其余部分不会改变所需的工具。

因此,Agent 可以在用户还在说话时就提前启动工具。这被称为推测执行,意思是在我们还没有完全确定需要它之前就开始工作,因为我们很可能需要它。

流程如下所示:

User is still speaking: "check my flight AI 101..."
        ↓
Agent starts check_flight_status(flight="AI 101") early
        ↓
User finishes speaking
        ↓
Tool result is already ready or almost ready
        ↓
Agent replies with almost no wait

在这里,工具在用户还在说话的时间内运行。因此,大部分延迟都消除了。如果完整句子最终需要不同的工具,Agent 会丢弃提前得到的结果。

非常重要:只有当运行工具毫无害处时,我们才能提前启动它。读取数据是安全的。查询航班状态是安全的,因为如果我们不需要结果,直接丢弃即可。但订票、转账或取消订单是不安全的。如果用户说“订一张票……哦不,等等,别订了”,我们绝不能已经把票订好了。所以,我们只对读取数据且不改变任何内容的工具使用投机执行。

如果你想深入了解 Agent 中的工具使用、如何设计实时语音 AI Agent,以及从零开始构建 AI 编程 Agent,我们在 Outcome School 的 AI 与机器学习课程中应有尽有。

技巧 5:让工具本身更快

我们还必须尽可能让工具本身更快。几种常见方法:

  • 缓存结果:如果很多用户询问同样的内容,比如今天的营业时间,Agent 会将答案保留一段时间并重复使用,而不是再次调用工具。
  • 在通话开始时加载数据:如果用户来电咨询自己的账户,Agent 可以在通话一开始就加载账户详情,甚至在用户开口询问之前。
  • 并行运行工具:如果 Agent 需要两个工具,比如航班状态和目的地天气,它会同时运行两者,而不是一个接一个。

这些方法每一项都能减少几秒等待时间,因此需要隐藏的时间也更少。

如果工具耗时过长怎么办?

有时,工具太慢,进度更新已经不够用了。

因此,Agent 必须有一个超时,即一个时间限制,超过后它就停止等待。

当超时发生时,Agent 不能只是沉默或说“出错了”。它必须诚实地告知用户,并给出一条可行的路径。例如:

  • “航空公司系统目前没有响应。我一收到状态就通过短信发给你,好吗?”
  • “这次比平时耗时更长。您希望我继续尝试,还是我可以帮您处理其他事情?”

在这里,我们可以看到 Agent 把一次失败转化为一个清晰的下一步,让用户了解情况。

保持更新:订阅我们的新闻通讯,将最新的 AI 与机器学习博客直接送达您的邮箱。

用代码将所有内容整合起来

现在,让我们用 Python 把技巧 1、技巧 2 和超时机制的一个简单版本放在一起看看。为了便于理解,我们会保持代码简短。

import asyncio
import random

QUICK_LINES = ["Sure, let me check that.", "One moment, please.", "Let me look that up."]
PROGRESS_LINES = ["I am still checking.", "Almost there, thanks for waiting."]

async def run_tool_with_cover(tool, args, speak):
    # start the tool as a separate task, so we do not block
    task = asyncio.create_task(tool(**args))

    # Technique 1: speak a short line right away
    await speak(random.choice(QUICK_LINES))

    # Technique 2: progress updates every 4 seconds
    for line in PROGRESS_LINES:
        done, _ = await asyncio.wait({task}, timeout=4)
        if done:
            return task.result()
        await speak(line)

    # timeout: give the tool 4 more seconds, then stop waiting
    done, _ = await asyncio.wait({task}, timeout=4)
    if done:
        return task.result()
    task.cancel()
    return None

在这里,我们有:

  • QUICK_LINES 和 PROGRESS_LINES,它们是 Agent 可以直接说出的现成语句。
  • speak,这是一个函数,将一句话发送给 TTS 并播放给用户。
  • asyncio.create_task(...),它将工具作为单独的任务启动。我们先启动它,这样在 Agent 说出简短语句的同时,工具也在运行。
  • asyncio.wait({task}, timeout=4),它最多等待该工具 4 秒。如果工具完成了,我们立即返回结果。如果没有完成,我们就说出下一条进度提示语。
  • 最后,如果在大约等待 12 秒后工具仍未完成,我们就取消它并返回 None。

现在,让我们看看 Agent 是如何使用它的:

result = await run_tool_with_cover(check_flight_status, {"flight": "AI 101"}, speak)

if result is None:
    await speak("The airline system is not responding right now. "
                "Would you like me to try again?")
else:
    # give the result to the LLM, and it writes the final reply
    reply = await llm_reply_with_tool_result(result)
    await speak(reply)

在这里,我们可以看到 Agent 总是会说点什么。如果工具很快,用户会先听到一句简短的话,然后听到答案。如果工具较慢,用户会听到进度更新。如果工具太慢,用户会听到一句坦诚的话,以及一个可行的后续方案。

现在,我们应该已经理解了语音 Agent 是如何掩盖慢速工具调用的延迟的,以及哪种技巧适合哪种类型的工具。

常见问题

语音 Agent 是否应该在每次工具调用前都说一句简短的话?

不应该。如果一个工具通常在不到一秒内就返回,Agent 可以跳过这句简短的话。否则,用户会先听到“请稍等”,紧接着就是答案,这听起来很奇怪。当工具需要几秒钟、否则线路会陷入沉默时,这句简短的话才有用。

在工具运行时说话能让工具调用变快吗?

不能。一句简短的话、进度更新,以及在工具于后台运行时说话,都只是掩盖延迟,并不能缩小延迟。要真正缩短延迟,Agent 可以提前启动工具、缓存结果、在通话开始时加载数据,以及并行运行工具。

在语音 Agent 中,哪些工具可以安全地提前启动?

只有那些只读取数据、不改变任何东西的工具,比如查询航班状态。如果提前得到的结果不需要,Agent 直接把它丢弃即可。而订票、转账或取消订单的工具绝不能提前启动,因为用户在说完这句话之前仍可能改变主意。

如果用户询问一个尚未返回的结果,语音 Agent 应该怎么说?

它必须说明自己仍在等待。当工具在后台运行时,LLM 绝不能编造一个尚未返回的结果。Agent 会跟踪哪些工具仍在运行,只有在结果被添加到对话中之后,才会把结果告诉用户。

为什么语音 Agent 的进度更新应该使用不同的话术?

如果 Agent 在漫长的等待中反复说同一句话,听起来会很重复。因此,每次进度更新都使用不同的话术,比如先说“我还在查询”,再说“马上就好,感谢您的耐心等待。”如果某个工具总是很慢,Agent 也可以在开始时说明它通常需要多长时间。

今天就到这里。

感谢

Amit Shekhar Outcome School 创始人

你可以通过以下方式与我联系:

关注 Outcome School: