语音交互集成:唤醒 → 识别 → 大模型意图 → 播报
把"听得见—听得懂—说得出—能打断"四件事串成一条链:硬件唤醒词 KWS、带热词表的流式 ASR、 Qwen3.5-0.8B 大模型三分类意图、TTS 合成播报,以及 SPEAKING 状态下的四步打断复位(对应需求 R1 / R3 / R7 / R8)。
01问题引入:为什么"听见了"却"听不懂"?
前三个实验完成了需求、架构与接口的纸面设计,本章开始进入第 2 章"功能集成":先让设备真正听懂人话。 源讲义指出,让机器"听见"很容易(ASR 转文字),难的是把自然语言映射到有限的三个任务: 装货(load)、分拣(sort)、码垛(palletize)。用户可能说"把箱子装上去""分一下类""堆起来", 甚至因为方言口音把"码垛"说成"马舵"、把"分拣"说成"封建"。
如果靠关键字匹配,每种说法都要写一条规则,永远覆盖不全;而源讲义的需求 R3 明确规定: LLM 只做意图三分类,禁止关键字匹配,且大模型只负责"映射功能",固定话术由 TTS 播报。 另外 R7 要求任务结束必须语音播报,R8 要求播报途中能被一句话打断并四步复位——这些都是本实验要逐条打通的链条。
02原理通俗讲解:四人流水线传话
把语音链路想成餐厅的"传菜口",四个人接力:
顾客(用户)必须先喊"小飞小飞 / 小飞同学 / 芝麻开门",门童才回头。硬件 mic_array 直接在麦克风阵列上做唤醒(voice_node L844-846),不耗主机算力;没接阵列时才退到 sherpa_onnx 软件唤醒。暗号没喊对,后面的人根本不动。
唤醒后先播提示音(_play_prompt_then_start_asr L593-612),再开始识别。识别器用
modified_beam_search 解码 + hotwords.txt 热词表(6 条词条 score=2.0,热词提权让"码垛/分拣"这类术语不容易被听歪)。
_llm_intent_worker(L688-767)把识别文本发给本机 Qwen3.5-0.8B,提示词限定
只输出 load / sort / palletize / none 四选一。这正是 R3:"大模型只做功能映射,不做自由对话"。
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。
config/eai_factory.yaml 的 voice.intents 把 load/sort/palletize 分别映射到
/eai_factory/start_load、/start_sort、/start_palletize(Trigger 服务)。
ASR 侧用 intent_hints 把易混词往任务靠(如"马舵"→码垛、"封建"→分拣),热词表与提示词双管齐下压低误听率。
调度器完成信号 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动画讲解:状态机与四人流水线
07互动讲解:意图仲裁实验台
点击下面的按钮,模拟 LLM 返回不同结果时 _llm_intent_worker 与兜底逻辑的仲裁过程:
08实操步骤
curl http://127.0.0.1:8080/v1/models
# 返回模型清单即为在线,否则先启动 Qwen3.5-0.8B 推理服务# 热词表:6 条词条,score=2.0 cat hotwords.txt # intents 映射与唤醒词 grep -n -A6 "intents" config/eai_factory.yaml grep -n "wakeup_words" config/eai_factory.yaml
ros2 launch eai_factory eai_factory.launch.py start_voice:=true
ros2 service list | grep eai_factory 与日志确认三个 Trigger 服务被分别调用,播报对应固定话术。VOICE INTERRUPT — stopping ... 类日志,播放停止并回 LISTENING,随后可再次下发指令(R8)。09重点与易错点
| 现象 | 根因 | 处理 |
|---|---|---|
| 喊唤醒词无反应 | 未接 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总结与思考
四段接力、状态机调度,听懂的前提是每段只做自己的事。
热词守住识别端,兜底守住服务端,但主路径永远是 LLM 三分类。
自然语言的自由度最终收口为三个 Trigger 服务调用。
断播放、置标志、发话题、回 LISTENING,顺序不能乱。