- 作者
-
-
- 姓名
- Amit Shekhar
- 发布于
-
语音 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: