ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Rokid Glasses AIUI 实战:从零开发“今天吃什么”语音技能

Rokid Glasses AIUI 实战:从零开发“今天吃什么”语音技能 1. 为什么要在 Rokid Glasses 上折腾一个“今天吃什么”的 AIUI拿到 Rokid Glasses 的开发者套件之后我第一反应不是去做导航、翻译这类“正经”功能而是想验证一个更接地气的问题AIUI 这种交互范式到底能不能扛住一个高频、低决策成本、但极其日常的场景。“今天吃什么”就是这么一个场景——它足够简单简单到任何人一听就懂又足够真实真实到每个人每天都要面对。用它来当 AIUI 开发的第一个练手项目比做一个“Hello World”式的语音播报有意义得多。先把概念说清楚。AIUI 不是单纯的语音助手也不是单纯的图形界面它是把语音、视觉、传感器输入和意图理解揉在一起的一套交互框架。在 Rokid Glasses 这种带显示、带麦克风阵列、带摄像头的眼镜形态设备上AIUI 的核心价值在于用户不需要掏出手机、不需要点按屏幕只需要一句话或者一个眼神方向系统就能给出一个“够用”的回应。而“今天吃什么”这个需求恰好完美匹配这种交互——用户要的不是一个精确答案而是一个能打破选择困难的随机建议外加一点点决策依据。这个项目适合谁如果你已经拿到了 Rokid Glasses 的开发权限会一点基础的脚本或应用开发但对 AIUI 的意图定义、语音链路、显示层渲染还没有完整跑通过一遍那这个项目就是为你准备的。它不涉及复杂的模型训练也不需要你搭建后端服务核心工作量集中在意图设计、语音交互闭环、以及眼镜端的信息呈现这三块。做完之后你会对 AIUI 的整个数据流有一个肌肉记忆级别的理解后面再做什么天气播报、日程提醒、物品识别都是同一套骨架换皮。我踩过的第一个坑就是低估了“随机”这件事在语音交互里的复杂度。你以为用户说“今天吃什么”你随机返回一个菜名就完事了实际测试下来用户会追问“还有别的吗”“太辣了”“有没有清淡点的”“附近能买到吗”。这些追问在图形界面里是几个按钮的事在 AIUI 里就是意图的二次甚至三次分发。所以这个项目真正的技术含量不在于随机算法而在于如何用有限的意图槽位覆盖用户无限的发散追问。2. 开发前的环境确认与 Rokid Glasses AIUI 能力边界2.1 开发者账号与设备连接状态检查在写第一行代码之前必须先把设备侧的准备工作做扎实。Rokid Glasses 的开发模式需要先在开发者平台注册应用拿到对应的 App Key 和 App Secret然后通过调试工具把应用推送到眼镜端。这一步看起来是流程性的但实际卡人的地方在于设备固件版本和 SDK 版本的匹配。我遇到过 SDK 文档里写的某个语音接口在设备上直接返回 unsupported排查了半天才发现是固件版本低了两个小版本。具体操作上你需要确认三件事眼镜端已经开启开发者模式并且和电脑处于同一调试网络下开发者后台创建的应用类型选择的是带 AIUI 能力的应用模板本地开发环境里安装的 CLI 工具版本与 SDK 版本对应。这三件事任何一件不对后面都会出现“代码没问题但设备没反应”的诡异现象。提示设备连接后先用官方提供的设备信息查询命令确认固件版本再去对照 SDK 的兼容性列表。不要跳过这一步否则后面调试语音链路时你会怀疑人生。2.2 AIUI 在眼镜端的输入输出通道理解 AIUI 的能力边界本质上是理解眼镜端有哪些输入通道和输出通道。输入侧主要有三路麦克风阵列采集的语音流、摄像头采集的图像流、以及触摸板/按键的物理交互。输出侧主要是两路骨传导或微型扬声器的音频输出、以及光波导显示屏的视觉输出。AIUI 框架做的事情就是把这些通道编排成一个统一的交互会话。对于“今天吃什么”这个项目我们主要用到的是语音输入和视觉语音输出。摄像头暂时不用但你要知道它在那里因为后续如果要做“识别冰箱里有什么食材再推荐菜谱”摄像头就是核心输入。物理交互也要保留一个兜底——当语音识别置信度低的时候用户可以通过触摸板确认或取消这是 AIUI 设计里非常重要的一条原则永远给用户留一个不用说话就能操作的退路。这里有个经验之谈眼镜端的显示区域非常有限大概只够显示两到三行短文本加一个简单的图标。所以你的输出内容必须极度精简。我一开始想把菜名、食材、做法、热量全塞进去结果在眼镜上看起来就是一坨糊在一起的文字。后来改成“菜名 一句推荐理由 一个操作提示”的三段式可读性立刻上来了。2.3 语音唤醒与意图识别的链路拆解AIUI 的语音链路可以粗略拆成四段唤醒词检测、语音转文字、意图解析、技能分发。唤醒词检测是在设备本地跑的功耗很低负责监听特定的唤醒词。一旦唤醒设备开始把语音流送到语音转文字模块得到文本后再交给意图解析器解析出用户到底想干什么最后把意图和槽位信息分发给对应的技能处理函数。“今天吃什么”这个技能需要注册的意图其实不止一个。核心意图是“推荐食物”但还要处理“换一个”“太辣了”“有没有面食”这类修正意图以及“算了不吃了”这类取消意图。每个意图都要定义对应的语料模板和槽位。比如“有没有{口味}的”就是一个带槽位的意图模板槽位是口味类型。这些模板写得越丰富用户的实际表达被正确解析的概率就越高。我实测下来意图模板的数量和识别准确率不是线性关系。写到二十条左右的时候常见表达基本都能覆盖了再往上加边际收益很低。真正影响体验的是兜底策略——当所有意图都没匹配上时系统应该怎么回应。我的做法是统一回一句“没听清你可以说随便来一个或者告诉我你想吃什么口味”然后给一个默认推荐。这样即使用户说了很奇怪的话交互也不会断掉。3. 从零搭建“今天吃什么”技能的完整实操3.1 项目初始化与技能注册在开发者后台创建一个新的 AIUI 技能技能名称填“今天吃什么”调用名称填一个简短的英文标识比如what_to_eat。技能类型选择自定义技能因为我们要自己处理意图逻辑。创建完成后会得到一个技能 ID这个 ID 在后面本地代码里注册意图的时候要用到。本地项目结构我建议这样组织一个入口文件负责初始化 AIUI 客户端和注册意图一个intent_handler文件放所有意图的处理函数一个food_data文件放菜品数据和推荐逻辑再加一个config文件放 App Key 之类的配置。这个结构不复杂但胜在清晰后面加新意图的时候不用在一堆代码里翻来翻去。初始化的时候有一个细节容易忽略AIUI 客户端的初始化是异步的必须等初始化完成的回调触发之后才能注册意图。我一开始把注册意图的代码写在初始化调用后面结果意图一直注册不上因为初始化还没完成。后来改成在初始化成功的回调里做注册问题就解决了。这个坑在官方文档里只是一笔带过但实际开发中非常容易踩。// 伪代码示意实际 API 名称以官方 SDK 为准 const aiuiClient new AIUICient({ appKey: your_app_key, appSecret: your_app_secret }); aiuiClient.on(initialized, () { aiuiClient.registerIntent(what_to_eat, { intentName: recommend_food, slots: [flavor, meal_type] }); aiuiClient.registerIntent(what_to_eat, { intentName: change_food }); aiuiClient.registerIntent(what_to_eat, { intentName: cancel }); });3.2 意图语料的设计与槽位定义语料设计是这个项目里最需要“人味”的部分。你不能只写“今天吃什么”这一句因为真实用户会说出各种变体“中午吃啥”“晚上吃什么好”“给我推荐个饭”“随便来一个”“不知道吃啥”。这些都要作为语料模板注册进去。我的做法是先自己对着眼镜说了二十遍不同说法把实际说出来的句子记下来再去重、归类最后整理成语料模板。槽位方面我定义了两个flavor表示口味偏好取值包括辣、清淡、甜、酸等meal_type表示餐次取值包括早餐、午餐、晚餐、夜宵。这两个槽位不是必须的用户不说就为空推荐逻辑里根据槽位是否为空做不同的过滤。比如用户说“有没有清淡点的”flavor槽位就是“清淡”推荐时只从清淡菜品里选。意图名称触发语料示例槽位处理逻辑recommend_food今天吃什么 / 推荐个饭flavor, meal_type根据槽位过滤菜品后随机推荐change_food换一个 / 还有别的吗无排除上一次推荐结果后重新随机cancel算了 / 不吃了无结束会话并给出友好回应语料模板写完之后一定要在开发者后台的测试工具里逐条验证。我遇到过一条语料“吃啥都行”被解析成了两个意图的冲突情况后来把这条语料删掉换成了“随便”才解决。语料之间的语义重叠是意图冲突的主要来源写的时候要刻意保持每条语料只指向一个明确的意图。3.3 推荐逻辑的实现与随机策略推荐逻辑看起来简单但要做好体验有几个讲究。第一随机不能是真随机。如果用户连续三次都听到同一个菜名他会觉得这个功能坏了。所以我在推荐逻辑里加了一个最近推荐队列每次推荐时排除掉最近五次出现过的菜品。第二推荐要带理由。光说一个菜名太干巴巴了加一句“今天天气热来碗凉面挺合适”或者“你上次说想吃辣的水煮肉片可以考虑”体验会好很多。理由可以基于时间、天气、历史偏好来生成哪怕只是简单的模板拼接也比干说菜名强。菜品数据我用一个 JSON 数组存每个菜品包含名称、口味标签、适合餐次、一句推荐语。数据量不用大三十到五十个菜就够覆盖日常选择了。关键是要分类均衡不能全是川菜也不能全是主食。我按口味和餐次做了交叉分类保证任何过滤条件下都至少有三到五个候选。const foodList [ { name: 番茄鸡蛋面, flavor: 清淡, meal: [早餐,午餐,晚餐], tip: 简单快手什么时候吃都不违和 }, { name: 水煮肉片, flavor: 辣, meal: [午餐,晚餐], tip: 想吃辣的时候来一份下饭 }, { name: 皮蛋瘦肉粥, flavor: 清淡, meal: [早餐,夜宵], tip: 胃不舒服的时候来一碗很舒服 }, // ... 更多菜品 ]; function recommendFood(flavor, mealType, recentList) { let candidates foodList.filter(f !recentList.includes(f.name)); if (flavor) candidates candidates.filter(f f.flavor flavor); if (mealType) candidates candidates.filter(f f.meal.includes(mealType)); if (candidates.length 0) candidates foodList; // 兜底 const picked candidates[Math.floor(Math.random() * candidates.length)]; return picked; }3.4 语音播报与屏幕显示的协同眼镜端的输出是语音加显示双通道这两个通道的协同是有讲究的。语音负责传递核心信息显示负责补充细节和提供操作提示。比如推荐“番茄鸡蛋面”的时候语音说“给你推荐番茄鸡蛋面简单快手什么时候吃都不违和”屏幕上同时显示菜名和一行小字“说‘换一个’看看别的”。这样用户即使没听清语音扫一眼屏幕也能知道结果并且知道下一步能做什么。语音播报的文本要单独准备不能直接把显示文本读出来。显示文本可以带标点和格式语音文本要更口语化、更短。我一开始偷懒用同一份文本结果语音读出来像在念说明书。后来把语音文本单独写了一套控制在二十个字以内听起来自然多了。屏幕显示的刷新时机也要注意。AIUI 的语音播报是异步的如果播报还没结束就刷新屏幕用户会看到文字和语音不同步。我的做法是等语音播报完成的回调触发后再更新显示虽然会慢那么零点几秒但体验上更连贯。这个细节在快速连续交互的时候尤其明显比如用户说“换一个”如果显示刷新太快而语音还在读上一个就会很混乱。4. 实测中暴露的问题与逐项排查过程4.1 唤醒后首句识别率低的排查设备刚唤醒后的第一句话识别率明显低于后续对话。我一开始以为是麦克风的问题后来用日志工具抓了语音链路的原始数据才发现唤醒词检测和语音转文字之间的切换有一个短暂的空窗期如果用户唤醒后立刻说话开头几个字会被吃掉。这不是 bug是语音链路的设计特性但需要在交互上做补偿。我的解决方案是在唤醒成功后先播一个很短的提示音大概两百毫秒给链路切换留出时间同时提示用户“可以说了”。加了提示音之后首句识别率从大概六成提升到了九成以上。这个改动成本极低但效果立竿见影。如果你也在做眼镜端的语音交互强烈建议加上这个提示音。注意提示音的音量和时长要控制好太长会显得拖沓太短又起不到提示作用。我试下来两百到三百毫秒的中等音量提示音最合适。4.2 意图冲突导致的错误分发前面提到过语料语义重叠会导致意图冲突实际表现是用户说“随便”的时候有时候被解析成recommend_food有时候被解析成change_food。排查方法是把每次意图解析的结果和原始文本都打到日志里跑一百次看分布。我发现“随便”这个词在两条语料模板里都出现了一条在recommend_food下一条在change_food下。删掉其中一条之后冲突就消失了。这个问题的根因是意图解析器在多个模板匹配度相近时会随机选一个而不是报错。所以语料设计的第一原则是任何一条语料只能出现在一个意图的模板里。写完语料之后一定要做交叉检查把重复的句子揪出来。我后来养成了一个习惯把所有语料导出到一个表格里用条件格式标出重复项每次改完语料都跑一遍这个检查。4.3 连续交互时的会话保持问题“今天吃什么”这个场景天然是多轮交互推荐、换一个、再换一个、就这个吧。但 AIUI 默认的会话超时时间比较短用户如果犹豫了几秒没说话会话就断了再说“换一个”的时候系统会重新走唤醒流程。这个体验很割裂。解决办法是在技能配置里把会话超时时间调长同时在一轮交互结束后主动追问“还要换吗”给用户一个继续对话的钩子。我把超时从默认的五秒调到了十五秒并且在每次推荐后都加一句“说换一个可以继续看”。实测下来用户连续交互的完成率提升了很多。但超时也不能太长否则用户已经走开了会话还挂着会浪费设备资源。十五秒是我试下来比较平衡的值。4.4 显示内容在强光下的可读性这个问题比较硬件向但很影响实际使用。Rokid Glasses 的光波导显示在室内看起来很清楚但到了户外强光下浅色文字几乎看不见。我一开始用的白色文字户外基本废了。后来改成高对比度的配色方案文字用深色描边加浅色填充可读性好了很多。另外显示区域的有效像素比想象中少字号不能太小。我试过用最小字号显示三行文字结果在眼镜上看起来像蚂蚁在爬。后来改成最多两行字号调大虽然信息量少了但至少能看清。在眼镜端做显示宁可少显示一点也要保证看清这是和手机屏幕完全不同的设计逻辑。5. 让这个技能更好用的几个进阶思路5.1 接入时间与天气做情境化推荐基础的随机推荐跑通之后最自然的扩展就是接入情境信息。早上八点推荐早餐类中午十二点推荐午餐类晚上六点之后推荐晚餐类这是基于时间的过滤。如果再接入天气数据下雨天推荐热汤面大热天推荐凉菜推荐的理由就更充分了。Rokid Glasses 本身有网络能力拉取天气接口不难难的是把天气数据映射到菜品标签上。我的做法是给每个菜品打上适合的天气标签比如“热食”“冷食”“汤类”然后根据实时天气做加权随机。这个扩展的技术点在于情境数据的获取时机。不要在每次推荐的时候都去拉天气那样延迟太高。我的做法是在技能启动的时候拉一次天气缓存起来会话期间用缓存数据。如果会话持续超过半小时再刷新一次。这样既保证了推荐的情境相关性又不会让用户等太久。5.2 用历史记录做个性化排序如果用户经常用这个技能可以记录他的选择历史慢慢学习他的偏好。比如他连续三次都选了辣味菜品那下次推荐的时候辣味菜品的权重就调高。这个逻辑不需要复杂的机器学习简单的计数加权就够了。实现上就是在本地存一个偏好计数器每次用户确认某个菜品就给它对应的口味标签加一分推荐时按分数做加权随机。但要注意不要过度拟合。如果用户偶尔想换口味系统一直推辣的他也会烦。所以加权要有上限不能让某个口味永远霸占推荐位。我设的上限是权重不超过基础权重的三倍这样既体现偏好又保留多样性。5.3 多人场景下的快速切换眼镜设备的一个特点是可能被多人轮流使用比如一家人吃饭前每个人都问一遍。这时候历史记录和偏好就会混在一起。我的处理方式是在会话开始时做一个简单的声纹区分如果检测到不同的说话人就切换到独立的偏好档案。声纹区分在 AIUI 框架里有现成的接口调用一下就行不需要自己训练模型。如果声纹区分不可用退而求其次的方案是让用户手动切换档案比如说“换个人问”就重置当前偏好。这个方案体验差一些但至少不会把不同人的偏好混在一起。我在没有声纹数据的情况下用的就是这个兜底方案。5.4 从“推荐”延伸到“决策辅助”“今天吃什么”的本质是决策困难推荐只是解决决策困难的一种方式。更进一步的做法是提供对比信息帮助用户自己做决定。比如同时给出两个候选语音说“A 是番茄鸡蛋面清淡快手B 是水煮肉片辣味下饭你选哪个”。这样用户不是在被动接受推荐而是在做选择参与感更强。这个模式的实现难点在于语音交互的选项确认。用户说“第一个”或者“A”都要能正确解析还要处理“都不想要”的情况。我在意图里加了select_option和reject_all两个意图来处理这些情况。实测下来双选项模式的用户满意度比单推荐模式高但交互轮次也更多适合不赶时间的场景。6. 我在这个项目里攒下的几条实在经验第一条AIUI 开发的核心工作量在意图设计不在代码。我大概花了七成时间在写语料、测语料、改语料写推荐逻辑的代码可能只占了两成。如果你打算做类似的技能先把意图和语料想清楚再动手写代码会省很多返工。第二条眼镜端的交互设计要遵循“一句话一个动作”的原则。用户说一句话系统给一个明确回应不要让用户在一句话里表达多个意图。我试过让用户一句话同时指定口味和餐次比如“来个清淡的午餐”识别率明显低于分开说。后来改成先问餐次再问口味虽然多了一轮但每轮的识别准确率都高了。第三条测试一定要在真实设备上做模拟器靠不住。语音链路的延迟、麦克风的拾音效果、显示的可读性这些东西在模拟器上完全体现不出来。我早期在模拟器上测得好好的推到设备上发现唤醒后首句识别率惨不忍睹。真实设备测试是绕不过去的。第四条给每个意图都留一个兜底回应。用户的实际表达永远比你想象的丰富总有意料之外的句子。兜底回应不需要多聪明只要能让对话继续下去就行。我的兜底回应是“没太听明白你可以说随便来一个或者告诉我你想吃什么”实测下来用户听到这句话基本都会换一种说法再说一遍交互不会断。第五条会话超时时间要结合场景调。吃饭决策这个场景用户犹豫的时间比较长超时设短了体验很差。但如果是查天气这种场景用户要的是快速答案超时设短一点反而干脆。没有通用的最优值要根据具体场景去试。最后说一个我个人的体会AIUI 这种交互范式最怕的就是“为了语音而语音”。如果一个操作在屏幕上点一下就能完成硬要用语音去做反而更累。“今天吃什么”之所以适合 AIUI是因为它天然就是语音场景——你不可能为了决定吃什么去掏手机点开一个 App。找到这种天然匹配的场景AIUI 的价值才能体现出来。这个项目做完之后我对什么场景该用 AIUI、什么场景不该用有了比看十篇文档都清晰的认识。
返回列表