做饭时手上沾满油,怎么问 AI?我给厨房装了个数字人厨师长
一个真实痛点厨房里没法用 AI事情是这样的。上周末我尝试做一道糖醋排骨油热到七成准备下排骨的时候突然忘了——是先放糖还是先放醋手上全是油手机在客厅充电平板挂在墙上但不想用油手去戳屏幕。那一刻我特别希望厨房墙上挂的屏幕里有个人我喊一声它就回答不用碰任何设备。回来之后我就在思考能不能搭建一套厨房烹饪助手具身交互智能体部署在厨房壁挂屏幕直接语音提问即可解答烹饪步骤、支持多轮追问、烹饪定时提醒还可以结合冰箱现有食材自动推荐菜谱。这个场景看似简单对 Agent 提出了很高的要求具备嘈杂环境语音识别能力、会话记忆能力、基于食材做菜谱推理的认知能力同时还需要一套可视化、可语音播报、可输出手势反馈的具身交互表达界面。这恰好就是Agent 认知与具身交互智能的交汇点Agent 作为认知大脑解决 “懂不懂做菜”具身交互智能解决 “如何在厨房这种特殊环境下自然使用 AI”。纯文本 Agent 在厨房场景几乎无法落地 —— 颠勺做菜的时候根本腾不出双手敲击键盘、触摸屏幕。为什么 Agent 需要一个「身体」先说清楚我的核心判断**Agent 缺的不是大脑**缺少的是面向真实终端的具身交互表达界面。过去一年我折腾过不少 Agent 框架——LangChain、AutoGPT、Dify能力都很强。但它们有一个共同问题交互界面是聊天框。你在客厅坐着用没问题但在厨房、在车间、在展厅、在门店——任何双手被占用的场景里聊天框就废了。Agent 的认知能力理解、推理、记忆、工具调用已经足够成熟Qwen、DeepSeek 这些大模型的推理水平完全能支撑烹饪指导这种场景。但如果输出只能通过文字它就永远被困在需要你主动去用的工具定位里。[魔珐星云](https://xingyun3d.com/)补齐的正是这一层。它不是虚拟形象制作工具也不是简单 Agent 套壳组件它实现 AI 多模态感知、环境理解依托语音、表情、语义手势、实时响应能力完成自然人机沟通。大模型解决 AI 的「大脑」魔珐星云补齐 AI 的「身体、表达和交互」。技术层面魔珐星云把端到端全链路封装为 SDK 一站式接入多模态感知层听见·看见·理解×大模型智能体层理解·决策·调度×表达引擎层3D 大模型·端侧渲染·百元芯片可跑·千路并发。选 Qwen Plus 做大脑是因为它中文理解力强、菜谱和烹饪知识覆盖面广。AI Coding 工具用 Trae之前用它搭过一个 Vue 项目体验不错这次试试更复杂的服务层架构。项目结构Trae 搭建四级服务分层用 Trae 创建项目时我给了它一段提示词创建一个 Vue3 TypeScript 项目实现厨房烹饪数字人助手。 要求 1. components 下放数字人 UI 组件 2. services 下分四个模块llm大模型菜谱问答、rag检索增强、knowledge-base烹饪知识库、xingyun星云SDK 3. 主入口 KitchenAssistant.vue集成数字人渲染和服务调用 数字人核心功能 - 菜谱推荐 支持按食材、口味、难度搜索菜谱点击菜谱可查看详细步骤并由数字人口播指导 - 食材知识 查询食材的分类、口感、烹饪方式和技巧 - 智能对话 基于大模型的烹饪问答结合RAG增强提供更精准的菜谱推荐 - 数字人互动 支持多种数字人动作打招呼、讲解、指导、推荐等口播菜谱步骤完整的项目骨架docs/ 魔珐星云数字人搭建指南.md src/ components/ KitchenAssistant.vue ← 厨房助手主界面 CustomerService.vue ← 预留客服场景扩展 services/ llm.service.js ← Qwen 大模型对接菜谱问答 rag.service.js ← 检索增强匹配菜谱步骤 knowledge-base.service.js ← 本地烹饪知识库食材、口味偏好 xingyun.service.js ← 魔珐星云 SDK 封装 App.vue main.ts index.html这个分层的关键在于认知逻辑和具身表达解耦。services/ 负责大脑——菜谱推理、步骤检索、知识记忆components/ 负责身体——数字人渲染、语音驱动、动作触发。两者通过事件通信改一个不影响另一个。下面逐模块拆解。让厨师长「站」到厨房屏幕上这是地基。魔珐星云具身驱动 SDK 封装在此负责数字人渲染、语音合成和动作驱动。先在魔珐星云平台创建驱动应用。厨房场景选了一个穿厨师服的男性形象音色选了热情爽朗的风格——做菜嘛总得有点烟火气。平台有 5000 形象可选也支持上传照片定制。拿到App ID和App Secret后开始写代码。// xingyun.service.js - 魔珐星云 SDK 封装 let sdkInstance null; export function initXingyun(containerId, appId, appSecret) { sdkInstance new XmovAvatar({ containerId, appId, appSecret, gatewayServer: https://nebula-agent.xingyun3d.com/user/v1/ttsa/session, hardwareAcceleration: prefer-hardware, onMessage: (msg) console.log([星云], msg), }); sdkInstance.init({ onDownloadProgress: (p) console.log([星云] 资源加载: ${p}%), }); return sdkInstance; } // 流式驱动is_start / is_end 控制播报节奏 export function speak(text, isStart, isEnd) { if (!sdkInstance) return; sdkInstance.speak(text, isStart, isEnd); } // 打断当前播报切回倾听状态——厨房场景刚需 export function interactiveIdle() { if (!sdkInstance) return; sdkInstance.interactiveidle(); } // SSML 驱动说话 语义化动作 export function speakWithAction(text, action) { const ssml speak ue4event typeka/type dataaction_semantic${action}/action_semantic/data /ue4event ${text} /speak; speak(ssml, true, true); } export function destroy() { if (sdkInstance) sdkInstance.destroy(); }两个坑提醒SDK 只支持localhost或https访问直接用 IP 会报VideoDecoder is not defined频繁刷新会触发10005房间限流靠beforeunload调destroy()释放连接。参数流路线在厨房场景的价值很直接——厨房屏幕通常是便宜的安卓平板或盒子百元级芯片就能跑 AI 端渲和端侧解算不需要高配 GPU。端到端约 500ms 响应意味着我在灶台前喊一声下一步该干嘛数字人几乎秒回不会让我等着油烧过头。knowledge-base.service.js给厨师长装「记忆」厨房助手需要记住三类信息用户的口味偏好不吃香菜、少油少盐、当前菜谱的进度做到第几步了、常备食材清单冰箱里有什么。// knowledge-base.service.js - 烹饪知识库管理 const STORAGE_KEY kitchen_assistant_kb; function loadKB() { const raw localStorage.getItem(STORAGE_KEY); if (!raw) return { preferences: [], ingredients: [], progress: {} }; try { return JSON.parse(raw); } catch { return { preferences: [], ingredients: [], progress: {} }; } } function saveKB(kb) { localStorage.setItem(STORAGE_KEY, JSON.stringify(kb)); } // 保存口味偏好 export function savePreference(preference) { const kb loadKB(); kb.preferences.push({ id: Date.now(), content: preference, // 不吃香菜 少油少盐 createdAt: new Date().toISOString(), }); saveKB(kb); } // 更新冰箱食材清单 export function updateIngredients(ingredients) { const kb loadKB(); kb.ingredients ingredients; // [鸡蛋,番茄,牛肉,土豆] saveKB(kb); } // 记录当前菜谱进度 export function saveProgress(dish, step) { const kb loadKB(); kb.progress { dish, step, updatedAt: new Date().toISOString() }; saveKB(kb); } // 检索 export function search(query) { const kb loadKB(); const results []; // 匹配偏好 kb.preferences.forEach(p { if (query.includes(p.content.slice(0, 2))) results.push({ ...p, type: preference }); }); // 匹配进度 if (kb.progress.dish query.includes(kb.progress.dish)) { results.push({ ...kb.progress, type: progress, content: 当前在做${kb.progress.dish}第${kb.progress.step}步 }); } // 返回食材清单 if (query.includes(食材) || query.includes(冰箱)) { results.push({ content: 冰箱里有${kb.ingredients.join(、)}, type: ingredients }); } return results; } export function getKB() { return loadKB(); }实际用起来是这样的我第一次跟数字人说记住我不吃香菜之后它推荐菜谱时会自动避开含香菜的菜。做到糖醋排骨第三步时我说记住进度之后我问我做到哪了它能回答你在做糖醋排骨做到第三步收汁。这种跨会话的持久记忆是传统聊天框 Agent 做不到的——ChatGPT 关了窗口就忘了你是谁但厨房助手得记住你的口味。rag.service.js检索增强让回答「言之有据」光有记忆不够Agent 回答烹饪问题时得能从知识库里检索菜谱信息注入到大模型上下文里避免瞎编菜谱。// rag.service.js - 检索增强生成 import { search as searchKB } from ./knowledge-base.service.js; import { streamChat } from ./llm.service.js; export async function ragQuery(question, onChunk) { // 1. 从知识库检索相关信息 const relevant searchKB(question); const context relevant .map(e [${e.type}] ${e.content}) .join(\n); // 2. 构造增强 prompt let enhancedPrompt question; if (context) { enhancedPrompt 参考以下已知信息回答用户问题。 已知信息 ${context} 用户问题${question}; } // 3. 流式调用大模型 await streamChat(enhancedPrompt, onChunk); }RAG 在厨房场景的价值很实际。有一次我问冰箱里有鸡蛋和番茄能做什么RAG 先检索到食材清单再交给 Qwen 推理菜谱数字人回答番茄炒蛋、番茄蛋汤、鸡蛋番茄面都可以做你想做哪个。如果没有 RAG大模型不知道你冰箱里有什么推荐出来的菜你可能没食材做。llm.service.jsQwen 做菜谱推理// llm.service.js - Qwen 大模型对接 const LLM_API https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions; const LLM_KEY your_dashscope_api_key; const SYSTEM_PROMPT 你是厨房AI助手大厨帮用户解决烹饪问题。 你的特点 1. 回答简洁2-3句话因为用户在做饭手忙脚乱没空听长篇大论 2. 步骤清晰用先...再...最后...的句式 3. 记住用户的口味偏好通过RAG注入推荐菜谱时自动规避忌口食材 4. 涉及油温、火候给具体描述比如筷子放进去周围冒密集小泡就是七成热 5. 用户问到做不出来的菜时给出替代方案 6. 语气热情像厨房里的大厨在教你做菜; export async function streamChat(userMessage, onChunk, context []) { const messages [ { role: system, content: SYSTEM_PROMPT }, ...context.slice(-6), { role: user, content: userMessage }, ]; const response await fetch(LLM_API, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${LLM_KEY}, }, body: JSON.stringify({ model: qwen-plus, messages, stream: true, }), }); const reader response.body.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop() || ; for (const line of lines) { if (!line.startsWith(data: ) || line data: [DONE]) continue; try { const json JSON.parse(line.slice(6)); const content json.choices?.[0]?.delta?.content; if (content) onChunk(content); } catch (e) { /* 忽略解析异常 */ } } } }System Prompt 里特意写了油温给具体描述——大模型容易说七成热油但很多人不知道七成热是多热。加了这条后Qwen 回答变成了筷子放进去周围冒密集小泡就是七成热实用多了。KitchenAssistant.vue把大脑和身体缝起来这是最关键的一步——把四个 service 的输出缝到数字人表达上。核心链路语音输入 → RAG 检索 Qwen 推理 → 流式文本 → 星云 SDK 参数流驱动 → 数字人说话。!-- KitchenAssistant.vue -- template div classkitchen-container div idavatar-sdk/div div idsubtitle classsubtitle/div div classstatus-bar span v-ifcooking正在做{{ currentDish }} · 第{{ currentStep }}步/span span v-else classidle-hint说大厨唤醒我/span /div /div /template script setup import { ref, onMounted, onBeforeUnmount } from vue; import { initXingyun, speak, interactiveIdle, speakWithAction, destroy } from ../services/xingyun.service.js; import { ragQuery } from ../services/rag.service.js; import { savePreference, saveProgress, search, getKB } from ../services/knowledge-base.service.js; const cooking ref(false); const currentDish ref(); const currentStep ref(0); let pendingText null; // 语音状态管理 function handleVoiceState(res) { const state (typeof res string) ? res : (res.state || res.data); if ((state end || state idle) pendingText) { speak(pendingText, true, true); pendingText null; } } // 打断 function interruptAndSpeak(text) { pendingText text; interactiveIdle(); } // 主对话流程 async function processCommand(msg) { // 记忆指令 if (msg.startsWith(记住) || msg.startsWith(我不吃)) { const pref msg.replace(/^(记住|我不吃)/, ).trim(); savePreference(pref || msg); speakWithAction(记下了以后做菜不放${pref || msg}。, ThumbsUp); return; } // 进度指令 if (msg.includes(记住进度)) { saveProgress(currentDish.value, currentStep.value); speakWithAction(进度已记住随时可以问我在做到哪一步。, ThumbsUp); return; } // RAG 检索 LLM 推理 → 流式驱动 let buf ; let count 0; await ragQuery(msg, (chunk) { buf chunk; count; if (count 6) { speak(buf, buf chunk, false); buf ; count 0; } }); if (buf) speak(buf, false, true); } onMounted(() { initXingyun(#avatar-sdk, your_appid, your_appsecret); // 开场白——根据时间问候 const hour new Date().getHours(); let greeting 我准备好啦今天想做什么菜; if (hour 10) greeting 早上好想做点早餐吗煎蛋还是下面条; else if (hour 16) greeting 该准备晚饭啦有什么想吃的吗; // 检查有无口味偏好个性化问候 const kb getKB(); if (kb.preferences.length 0) { greeting 我记着你不吃${kb.preferences[0].content}呢。; } speakWithAction(greeting, Hello); }); onBeforeUnmount(() { destroy(); }); /script style scoped .kitchen-container { width: 100%; height: 100%; position: relative; } #avatar-sdk { width: 100%; height: 100%; } .subtitle { position: absolute; bottom: 60px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.7); color: #fff; padding: 8px 20px; border-radius: 20px; font-size: 16px; max-width: 80%; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; } .subtitle.show { opacity: 1; } .status-bar { position: absolute; top: 12px; left: 12px; background: rgba(255, 255, 255, 0.9); padding: 4px 12px; border-radius: 12px; font-size: 13px; color: #333; } .idle-hint { color: #999; } /style定时提醒烹饪场景的刚需做菜最怕忘时间——红烧肉炖久了肉烂、蒸蛋蒸久了有孔。我在 KitchenAssistant.vue 里加了定时任务让数字人主动开口提醒// 定时提醒模块 const timers new Map(); function setCookingTimer(dish, step, minutes) { const timerId setTimeout(() { speakWithAction( ${dish}的${step}已经${minutes}分钟了该看看了, Guide ); }, minutes * 60 * 1000); timers.set(${dish}-${step}, timerId); } // 语音触发大厨设个5分钟的定时 if (msg.includes(定时) || msg.includes(设个)) { const match msg.match(/(\d)分钟/); if (match) { const mins parseInt(match[1]); setCookingTimer(currentDish.value || 当前菜, currentStep.value || 这步, mins); speakWithAction(好${mins}分钟后提醒你。, ThumbsUp); return; } }通过 SSML 的 KA 指令数字人提醒时会配合动作——指引手势表示该看看锅了比手机闹钟有效太多。有一次我在客厅等红烧肉炖好数字人从厨房屏幕喊红烧肉已经炖了 30 分钟了该收汁了声音穿过客厅都能听到——这种主动交互是纯文本 Agent 完全做不到的。实际用了一周的感受认知层的表现。RAG 知识库的组合让数字人真正有了记忆。第一次跟它说记住我不吃香菜之后推荐菜谱全部自动规避香菜。做到一半问我到哪一步了它能从进度记录里回答。这种跨会话的持久认知比堆 context 强太多。具身层的体验。过去拿手机查阅菜谱手上沾满油污还要小心触碰屏幕如今壁挂屏幕部署具身交互智能体纯语音即可提问无需触摸设备。原生支持交互打断播报烹饪步骤途中临时提问 “盐放多少”会立刻终止当前播报应答完成后继续讲解端到端约 500ms 响应真正做到 “开口即可获得反馈”。参数流在厨房场景的价值。厨房屏幕就是个普通安卓平板百元级芯片跑 AI 端渲毫无压力。如果是视频流方案还得在厨房里放台高配主机——先不说成本厨房那油烟环境机器活不过三个月。参数流方案让数字人在低性能终端上稳定跑这在厨房场景是硬要求。主动播报。定时提醒功能用了一周就离不开了。以前用手机定闹钟响了还得去看是哪个菜的时间到了。现在数字人直接说红烧肉该收汁了不用看不用记比闹钟有效太多。Qwen 的表现。Qwen Plus 在烹饪问答上表现不错菜谱步骤清晰油温火候的描述加了具体判断方法后很实用。有一次我问糖醋排骨的糖醋比例它回答排骨和糖醋的比例是 1:1:1也就是一斤排骨配一两糖一两醋精准到位。Trae 的开发效率。项目从零到跑通大约一天。Trae 生成的服务层分层很干净四个 service 模块职责清晰。中文注释理解能力比预期好写检索知识库并注入上下文这种注释它直接帮你生成 RAG 框架代码。整体改单个模块不影响其他后续迭代很方便。写在最后完成这个 Demo 之前我曾经以为 Agent 只需要把推理、记忆、工具调用这类 “大脑能力” 做到足够强就可以。搭建完这套厨房项目之后我的认知发生改变Agent 的认知大脑解决 “能不能思考”具身交互智能解决 “能不能被真实场景所使用”。厨房、车间、展厅这类双手无法自由操作屏幕的场景如果缺少完整的具身交互表达能力Agent 即便再聪明也属于无法落地的工具。魔珐星云依托AI 端侧解算 自研参数流架构为各类 Agent 补齐完整的具身交互智能体系。它不去解决 “Agent 懂不懂烹饪” 这类认知问题而是解决核心落地难题Agent 如何在厨房环境下实现自然的人机交互。具身交互 Agent 会成为下一代人机交互主流入口本项目的厨房实战充分印证该判断壁挂屏幕部署具身交互智能体语音提问、主动定时提醒收汁整套交互体验远优于翻阅手机查阅菜谱。大模型让 AI 学会了思考下一步是让 AI 进入终端以具身的方式与人交互。厨房只是其中一个场景但它是最高频、最日常的那个——毕竟人天天都要吃饭。数字人体验