综合项目 / 智慧工厂装货分拣码垛 / EXP-04
EXP-04 · 第 2 章 · 综合性 · 6 学时

语音交互集成:唤醒 → 识别 → 大模型意图 → 播报

把"听得见—听得懂—说得出—能打断"四件事串成一条链:硬件唤醒词 KWS、带热词表的流式 ASR、 Qwen3.5-0.8B 大模型三分类意图、TTS 合成播报,以及 SPEAKING 状态下的四步打断复位(对应需求 R1 / R3 / R7 / R8)。

🎙️ 语音链路 KWS 硬件唤醒 热词表 modified_beam_search LLM 三分类意图 SPEAKING 打断四步

01问题引入:为什么"听见了"却"听不懂"?

前三个实验完成了需求、架构与接口的纸面设计,本章开始进入第 2 章"功能集成":先让设备真正听懂人话。 源讲义指出,让机器"听见"很容易(ASR 转文字),难的是把自然语言映射到有限的三个任务: 装货(load)、分拣(sort)、码垛(palletize)。用户可能说"把箱子装上去""分一下类""堆起来", 甚至因为方言口音把"码垛"说成"马舵"、把"分拣"说成"封建"。

如果靠关键字匹配,每种说法都要写一条规则,永远覆盖不全;而源讲义的需求 R3 明确规定: LLM 只做意图三分类,禁止关键字匹配,且大模型只负责"映射功能",固定话术由 TTS 播报。 另外 R7 要求任务结束必须语音播报,R8 要求播报途中能被一句话打断并四步复位——这些都是本实验要逐条打通的链条。

02原理通俗讲解:四人流水线传话

把语音链路想成餐厅的"传菜口",四个人接力:

KWS · 门童
只认一句暗号

顾客(用户)必须先喊"小飞小飞 / 小飞同学 / 芝麻开门",门童才回头。硬件 mic_array 直接在麦克风阵列上做唤醒(voice_node L844-846),不耗主机算力;没接阵列时才退到 sherpa_onnx 软件唤醒。暗号没喊对,后面的人根本不动。

ASR · 速记员
照着热词本听写

唤醒后先播提示音(_play_prompt_then_start_asr L593-612),再开始识别。识别器用 modified_beam_search 解码 + hotwords.txt 热词表(6 条词条 score=2.0,热词提权让"码垛/分拣"这类术语不容易被听歪)。

LLM · 前台接待
只回答一个英文单词

_llm_intent_worker(L688-767)把识别文本发给本机 Qwen3.5-0.8B,提示词限定 只输出 load / sort / palletize / none 四选一。这正是 R3:"大模型只做功能映射,不做自由对话"。

TTS · 播音员
固定话术,不即兴

sherpa_onnx Matcha 声学模型 + vocos 声码器(L409-432)合成固定播报。任务完成时由调度器按任务名给出 "装货完成 / 分拣完成 / 码垛完成"(scheduler L202-208),播报内容不经过大模型,保证话术可控(R7)。

兜底 大模型服务没起来怎么办?_keyword_fallback(L769-786)在 LLM 结果超时/异常时按关键字兜底, 且两路结果在 L746-754 仲裁——兜底永远只是兜底,正常路径必须让 LLM 出结果,否则 R3 就形同虚设。

03关联知识点

状态机
四个状态来回走

IDLE(待机)→ LISTENING(收音)→ PROCESSING(LLM 推理)→ SPEAKING(播报),任何状态被打断都按 R8 四步复位回 LISTENING。

intents 映射
yaml 里的一张表

config/eai_factory.yaml 的 voice.intents 把 load/sort/palletize 分别映射到 /eai_factory/start_load、/start_sort、/start_palletize(Trigger 服务)。

意图提示词
intent_hints

ASR 侧用 intent_hints 把易混词往任务靠(如"马舵"→码垛、"封建"→分拣),热词表与提示词双管齐下压低误听率。

eai_speak 通道
单向文字播报

调度器完成信号 done → /eai_factory/speak(std_msgs/String)→ voice_node 合成并播放,与前端识别解耦。

04实验目标

  • 跑通"唤醒 → 提示音 → ASR → LLM 三分类 → TTS 播报"完整链路,喊一句话能够触发对应任务服务。
  • 验证三个唤醒词(小飞小飞 / 小飞同学 / 芝麻开门)全部生效,理解硬件唤醒与软件唤醒的差异。
  • 对比热词表开关前后的识别效果,解释 modified_beam_search 与热词提权的作用。
  • 在播报途中说出第二句话,观察 LISTENING/PROCESSING/SPEAKING 状态迁移与 interrupt 话题,确认 R8 四步复位。
  • 完成 TC1~TC6 测试用例,按源讲义评分表(15/20/25/15/25)自测。

05实验环境与准备

  • 工业相机 + 麦克风阵列(mic_array 支持硬件唤醒;无阵列则确认软件唤醒已编入启动分支)。
  • 本机 LLM 服务已启动:模型 Qwen3.5-0.8B(约 500MB 常驻内存),推理端口 8080;用 curl http://127.0.0.1:8080/v1/models 确认服务在线。
  • 热词文件 hotwords.txt 就位(6 条,score=2.0),intent_hints 在 yaml 中配置完成。
  • 启动主程序:ros2 launch eai_factory eai_factory.launch.py start_voice:=true (voice 开关默认即为 true,视觉/抓取等可按需关闭以缩小变量)。
  • 观察终端日志,voice_node 启动时会按顺序打印唤醒词加载、ASR 初始化、TTS 初始化、LLM 连接等日志行。

