语音中的工具调用与状态

V5
实战手册 · 语音与实时智能体

语音中的工具调用与状态:不留空气死寂地调工具,靠耳朵确认。

文本智能体可以想四秒,没人察觉。一个沉默四秒的语音智能体,在来电者看来,已经卡死或挂断。工具调用是语音智能体最显眼地崩坏之处,因为聊天里不可见的后端延迟,在语音里变成对话中一个能听见的窟窿。任务是让工具调用对话化:永不沉默、能靠耳朵确认、且能在一通有状态的通话里恢复。

STEP 1

在工具调用之前、期间、之后都说话。

语音工具调用里杠杆率最高的单一技术:在工具延迟落地之前就发出一句口头应答,让来电者听到一个有人样的停顿,而非一条死线。

caller: "what's my balance?"
  t+0.2s  agent: "Let me pull that up..."   <-- cover starts
  t+0.3s  [tool: get_balance() dispatched]
  t+1.6s  [tool returns]
  t+1.8s  agent: "Your balance is $2,481.10."
silence the caller actually experienced: ~0ms

OpenAI 的 Realtime API 直接把这个暴露为前导语(preambles)——函数调用在途时模型说一句短的填充。前导语必须足够通用,无论结果如何都为真("我查一下"),绝不能是对答案的猜测(工具返回前就说"看起来你都付清了",正是智能体撒谎的方式)。

STEP 2

工具调用是异步的;对话不在它上面阻塞。

在文本智能体里循环自然是串行的:调工具、等、继续。在语音里你不能在等待时冻住音频通道。工具调用作为一个异步任务跑;智能体保持在线,能在后端干活时应答、回答一个澄清子问题,或吸收一次打断。

# voice/tool_runtime.py — never block the audio loop
async def on_tool_call(call, session):
    await session.say(filler_for(call))      # cover now
    task = asyncio.create_task(run(call))     # off the path
    while not task.done():
        if session.user_started_talking():
            await session.yield_floor()       # caller wins
        await asyncio.sleep(0.05)
    return task.result()

把相互独立的工具调用并行跑,绝不要串行——三个串行的 400 ms 调用是一个 1.2 s 窟窿;三个并行的是 400 ms。语音去掉了文本智能体里掩盖串行工具延迟的那点余地,所以那里你可以偷懒的并行,这里是强制的。

STEP 3

靠耳朵确认副作用,在它发生之前。

语音智能体没法弹一个确认对话框。确认就是那个口头轮次,而对任何不可逆动作它是强制的:用来电者的措辞把那些有后果的参数读回去,并在工具触发前要求一个明确的"是"。

# confirm BEFORE the mutating call, not after
agent: "So that's cancelling the 7pm reservation
        for four at Nopa tonight — shall I?"
caller: "yes"
  → only NOW dispatch cancel_reservation(...)

这与修转写税的那个读回是同一个(见 speech-stack):在一条有损音频信道上,确认轮次同时是听错意图错的纠错码。仅对只读、可逆的动作才跳过它。

STEP 4

状态必须熬过一通有状态、可打断的通话。

一通语音通话是一段长的有状态会话,来电者可以在任何点打断、改主意、回头改一个槽位("其实,改成 8 点")。智能体的状态不是"到目前为止的转写文本"——它是结构化的槽位与待办动作集合,且必须跟踪什么被确认、什么是暂定、以及来电者实际听到了什么。

  • 槽位状态,而非转写状态。跟踪 {party: 4, time: 8pm (was 7pm), date: today},而非每轮重新解析的一墙文本。
  • 已确认 vs 暂定。你读回后被来电者纠正过的值是已确认;只是顺带提过的是暂定,任何变更前必须确认。
  • 说出口 vs 已生成。若一次打断在句子中途截断了智能体,状态必须反映出声说过的,否则智能体会引用一个来电者从未听到的数字。
STEP 5

慢的与失败的工具是对话事件。

一个耗八秒、或失败的后端,不是一个记下来咽掉的异常——来电者在线上,会听见它。给每个工具调用一个对话级超时与一个口头应急。

  • 带口头出口的超时。若工具超出预算,诚实说出来("这个比平常久——我回拨给你,还是您稍等一下?"),而非永远念填充词。
  • 降级,别冻住。失败时,给出次优路径(回拨、转人工、重试)——绝不沉默,绝不无限"还在处理那个"。
  • 打断下的幂等。工具中途的一次打断或挂断不得重复扣款或重复预订;变更类调用需要幂等键,与文本智能体里完全一样,只是这里的失败还能被听见。
STEP 6

诚实的权衡。

你为掩盖工具延迟而加的每一句遮掩话,都是来电者没要的对话时间;用填充词把通话撑长本身就是一种失败模式——目标是快的工具,口头遮掩是对慢的征税,而非修它的替代品。