LabHub
开始
学习 学习路径 课程

语音 AI 智能体 — 会听、会查、会说的流水线

用声音驱动的工具 — 状态机·确认提问·重试·转交人工

在 LabHub 中继续学习

一句话总结

语音智能体的事故,大多出在工具上。没有听到“是”这个回答就创建了预约,把超时的请求重发一遍,结果预约变成了两个,没听懂却反复问同一个问题。所以能做什么由状态机决定,模型只负责把用户的话转换成槽位(日期、时间、姓名)。不可撤销的工具只在确认问题之后才调用,超时不重发。连续两次没听懂就转交给人工,转交时要把已经听到的内容概括后一起交过去。

为什么需要它

如果只用一个 LLM 来做电话预约助手,就要把整段对话都交给模型,还要把“下一步做什么”也交给它决定。我们让这个镜像中的 0.5B 模型用 json_schema 做意图分类,结果格式完全正确,但 8 个句子里只答对了 1 个——几乎所有句子都被分类成了 enum 的第一个值“book”(本模块实验第 2 步会亲自测量)。用语法强制格式,并不能强制内容。如果让这样的模型来判断“现在可以预约了吗”,即使用户回答“不是”,也可能创建出预约。

工作原理

填槽与状态。预约需要的槽位有三个:日期、时间、姓名。状态按 LISTEN → ASK_DAY → ASK_TIME → ASK_NAME → CONFIRM → DONE 流转,并且在任何状态下都可以转到 HANDOFF(交给人工)。如果用户一次说出“周二上午 10 点,名字是 Jamie”,就没有空槽位了,直接进入 CONFIRM。由于状态决定了能做什么,所以 book 只有在 CONFIRM 中听到“是”时才会被调用——无论模型说了什么,结构上都是如此。

确认问题中的“不是”。“不是,改成两点半”不是拒绝,而是修改。如果其中带有新的时间,就只改那个槽位并重新确认;如果没有任何信息,就不知道哪里错了,所以从时间开始重新询问。语音的误识别比文字更多,因此在执行不可撤销的操作之前复述确认,不是可选项。

重试与退避。工具错误分为两类。超时(ToolTimeout)有可能通过重发同一个请求解决。拒绝(ToolError,例如那个时段刚刚被订满)则是重发也会得到同样的答案。对于只读工具(查找空闲时间)的超时,以 0.5 秒、1.0 秒这样逐渐拉长的间隔最多再调用两次,仍然不行就不要死守,直接转交。在电话里,等待的那几秒就是沉默。

不可撤销的工具。如果预约(book)以超时结束,那不是“没成功”,而是“不知道”。服务器可能已经创建了预约,只是响应来得晚。重发的话,预约就会变成两个。所以对不可撤销的工具,超时时不重试,而是转交给人工确认(如果是带有幂等键的 API,可以用同一个键重发来安全地确认——不是这样的 API 就不要这么做)。拒绝(没有空位)则重新查找空闲时间,再重新询问。

转交给人工。转交时要遵守三点。用户要找人工时,随时立即转交。连续两次没听懂,就不再尝试第三次。而且要把已经听到的槽位概括后一并转交——让接收的人从头再问一遍,是最糟糕的转交。

测试靠记录,而不是录音。测试智能体时,如果比较话语的措辞,每次改变表达,测试就会失败。应当接入带有故障计划的模拟工具和模拟的睡眠(sleep),比较状态流转和工具调用记录。重试的退避也不是真的去等待,而是用“打算休息多久”来确认。

在现场相遇的样子

LangGraph 课程用图来处理同样的想法——节点改变状态,条件边决定下一步,在需要人工批准的地方停下来。MCP 服务器课程则从工具这一侧守住同样的边界——工具自己收窄权限,并对破坏性操作要求确认。语音智能体处于这两者之间,并且增加了一个约束:人没有屏幕,只能靠耳朵来确认。

规则解析器也并不完美。本课程的解析器会把“I need to see the doctor on Thursday afternoon”读成提供信息(inform),而不是预约(book)——因为列表里没有表示“预约”的词。即便如此,在状态机里也不会出大事故。日期已经填好,所以会询问下一个空槽位(时间),最终还是要经过确认问题。即使理解模块出错,不可撤销的事情也会停在确认这一关——这就是使用状态机的原因。即使把理解模块换成 LLM,这个结构也保持不变。变化的只是填槽的那个部件。

下一项实验要做什么

向模拟的诊所预约 API 注入故障,写下工具的性质。测量 LLM 和规则解析器的意图正确率。把预约智能体做成状态机,并逐步添加功能——顺利流程、确认时的“不是”、只读工具的重试、转交给人工、不可撤销工具的失败。评分器会用你的 agent.py 亲自运行九个场景。