基于 DeepSeek + 魔珐星云具身驱动 SDK 实现手机推荐员
一、项目背景
随着大模型能力的不断增强,传统的网页客服已经不再局限于“文字问答”。如果将大语言模型与 3D 数字人结合起来,就可以实现一个能够“听懂需求、生成回答、语音播报、表情动作同步”的智能导购系统。
本项目尝试实现一个面向手机门店场景的 AI 数字导购员。用户可以输入自己的购机需求,例如预算、品牌偏好、拍照、游戏、续航、轻薄等要求,系统会调用大模型生成口语化导购回复,并驱动 3D 数字人进行语音播报和动作表达。
最终目标是实现一个类似线下门店导购的交互体验:
用户输入需求,AI 负责分析和推荐,数字人负责自然表达。
二、项目最终效果说明
项目页面整体分为两个区域:
左侧是 3D 数字人展示区域,用于加载魔珐星云数字人。
右侧是聊天交互区域,包括对话记录、快捷需求按钮和输入框。
用户可以直接输入:
预算3000元,想要拍照好一点的手机
也可以点击快捷标签,例如:
3000元拍照手机
4000元游戏手机
长辈用机
轻薄安卓手机
AI 会先收集用户的购机信息,例如预算、品牌偏好、日常用途和核心需求。如果信息不完整,AI 会继续追问;如果信息足够,则推荐 2 到 3 款适合的手机,并给出推荐理由和避坑提醒。
三、实际效果图

