拓冰建站拓冰建站
首页 / 资讯中心 / 正文

机器人语音交互实战:离线命令词与在线多轮对话混合架构解析

做机器人语音交互有一个绕不开的取舍离线方案反应快、不怕断网但是笨在线方案聪明、能闲聊天但是慢、依赖网络每轮调用都在烧钱。我前前后后做了三个原型最后定下来的是WT2606A做离线唤醒和固定指令、ESP32做主控、云端ASR加大模型做开放域多轮对话的混合架构。这篇文章不是官方文档复述是我从零开始把200条离线命令词和在线多轮对话塞进一台小机器人之后沉淀下来的完整思路包括命令词规划、硬件接线、UART协议对接、离线在线仲裁、会话管理以及几个在文档里根本查不到的坑。1. 先从产品需求说起为什么要做离线在线双引擎1.1 纯离线方案的短板我在做的是一台桌面陪伴机器人核心场景有三个用户叫它名字能响应、能听懂往前走走讲个笑话定个闹钟这类固定指令、能接住用户随口问的开放性问题。最开始我只有WT2606A离线命令词方案词条做了80条识别速度确实好基本在300毫秒内就能出结果断网也能用。但问题也很快冒出来用户不讲词条里的话。我说暂停一下词条里只有停止识别器给我的结果要么是误判成停止要么是未命中。用户不会去背你的命令表这是离线方案最大的天花板。新增一个功能要重新录词、重新生成固件、重新烧录。我一天能迭代三次就得烧录三次维护成本全耗在这条流水线上。所有问答都是预先录好的音频用户多问两句为什么然后呢就露馅了预设的话术撑不起一场真正的对话。这不是WT2606A的问题是纯离线产品形态的问题。200条命令词再多它也是一个有穷集合而对话场景本质上是无穷的。陪伴类机器人的核心卖点是能聊纯离线方案注定只能做一个会背台词的空壳。1.2 纯在线方案在机器人场景的致命问题后来我换了一条路麦克风直连ESP32录音推给云端ASR识别文本丢给大模型返回的文本再合成语音播放。这样确实什么都能聊但机器人场景有几件事让我非常难受。延迟。唤醒词识别、VAD断句、ASR、大模型首token、TTS合成一条链路走下来普通水平要2到4秒。用户对着机器人说完话盯着它等三秒才开口体验是灾难性的。断网变哑巴。机器人摆在用户家里WiFi一抖动对话就卡住。有些人会说我缓存一部分离线指令不就行了——但安全指令如果走云端延迟就是风险。费用。开放域对话一次调用几分钱听着不多可一台教育机器人每天被小朋友问几十轮一年下来隐形成本很高。更麻烦的是这种成本会随着用户量线性上涨。最让我介意的其实不是成本和延迟而是可靠性。机器人是有物理动作的如果急停这类指令要走拾音→云端→返回→执行这条链路一旦网络抖动机器人都能撞墙了还没收到指令。这类指令必须在本地闭环一秒都不能等。1.3 WT2606A在我的架构里到底负责什么所以我把方案定为双引擎WT2606A负责本地闭环的唤醒和固定指令ESP32负责联网的开放域对话。WT2606A在我的架构里不是替代主控它是机器人的听觉反射弧——低级但必须快的反应走它高级的思考走云端。这里说清楚WT2606A适合做什么、不适合做什么。它适合做唤醒词识别、固定词条的快速识别、本地语音播报内置功放直接推喇叭、以及和主控之间的UART指令交互。它不适合做开放域的语义理解、需要大量动态知识的问答、噪声环境下的长句识别。标题里提到的200条离线命令词不是白给的这200条就是我给机器人设计的本能反应库。它的价值在于把高频、固定、需要秒回的内容全部本地消化掉只有真正超出词表范围的开放问题才值得花一次网络请求的费用和延迟去问大模型。接下来我会讲怎么把这200条塞得既有层次又不会互相打架。2. WT2606A的资源盘点200条命令词是怎么塞进去的2.1 这芯片到底有多少家底WT2606A是唯创知音的一颗语音芯片内部有一颗DSP核心带麦克风输入、音频功放输出外挂SPI Flash存语音资源和识别模型。我手上这颗用的是离线识别固件支持最大200条命令词的词库容量。具体到单个工程识别词条、回复音频、识别模型是一起打包下载到Flash里的。几个关键点先列出来工作电压范围比较宽3.3V和5V都能跑我实际用的是5V供电输出功率更大喇叭声音更足。UART是标配波特率9600到115200都可以配置我和ESP32之间用115200。有IO口可以配置成触发播放、电平输出但我基本不用IO交互全部走UART方便主控统一管理状态。离线识别的响应速度我做过的测试在300毫秒左右出结果具体取决于词条数量和芯片负载。需要说明的是不同批次、不同固件版本的细节有差异。比如200条这个上限有的固件版本可能只有100条买之前一定要向原厂确认型号后缀和固件版本。我吃过这个亏第一次拿到的芯片固件只支持80条后来查了规格书才发现要选带特定标识的版本外壳上肉眼根本看不出来只能靠文档和工具确认。2.2 一条命令词不只是词是词播报动作的组合用配置工具加词条的时候界面上一行并不只是填个文本。一条完整的命令词可以由三部分组成识别词对应的音频特征也叫训练词、识别成功后要播报的回复音频、以及识别结果的上报行为。这颗芯片的离线识别原理是声学特征匹配不是语义理解——它比对的是用户语音和训练词音频在声学空间里的距离所以训练词的质量直接决定识别率。这里有个设计选择值得展开说。芯片支持识别成功后自动播报绑定音频比如识别到你好小W自动回一句我在呢。好处是本地闭环不经过主控延迟最低坏处是主控不知道发生了这件事如果需要联动动作比如转个身就必须靠UART通知主控。我的做法是所有词条都不绑定播报识别结果一律通过UART上报给ESP32由ESP32决定播什么、动什么。这样做的代价是每一条回复都要主控参与响应会慢几十毫秒但换来的是状态统一——机器人不会出现嘴里说着你好、身体没反应的割裂感。对于做产品的人来说状态统一比省那几十毫秒重要得多。2.3 200条词条的分组规划先分类再分配ID段200条看起来多如果不做规划配置工具里拉一个很长的列表后期维护就是灾难。我是按功能域分的组并且在ID上预留了段位这样MCU收到ID就能直接路由不用在代码里写一堆散落的判断。我实际的分组如下分组ID段条数典型词条唤醒/基础应答0x01-0x1012你好小W、过来、别闹运动控制0x11-0x3024前进、后退、左转、停止家电/环境控制0x31-0x5028开灯、关灯、调到26度状态查询0x51-0x6016电量多少、几点了娱乐/闲聊触发0x61-0xA056讲个笑话、唱首歌安全指令0xA1-0xB012急停、关机、音量小点备用扩展0xB1-0xC852预留ID段规划的意义在软件层面ESP32收到ID后直接按区间分支处理0x11-0x30走运动控制模块0xA1-0xB0走安全策略模块不需要在代码里写200个case。如果以后要加词条只要新的ID落在这个段位内业务逻辑基本不用动。还有一个词条设计原则发音相似度避让。离线识别的原理是声学特征匹配而不是语义理解开灯和开窗这种词在安静的客厅里问题不大但在电机噪音背景下误识别率明显上升。我的经验是同一个分组内的词条首字尽量不同韵母差异尽量大。讲个笑话和讲个故事这种就别同时放留一个另一个让在线对话去处理反正在线模型能理解同义表达不需要靠词库堆。3. 离线命令词配置实录接线、上位机、烧录、联调3.1 最小硬件系统怎么搭先说我手上的调试环境WT2606A模组带麦克风和喇叭接口、一个USB转TTL模块、ESP32开发板、两个3W小喇叭、5V电源。调试阶段的接线很简单连接对象WT2606A引脚说明USB转TTL TXDRXD上位机下载配置USB转TTL RXDTXD读取识别结果日志ESP32 UART2 TXDRXD运行时命令下发ESP32 UART2 RXDTXD识别结果上报电源正极VCC5V电源负极GND共地注意一下如果上位机调试和ESP32都挂在同一个UART上会打架。我调试阶段的做法是先用USB转TTL把词库下载好然后拔掉USB转TTL的连线再让ESP32接管UART。虽然芯片的UART从物理上可以共用一根线但两路TXD同时挂上去会互相干扰别图省事该拔就拔。3.2 上位机组词和参数设置唯创知音官方提供了PC端的配置工具通过串口连接芯片后流程大概是这样的新建工程选择芯片型号和固件版本确认识别模式支持200条。添加词条录音或者用TTS生成每个词的语音文件作为一个独立词条加入词库。为每条词设置ID、播报音频我全留空、识别生效条件。设置全局参数波特率115200、识别灵敏度、未命中时的提示音频。编译工程生成固件下载到芯片的SPI Flash。用工具自带的串口调试窗口测试喊词条看返回结果。参数里面最有讲究的是识别灵敏度。灵敏度调高容易误触发调低喊词条不响应。刚开始我图省事用默认值在办公室环境里误触发率挺高键盘声都能把它喊醒。后来我把灵敏度下调了两档误触明显减少缺点是语速稍快或者声音小的时候会漏识别。这个东西没有标准答案得根据机器人的实际使用环境来来回回调建议做成可配置项出厂前用不同环境各跑一轮。还有一个细节词条音频文件是自己录还是用工具生成会直接影响识别率。我的实测是用同一个人、同一套设备录制的词条音频识别率最稳定拿TTS合成的词条识别率就差不少因为用户真实说话的声学特征和你塞进去的模板差异越大匹配就越吃力。条件允许的话让产品团队里说话习惯最接近目标用户的人来录词条。3.3 UART协议对接识别结果怎么到ESP32词条下载好之后剩下的工作就是让ESP32看懂芯片上报的数据。不同固件版本的协议有差异我手上这版固件的大致帧格式是帧头两个固定字节 命令字 数据长度 数据区 校验。识别成功时芯片上报一条带有词条ID的帧上电时会主动上报一条就绪事件未命中时有的固件会上报专门的未命中事件有的什么都不发这一点要提前确认。踩过的坑是芯片上电后会先主动上报一条上电就绪事件很多人在代码里把这条事件误当成识别结果导致机器人一开机就执行了一遍上次的命令。我在ESP32的解析层加了一个状态过滤只有处于已就绪状态之后收到的识别帧才交给业务层处理。另一个坑是连续识别模式下的重复上报。芯片在持续监听时同一个词条如果被周围环境音反复触发可能短时间内上报多帧相同ID。处理办法是在应用层做去重比如500毫秒内相同的识别ID只响应一次。这个去重逻辑别看简单少了它用户说一声前进机器人可能原地表演三段跳。uint8_t frame[10]; if (readUartFrame(frame)) { if (frame[0] 0xAA frame[1] 0x55) { // 帧头 uint8_t cmd frame[2]; if (cmd CMD_RECOG_RESULT) { uint16_t cmdId (frame[4] 8) | frame[5]; if (cmdId ! lastCmdId || millis() - lastCmdTime 500) { handleCommand(cmdId); lastCmdId cmdId; lastCmdTime millis(); } } } }3.4 200条词平均下来最容易翻车的三个地方第一回复音频太长会吞掉下一句。我最初给讲个笑话绑了一段20秒的相声播报期间芯片的麦克风输入被本地播放干扰用户紧接着说的再来一个基本识别不到。后来我把策略定为所有本地播报控制在3秒以内长回复由ESP32在TTS播放结束后主动重新开启监听。第二词条音频格式不一致导致编译失败或者识别率异常。配置工具对音频格式有要求一般是特定采样率和编码我遇到过采样率给成了44.1kHz工具没报错但识别率断崖式下降。建议所有的词条音频统一下载前做一次批处理统一采样率、统一音量、统一静音头长度别嫌麻烦。第三词库更新之后芯片没有真正加载新固件。工具提示下载成功但实测还是旧词条生效多半是Flash里的旧模型没擦干净或者没有让芯片冷启动。我现在的习惯是下载完成后断电重启芯片再在上位机里读取一次词条列表核对宁可多花十秒不然后面所有联调都在对着一个错误的词库干活排查到半夜才发现根因那种滋味不好受。4. 离线在线的仲裁逻辑一秒内决定谁来回答4.1 系统状态机IDLE、WAKE、DIALOG、BUSY离线引擎和在线引擎都存在于同一台机器人上第一件事就是把它们的说话权分清楚。我用一个简单的状态机来管理IDLE系统低功耗待机WT2606A处于唤醒词监听模式只认唤醒词比如你好小W其余词条不响应。WAKE唤醒词命中进入对话准备态。此时WT2606A切换到全量200条监听ESP32同时准备在线ASR通道。DIALOG对话态。用户每说一句先走离线识别未命中再转在线。BUSY机器人正在播报长内容或者正在执行动作此时对新的语音输入做降级处理只响应急停类危险指令。状态转换的核心逻辑是离线识别永远是第一步只有离线没接住的问题才轮到在线。这个顺序不能反一旦反过来简单的停止也要绕一圈云端延迟和可靠性都不可接受。我在WAKE态加了一个超时机制唤醒后8秒内没有任何有效输入自动回到IDLE避免机器人一直竖着耳朵处于高功耗状态也避免用户不说话了它还开着ASR通道烧流量。4.2 离线优先在线兜底具体怎么调度具体到一次对话的处理我写在ESP32上的逻辑是这样的WT2606A上报识别帧先做ID去重和状态过滤。判断ID是否在安全指令段0xA1-0xB0是则立刻执行无论当前在播什么、在干什么。判断机器人是否处于BUSY处于BUSY则把指令缓存或者丢弃不做在线兜底。正常DIALOG状态下ID如果命中本地执行并播报回复本次对话结束。如果WT2606A在超时窗口内上报的是未命中事件或者在一定时间内没有任何上报ESP32把本次录音推给在线ASR。这里有个取舍要交代在线兜底的录音从哪来。由于WT2606A上报未命中时并不会把音频内容交给ESP32ESP32要拿到这段语音需要自己重新采集。这就回到了双麦克风还是重录音的问题下一章详细说。另一个思路是离线在线并行识别——WT2606A在离线识别的同时ESP32的麦克风也一直在录音并流式上传。两个结果谁先出、谁更优用谁。这种方式响应更快但音频上行流量翻倍而且离线在线同时命中时比如讲个笑话既在词库里又在在线闲聊模型里被支持要额外设计优先级。我的做法是离线优先因为离线词条是我精心设计过的标准答案在线答案反而是兜底优先级给标准答案更稳。4.3 多轮对话的上下文到底存在哪里标题里说的在线多轮对话核心不只是把语音转成文本丢给大模型而是要让多轮对话和离线命令词共享同一个记忆。举个例子用户说帮我查一下明天的天气在线对话返回了天气。然后用户紧接着说那后天呢——这里的那后天呢如果没有上下文任何模型都答不了。所以在线ASR拿到的文本必须带着前面的对话历史一起发给大模型。我的会话管理器维护一个历史消息队列结构大概是这样的每个会话有一个会话ID对应一台机器人的一次连续交互周期。每次用户turn把用户文本追加进历史每次助手turn把回复文本也追加进历史。历史列表只保留最近10轮超过的丢弃。会话有个活动时间窗口超过15分钟没有任何对话清空历史、销毁会话防止上下文越攒越乱。至于离线命令词和上下文的关系我的处理方式是当用户先说关灯离线执行再说顺便把窗帘也关了在线兜底我会把关灯这个动作文本也追加进上下文历史让大模型知道当前对话的环境状态。这样模型的回答会更贴近实际场景而不是从零开始猜。这个混写历史的操作看起来不起眼但对机器人的体验提升非常大。用户会觉得机器人记得自己刚才干了什么而不是一个每句话都要重新寒暄的客服。5. 在线多轮对话链路搭建音频上行、语音识别与会话拼接5.1 音频上行双麦克风还是芯片回传这是整个方案里最容易被忽视、也最容易设计错的地方。要做在线ASR云端需要拿到用户这段语音的音频数据。问题在于WT2606A离线上报了未命中之后它不会把这段音频原样吐给你。我调研过两条路。方案A让WT2606A把录音数据经UART回传给ESP32。芯片有录音功能但UART带宽有限115200波特率下要想实时传16kHz/16bit单声道PCM256kbps带宽差一倍多只能降采样到8kHz或者每秒分包传输延迟和丢包都很严重。某些固件支持压缩格式或者缓存录音后整包上传但实验下来延迟感人IM聊天式的体验不适合对话。方案BESP32独立接一颗I2S麦克风离线在线各听各的。这是我现在用的方案。WT2606A用自己的麦克风做唤醒和离线识别ESP32的I2S麦克风专门服务在线ASR。两者在物理上是独立的不会抢资源延迟也可控。方案B的代价是麦克风数量多了一个但换来的是架构清晰。实际产品通常会把两路麦克风都排在前面板做成双麦阵列的样子顺便还能做简单的波束成型。如果是产品开发我建议直接按方案B设计不要试图省这个麦克风。对比项方案A芯片录音回传方案B独立I2S麦克风带宽需求高需压缩或降采样低直接走I2S延迟偏高需分包重组低可流式上传硬件成本省一个麦克风多一个麦克风可靠性依赖固件协议自主可控我的结论适合玩具级验证适合做产品5.2 VAD断句与ASR接入怎么让云端知道这句话说完了有了音频流下一步是判断用户什么时候说完了。机器人的对话场景里用户说话不是干净的会有停顿、语气词、背景声。如果每半秒就送给ASR一次会收到一堆嗯那个如果一直等又会让用户觉得反应迟钝。我用的方案是先做能量和静音检测VAD在ESP32端实时计算音频帧的能量连续静音超过600毫秒就认为一句话结束然后把累积的音频一次性送给云端ASR识别。这样做的好处是简单可靠坏处是对语速慢的人不友好中途稍微一想就是一句长停顿被拆成了两句。更高级的做法是用云端流式ASR边录音边上传云端自己判断断句。我后来切到了流式方案因为延迟体验确实好不少——用户还没说完云端已经出了前半句的临时结果说完的时候基本已经可以直接取最终结果了。如果你的在线ASR服务商提供流式接口直接上流式别走我走过的弯路。5.3 大模型接入提示词里塞进机器人的人设和状态ASR拿到用户文本后我把下面几样东西一起打包发给大模型系统提示词定义机器人的角色。我是这样写的你是一个桌面陪伴机器人名叫小W说话简短自然不要用markdown不要输出长段落回答控制在50字以内。机器人当前状态摘要比如机器人电量30%正在执行待机任务今天已经陪用户聊了5轮。这些信息能让模型的回答更接地气。多轮会话历史上一节说的历史队列。用户当前这句话。值得注意的一个细节用大模型做机器人对话最怕它答成一段议论文。我在提示词里明确约束了回答长度和说话方式并且在程序里对返回文本做了强制截断超过80个字符的TTS一律截掉。别把TTS的处理能力压给模型模型说长话很容易TTS放出来用户只会觉得烦。5.4 TTS播报和打断离线指令永远有最高优先级在线链路最后一步是把大模型返回的文本合成本地语音播报。TTS服务商选择上我优先看发音自然度和响应时间首包时间尽量控制在300毫秒以内。合成方式有在线API和本地SDK两种我前期先用在线API后面优化阶段换成了设备端SDK以减少一次网络往返。播报阶段要处理一个关键问题播报过程中用户说话怎么办。我的策略是分三级第一级安全指令急停、关机、停止任何时候都必须立即打断当前播报并执行。第二级离线词库命中可以打断TTS播报但需要等待一个短语播报的自然间断避免硬切导致的机械感。第三级在线ASR识别到新指令在TTS播报结束后再处理不打断当前播报。这个三级策略本质上就是在BUSY状态下的降级处理和第四章的状态机是配套的。简单说在线对话可以被离线打断离线可以被安全指令打断安全指令是绝对优先级。把这条规则写进代码之后机器人再也没出现过叫它急停它还在唱歌的情况。6. 实测效果与调优方向数字、问题和下一步6.1 离线识别实测我把这套系统放在三个环境里各测了一轮安静的办公室、开着空调和风扇的起居室、旁边有电机运转的工作间。离线词库200条全部启用唤醒词你好小W单独测试。安静环境下200条词条的识别率在95%以上响应延迟大概300毫秒。起居室环境下空调风声没有明显影响识别率大概90%。工作间电机噪音下识别率掉到80%左右误触发也明显变多。我后来做了两个改进一是把识别灵敏度从默认值下调两档误触发少了一半代价是远距离2米以上喊话识别率下降二是在硬件上把麦克风尽量远离扬声器和电机中间加了一层减震垫效果比调参数更明显。硬件布局对识别率的影响往往比软件参数大得多这是很多人忽略的。6.2 在线链路延迟拆解在线对话的延迟是另一个关注点。我用流式ASR之后整体链路的典型耗时是这样的环节耗时VAD断句等待0.6s流式ASR识别0.4-0.8s大模型首token0.5-1.0sTTS首包0.3-0.6s合计1.8-3.0s用户真实的体感是说完话之后大约2秒左右机器人开始出声。这个数字在可用范围内但离自然对话还有差距。其中VAD断句的0.6秒等待是固定开销想省只能牺牲断句准确度大模型的首token延迟换不同服务商差距明显值得花时间对比。6.3 下一步的优化方向按照我现在的路线接下来会做这几件事。第一把高频问答做成本地缓存。比如你会什么你叫什么这类问题不一定要走大模型可以在本地建一个意图到回复的映射表命中直接本地TTS。这个思路和离线命令词是同构的只不过把匹配层从WT2606A换成了ESP32端的关键词匹配。第二给在线对话加置信度路由。在线ASR的结果可以带一个置信度分值低于阈值的文本可能是误识别不要让大模型强行回答而是给一个抱歉没听清之类的本地兜底回复。这个细节能显著减少答非所问的尴尬。第三离线词库和在线知识的自动同步。我现在是人工维护词库未来打算做一个管理后台远程推送新词条固件到机器人的Flash同时更新大模型的提示词和本地缓存表。这样一套交互逻辑里面离线在线两条路都能迭代不用每次让用户返厂。最后聊一点个人体会。我踩过最大的坑是把离线识别当成落后的方案一度想把所有交互都交给在线大模型。做完这套混合架构才明白真正好的机器人语音交互不是哪一个引擎更强而是让每个引擎干自己最擅长的事WT2606A这种离线引擎负责秒级响应和保命指令云端负责开放域的聪明劲中间的仲裁逻辑才是灵魂。离线在线不是替代关系是分工关系。如果你也在做类似的机器人项目我建议第一版先把离线命令词的体验做到极致再往上面叠加在线能力这个顺序能帮你省掉大量返工。希望这篇思路梳理对你有用有更好的方案也欢迎交流。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门