06动画讲解:状态机与四人流水线

voice_node 状态机(R8 打断复位) IDLE 待机 LISTENING ASR 收音 PROCESSING LLM 推理 SPEAKING TTS 播报 唤醒 文本 意图 interrupt 打断(R8 复位四步:中断播放 → 置 interrupt → 发 interrupt=true → 回 LISTENING) 端到端链路:四段接力,各司其职 KWS 唤醒词 小飞小飞 / 芝麻开门 ASR + 热词表 modified_beam_search LLM 三分类 Qwen3.5-0.8B · 8080 TTS 固定话术 Matcha + vocos
图 4-1 上:voice_node 四态状态机,红色虚线为 R8 打断复位路径;下:KWS→ASR→LLM→TTS 四段接力,光点走完一圈即一次完整交互。

07互动讲解:意图仲裁实验台

点击下面的按钮,模拟 LLM 返回不同结果时 _llm_intent_worker 与兜底逻辑的仲裁过程:

🎛️ 意图仲裁台(LLM 结果 vs 关键字兜底)
选择一种场景,观察仲裁分支与最终发布的服务调用……
要点 无论哪条路径,最终动作都落在 intents 表的三个 Trigger 服务上——意图识别的自由度在收口处被压成三个确定动作,这就是 R1(单轮)+R3(只分类)的组合效果。

08实操步骤

1
确认 LLM 服务在线
curl http://127.0.0.1:8080/v1/models
# 返回模型清单即为在线,否则先启动 Qwen3.5-0.8B 推理服务
2
检查热词与意图映射
# 热词表:6 条词条,score=2.0
cat hotwords.txt
# intents 映射与唤醒词
grep -n -A6 "intents" config/eai_factory.yaml
grep -n "wakeup_words" config/eai_factory.yaml
3
带语音启动全栈
ros2 launch eai_factory eai_factory.launch.py start_voice:=true
4
TC1 唤醒三连测:依次喊"小飞小飞""小飞同学""芝麻开门",每次都应听到提示音并看到 LISTENING 输出。
5
TC2~TC3 意图三分类:依次说"开始装货""执行分拣""开始码垛",用 ros2 service list | grep eai_factory 与日志确认三个 Trigger 服务被分别调用,播报对应固定话术。
6
TC4 热词对比:临时停用热词表再念"码垛",观察误识别率变化,之后恢复。
7
TC5 打断测试:触发一次任务播报,在 SPEAKING 期间说出"小飞小飞",终端应出现 VOICE INTERRUPT — stopping ... 类日志,播放停止并回 LISTENING,随后可再次下发指令(R8)。
8
TC6 全链路时序:不看终端,仅凭"唤醒音 → 识别停顿 → 播报"三段听感复述状态机走向,并与日志时间戳对照。

09重点与易错点

诊断先分清是哪一段坏了:唤醒没反应查 KWS/阵列,识别错查热词,意图错查 LLM 是否在线,没播报查 eai_speak 订阅与 TTS 初始化日志。
现象根因处理
喊唤醒词无反应未接 mic_array 却期望硬件唤醒,或 start_voice:=false确认启动开关与唤醒分支,看启动日志加载了哪个唤醒源
"码垛"被识别成"马舵"热词表未生效或 score 未提权核对 hotwords.txt(6 条 score=2.0)与 intent_hints
"分拣"听成"封建"同上;声学相近词仅靠 ASR 兜不住热词+intent_hints 双份配置,测试时按热词优先
说了指令没动作LLM 服务 8080 未启动,且兜底也没命中关键字curl /v1/models 探活后再测,兜底不可当主路径
播报无法打断打断分支只在 SPEAKING 生效,时序没踩上;或播放器未接入 interrupt对照 L636-661 四步,确认 interrupt_event 与话题发布
完成播报话术不对自定义播报绕过了 done→speak 固定映射沿用 scheduler L202-208 的"装货/分拣/码垛完成"三句固定话术(R7)
  • 评分自测(源讲义 15/20/25/15/25):链路完整性 15、唤醒与识别 20、意图三分类 25、打断复位 15、全链路时序与报告 25。
  • 报告必须附终端日志时间戳,证明"唤醒—识别—意图—动作—播报"五段顺序无倒挂。
  • LLM 提示词输出若带多余句子,先在 worker 侧做取词处理,不要改提示词以外的解析逻辑。

10总结与思考

一条链
KWS → ASR → LLM → TTS

四段接力、状态机调度,听懂的前提是每段只做自己的事。

两道防线
热词提权 + 关键字兜底

热词守住识别端,兜底守住服务端,但主路径永远是 LLM 三分类。

三个确定
load / sort / palletize

自然语言的自由度最终收口为三个 Trigger 服务调用。

四步复位
R8 打断

断播放、置标志、发话题、回 LISTENING,顺序不能乱。

思考 1 现在是"唤醒 → 说一句 → 停"的两段式交互,如果改成持续听写(无唤醒词一直识别),会带来什么新问题? 提示:误触发率、隐私、以及打断语义还成不成立。
思考 2 如果要新增第 4 个任务"盘点(inventory)",热词表、intent_hints、LLM 提示词、intents 映射、固定播报话术 五处分别要改哪里?哪一处不改会导致"识别对了但没动作"?