四、技术选型
本项目主要使用以下技术:
| 技术 | 作用 |
|---|---|
| HTML / CSS / JavaScript | 前端页面与交互逻辑 |
| Node.js | 搭建本地后端服务 |
| Express | 提供后端 API 接口 |
| dotenv | 读取本地环境变量 |
| DeepSeek API | 生成导购回复 |
| 魔珐星云具身驱动 JS SDK | 驱动 3D 数字人说话、表情和动作 |
| PyCharm | 项目开发工具 |
整体思路是:
前端负责展示页面、接收用户输入、播放数字人。
后端负责安全调用 DeepSeek,不把大模型密钥暴露在浏览器里。
魔珐 SDK 负责加载数字人,并根据 AI 回复文本进行语音合成、口型同步和动作驱动。
五、项目结构设计
项目整体结构如下:
ai-phone-shopping-guide/
├── index.html # 前端页面
├── server.js # Node.js 后端服务
├── package.json # 项目依赖配置
├── .env # 环境变量配置,不提交到公开仓库
└── .gitignore # Git 忽略配置
六、整体实现流程
整个项目可以拆成四个核心步骤:
第一步,搭建前端页面,包括数字人区域和聊天区域。
第二步,使用 Express 搭建后端接口,用于调用 DeepSeek。
第三步,前端调用后端接口获取 AI 回复。
第四步,将 AI 回复传给魔珐 SDK,驱动数字人说话。
整体流程如下:
用户输入购机需求
↓
前端发送请求到本地后端 /api/chat
↓
后端携带系统提示词请求 DeepSeek
↓
DeepSeek 返回导购回复
↓
前端显示回复
↓
魔珐 SDK 驱动数字人口播回复
七、前端页面设计
前端页面主要分为两个区域:
左侧是数字人容器:
<div class="avatar-box">
<div id="sdk"></div>
</div>
右侧是聊天区域:
<div class="chat-box">
<div class="header">
<h1>AI 手机数字导购员</h1>
<p>我会根据预算、品牌偏好、用途和核心需求,为顾客推荐合适机型。</p>
</div>
<div class="chat-log" id="chatLog"></div>
<div class="quick-tags">
<button class="tag">3000元拍照手机</button>
<button class="tag">4000元游戏手机</button>
<button class="tag">长辈用机</button>
<button class="tag">轻薄安卓手机</button>
</div>
<div class="input-area">
<input id="userInput" placeholder="输入你的购机需求" />
<button id="sendBtn">发送咨询</button>
</div>
</div>
页面布局采用左右分栏:
左侧固定宽度,用于显示数字人。
右侧自适应宽度,用于聊天交互。
同时加入了移动端适配,当屏幕较窄时,页面会变成上下布局。
八、魔珐星云 JS SDK 接入
本项目使用的是魔珐星云具身驱动 JS SDK。
首先需要在页面中引入 SDK:
<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>
然后创建数字人实例。
这里需要特别注意,SDK 文档中使用的是 containerId,而不是普通的 DOM 对象。
核心结构如下:
const avatarSdk = new XmovAvatar({
containerId: "#sdk",
appId: "你的魔珐AppID",
appSecret: "你的魔珐AppSecret",
gatewayServer: "https://nebula-agent.xingyun3d.com/user/v1/ttsa/session",
hardwareAcceleration: "prefer-hardware",
onMessage(message) {
console.log("SDK message:", message);
},
onStateChange(state) {
console.log("SDK state:", state);
},
onStatusChange(status) {
console.log("SDK status:", status);
},
onVoiceStateChange(status) {
console.log("SDK voice status:", status);
}
});
初始化数字人时,需要调用 init 方法:
await avatarSdk.init({
initModel: "normal",
onDownloadProgress(progress) {
console.log("数字人资源加载进度:", progress);
}
});
驱动数字人说话时,使用 speak 方法:
avatarSdk.speak("欢迎使用 AI 手机数字导购员", true, true);
这里的三个参数分别表示:
第一个参数:要播报的文本或 SSML
第二个参数:是否是本次说话开始
第三个参数:是否是本次说话结束
在本项目中,我们主要使用非流式方式,因此通常写成:
avatarSdk.speak(text, true, true);
页面关闭时,建议释放 SDK 连接:
window.addEventListener("beforeunload", function () {
if (avatarSdk && avatarSdk.destroy) {
avatarSdk.destroy();
}
});
这样可以避免数字人连接没有释放,导致并发房间占用。
九、DeepSeek 后端接口设计
为了避免 DeepSeek API Key 暴露在前端,本项目没有在 index.html 中直接请求 DeepSeek,而是通过 Node.js 后端中转。
前端只请求自己的后端接口:
fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
userText,
history
})
});
后端再读取 .env 中的 DeepSeek Key,去请求 DeepSeek 接口。
后端核心框架如下:
import express from "express";
import dotenv from "dotenv";
dotenv.config();
const app = express();
app.use(express.json());
const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY;
const DEEPSEEK_URL = process.env.DEEPSEEK_URL;
app.post("/api/chat", async (req, res) => {
const { userText, history = [] } = req.body;
const messages = [
{
role: "system",
content: buildSystemPrompt()
},
...history,
{
role: "user",
content: userText
}
];
const response = await fetch(DEEPSEEK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${DEEPSEEK_API_KEY}`
},
body: JSON.stringify({
model: "deepseek-chat",
messages
})
});
const data = await response.json();
res.json({
reply: data.choices[0].message.content
});
});
app.use(express.static("."));
app.listen(3000, () => {
console.log("服务已启动:http://localhost:3000");
});
这里没有放完整代码,只展示了核心框架。
实际开发时还需要加入错误处理,例如:
if (!DEEPSEEK_API_KEY) {
return res.status(500).json({
error: "服务端未配置 DEEPSEEK_API_KEY"
});
}
还需要处理 DeepSeek 返回为空、接口请求失败等情况。
十、环境变量 .env 配置
.env 是环境变量配置文件,用来保存不适合写在前端代码中的敏感信息。
本项目中的 .env 大致如下:
DEEPSEEK_API_KEY=你的DeepSeek密钥
DEEPSEEK_URL=https://api.deepseek.com/v1/chat/completions
PORT=3000
同时建议创建 .gitignore:
node_modules
.env
这样可以避免依赖目录和密钥文件被误提交。
十一、系统提示词设计
本项目的关键点之一是系统提示词。
因为我们要做的是“手机导购员”,不是普通聊天机器人,所以需要给大模型设置清晰的角色和规则。
系统提示词大致包含以下约束:
你是线下门店专业手机导购数字人,沟通温和、自然、不强行推销。
你需要优先补齐用户缺失信息,每次只问一个问题。
信息收集顺序为:
预算
品牌偏好
日常用途
核心需求
核心需求包括:
游戏
拍照
续航
轻薄
护眼
系统流畅
老人使用
学生使用
当信息足够时,推荐 2 到 3 款机型。
每款机型需要说明:
大致价格段
核心卖点
适配理由
最后给出购机避坑提醒。
输出需要口语化,适合数字人口播。
不要使用 Markdown。
不要输出表格或复杂符号。
这样设计的目的是让 AI 回复更像真实导购,而不是机械地罗列参数。
例如用户只输入:
你好
AI 不应该直接推荐手机,而应该回复类似:
你好呀,欢迎来看手机。请问你这次购机大概的预算是多少呢?
如果用户输入:
预算3000,主要拍照,想要安卓
AI 可以继续追问品牌偏好,或者在信息足够时直接给出推荐。
十二、前端聊天逻辑
前端聊天逻辑主要包含四个步骤:
第一步,获取用户输入。
第二步,将用户消息显示到页面。
第三步,请求后端接口获取 AI 回复。
第四步,将 AI 回复显示到页面,并交给数字人口播。
核心结构如下:
async function sendChat() {
const text = userInput.value.trim();
if (!text || isSending) {
return;
}
addMsg(text, true);
userInput.value = "";
setSendingState(true);
const reply = await getAiReply(text);
addMsg(reply, false);
await speakByAvatar(reply);
setSendingState(false);
}
为了避免用户连续点击按钮导致重复请求,可以加入状态控制:
function setSendingState(sending) {
isSending = sending;
sendBtn.disabled = sending;
sendBtn.innerText = sending ? "思考中..." : "发送咨询";
userInput.disabled = sending;
}
这样用户体验会更稳定。
十三、数字人播报逻辑
数字人播报封装成一个独立方法:
async function speakByAvatar(text) {
if (!isAvatarReady || !avatarSdk || !text) {
return;
}
try {
avatarSdk.speak(text, true, true);
} catch (error) {
console.error("数字人播报失败:", error);
}
}
如果数字人没有初始化成功,系统仍然允许文字聊天继续进行。
这是一个很重要的容错设计。
因为数字人 SDK 可能受到网络、授权、浏览器兼容性、资源加载等因素影响,如果数字人失败就让整个系统不可用,用户体验会比较差。
所以本项目采用:
数字人成功初始化:文字 + 数字人口播
数字人初始化失败:保留文字聊天功能
十四、开发过程中遇到的问题与解决方法
1. npm 无法识别
在 PyCharm 终端中执行:
npm run dev
如果出现:
无法将 “npm” 项识别为 cmdlet、函数、脚本文件或可运行程序的名称
说明当前环境找不到 npm。
通常原因是没有安装 Node.js,或者 Node.js 没有加入系统环境变量。
解决方法:
安装 Node.js LTS 版本。
安装完成后重新打开 PyCharm。
然后执行:
node -v
npm -v
如果能够显示版本号,说明安装成功。
2. 页面能打开,但数字人黑屏
如果页面右侧聊天正常,但左侧数字人是黑屏,通常说明魔珐 SDK 初始化失败。
可能原因包括:
AppID 或 AppSecret 没有填写真实值
SDK 文件没有加载成功
数字人应用配置不正确
当前访问地址不符合 SDK 要求
网络无法访问数字人资源
本项目中需要特别注意,魔珐 SDK 文档中写明:
SDK 支持 localhost 或 https 访问
因此本地调试时建议使用:
http://localhost:3000
不要使用:
http://127.0.0.1:3000
http://局域网IP:3000
直接双击 index.html
3. SDK 参数写错导致初始化失败
一开始容易把数字人容器写成:
container: document.getElementById("sdk")
但根据当前 SDK 文档,应该使用:
containerId: "#sdk"
同样,数字人说话方法也需要根据文档使用:
avatarSdk.speak(text, true, true);
而不是其它旧版本写法。
4. DeepSeek 密钥不能放前端
前端代码是可以被浏览器查看的。
如果把大模型密钥写在 index.html 中,例如:
const API_KEY = "真实密钥";
那么别人打开浏览器开发者工具就可能看到。
因此本项目采用 Node.js 后端代理方式:
前端 → 本地后端 → DeepSeek
前端不直接接触 DeepSeek API Key。
5. 聊天历史丢失问题
如果每次请求只发送用户当前输入,大模型就不知道前面聊过什么。
因此前端维护了一个简单的 chatHistory:
const chatHistory = [];
每次用户提问和 AI 回复后,都保存到历史中:
chatHistory.push({
role: "user",
content: userText
});
chatHistory.push({
role: "assistant",
content: reply
});
请求后端时,将历史一起传过去:
body: JSON.stringify({
userText,
history: chatHistory
})
后端再将历史拼接进 messages 中。
为了避免历史过长,可以只保留最近若干轮对话。
十五、运行步骤
1. 安装依赖
在项目目录下执行:
npm install
2. 配置环境变量
创建 .env 文件,填写 DeepSeek 配置:
DEEPSEEK_API_KEY=你的DeepSeek密钥
DEEPSEEK_URL=https://api.deepseek.com/v1/chat/completions
PORT=3000
3. 配置魔珐应用信息
在前端代码中填写魔珐星云后台创建的驱动应用信息:
const MOFA_APP_ID = "你的魔珐AppID";
const MOFA_APP_SECRET = "你的魔珐AppSecret";
4. 启动项目
执行:
npm run dev
启动后浏览器访问:
http://localhost:3000
十六、项目测试示例
可以输入以下内容测试项目效果。
测试:
你好
预期效果:
AI 友好回应,并询问购机预算。
测试拍照需求:
预算3000元,想买拍照好一点的安卓手机
预期效果:
AI 继续确认品牌偏好或核心需求,也可能直接根据已有信息推荐机型。
测试游戏需求:
预算4000左右,主要打游戏,续航也要好
预期效果:
AI 推荐偏性能、散热和续航的机型。
测试长辈用机:
给长辈买,要求屏幕大、续航好、系统简单
预期效果:
AI 推荐适合长辈使用的大屏、长续航、操作简单的手机。
十七、项目优化方向
当前项目已经实现了基础的 AI 数字导购能力,但如果要继续完善,还可以从以下几个方向优化。
1. 接入真实商品库
目前手机推荐主要依赖大模型知识。
如果要用于真实门店,最好接入商品库,例如:
门店库存
商品价格
当前促销活动
商品参数
商品上下架状态
这样 AI 推荐时就不会推荐已经停售或门店无货的机型。
可以将商品库设计为 JSON、数据库或 ERP 接口。
2. 增加流式回复
当前项目采用的是一次性返回完整回复。
后续可以使用流式接口,让 AI 边生成边返回,再结合魔珐 SDK 的流式 speak 能力,实现更自然的实时口播。
流式调用时需要控制:
第一次 speak:is_start = true
中间 speak:is_start = false,is_end = false
最后 speak:is_end = true
3. 增加语音输入
当前用户通过文字输入购机需求。
后续可以加入浏览器语音识别能力,让用户直接说:
我想买一台三千左右拍照好看的手机
系统识别成文字后再发送给大模型。
这样更接近真实门店导购体验。
4. 增加商品卡片展示
目前 AI 回复主要是文字。
后续可以在右侧聊天区展示商品卡片,包括:
手机图片
价格
核心参数
推荐理由
购买按钮
也可以结合魔珐 SDK 的 Widget 能力,在数字人播报过程中展示图片、字幕或视频内容。
5. 优化数字人状态
可以根据交互流程切换数字人状态:
用户输入时:listen
AI 思考时:think
AI 回复时:speak
空闲时:interactive_idle
这样数字人的表现会更自然。
十八、总结
本项目实现了一个基于 DeepSeek 和魔珐星云具身驱动 SDK 的 AI 手机数字导购员。
项目核心价值在于:
大模型负责理解和生成导购话术
数字人负责语音、口型、动作和表情表达
前端负责交互展示
后端负责安全调用大模型接口
通过这个项目,可以初步体验到“大模型 + 3D 数字人”在智能导购场景中的落地方式。
相比传统文字客服,数字人导购更直观、更自然,也更适合门店大屏、展厅导览、产品介绍等场景。
当然,当前版本仍然是一个 Demo 原型。如果要进一步应用到真实业务中,还需要接入真实商品库、完善权限安全、优化语音输入、增加商品卡片和部署到 HTTPS 环境。
整体来看,这个项目非常适合作为学习大模型应用开发、数字人 SDK 接入、前后端联调和智能导购场景落地的实践案例。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)