news 2026/7/18 22:31:16

从纯文本到具身交互智能:魔珐星云+国产大模型搭建拥有 3D 具身的展厅Agent

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从纯文本到具身交互智能:魔珐星云+国产大模型搭建拥有 3D 具身的展厅Agent

一、交互范式重构:大模型如何从“理解世界”走向“进入现场”

1.1 从文本问答到具身在场:AI 交互边界的重新定义

以 DeepSeek 为代表的大模型,已经赋予 AI 完善的语言理解、逻辑推理、文本生成能力,各类纯文本 Agent 也成为主流工具。但这类 AI 只拥有 “思考大脑”,缺少完整具身交互智能,落地展厅、门店等线下终端时,仅靠文字聊天框无法实现真人化现场沟通。

在办公和编程场景中,用户可以耐心阅读长文本,也可以随时复制、修改和反复追问;而在展厅环境中,参观者通常站在屏幕前,希望直接提出问题,并迅速判断系统是否已经接收、是否正在处理以及何时开始回答。若整个过程仍依赖键盘输入和大段文字阅读,大模型虽然接入了展厅系统,却还没有真正融入展厅的交互环境。

真实场景中的交流不只有信息内容,还包括声音、节奏、姿态和即时反馈。面对真人讲解员时,我们能够从眼神、表情和动作中判断对方是否正在倾听,也可以在讲解过程中随时插话,调整交流方向。文本 AI 通常只能呈现最终答案,用户很难感知其等待、处理和表达过程。

因此,AI 正在经历一次从文本交互具身交互智能的变化。具身交互智能不是简单给 AI 贴虚拟皮肤,也不止实体人形机器人,是一套完整人机交互体系。网页中的 3D 数字人、展厅大屏上的虚拟讲解员、移动应用中的数字助手以及人形机器人,都可以成为 AI 的身体载体。

在我看来,具身交互智能是指:

让 AI 拥有可被看见的身体、可被感知的状态、可自然表达的语音表情动作,以及可实时响应、可随时打断的交互能力。

这意味着,AI 的回答不再只是屏幕上的一段文字,而是一个完整的表达过程:用户提出问题后,智能体能够呈现交互状态,通过语音和动作组织答案,并允许用户中途打断或继续追问。

从这个角度看,大模型负责 AI 认知推理,Agent 负责任务调度执行;魔珐星云作为全域具身交互智能开放平台,补齐 AI 缺失的实体载体、多模态表达、实时双向交互整套能力。它绝非简单数字人渲染工具,也不是 Agent 外层视觉装饰,依托自研参数流、AI 端侧解算,为所有大模型、Agent 提供标准化具身交互底层能力。

换句话说,大模型解决 AI 的“大脑”,魔珐星云补齐 AI 的“身体、表达和交互”。只有当认知能力与表达载体真正连接起来,AI 才可能从一个隐藏在聊天框中的文本工具,转变为能够进入真实服务场景的智能体。

为了验证这种交互方式,本文选择展厅讲解作为具体场景,搭建一个由 DeepSeek 与魔珐星云 SDK 共同驱动的 3D 讲解 Agent。

二、从能力拼接到体验断层:传统数字人方案的系统性瓶颈

2.1 LLM、TTS 与渲染链路之间的协同断点

从功能层面看,构建一个“能回答、会说话、有形象”的数字人并不复杂:大模型负责理解问题并生成文本,TTS 将文本转换为语音,数字人渲染模块再完成口型、表情和动作呈现。多个模块依次连接,似乎已经具备完整的交互能力。

但真实体验并不取决于单个模块是否可用,而取决于整条链路能否协同运行。当大模型、语音合成和数字人渲染彼此独立时,开发者仍需自行处理文本切分、语音队列、口型同步、动作衔接、状态切换与异常恢复。模型已经输出内容,语音却仍在等待;声音已经播放,人物动作尚未跟进;用户发起新问题后,旧回答仍在继续。这些看似细微的不同步,最终都会转化为用户能够直接感知的等待感和割裂感。

因此,具身交互的技术门槛并不是简单地把 LLM、TTS 和 3D 形象串联起来,而是让认知、声音、动作和交互状态围绕同一轮对话形成统一节奏。

2.2 从预制播报到实时交互的能力分界

传统数字人更擅长处理预先确定的内容,例如宣传视频、固定讲解词或循环播放脚本。这类场景以内容展示为核心,语音、口型和动作可以提前制作,用户也不需要在播放过程中改变交流方向。

展厅讲解 Agent 面对的则是动态交互。参观者的问题无法提前预设,回答需要由大模型实时生成,数字人的语音、表情和动作也必须随内容同步变化。更重要的是,参观者可能在讲解过程中继续追问、补充条件,甚至直接插话,系统需要迅速结束旧任务并进入下一轮交流。

这正是“能播报”与“能交互”的根本区别。前者关注内容是否能够被完整呈现,后者关注智能体能否理解现场变化、及时组织表达,并保持多轮对话的连续性。数字人的核心价值也由此从“像人展示内容”,转向“代表 AI 与人自然交流”。

2.3 参数流与端侧技术重构实时交互链路

面对实时交互需求,继续依赖多套独立系统叠加,不仅会拉长处理链路,也会增加云端渲染、网络传输和终端部署的复杂度。尤其在展厅大屏、门店终端和公共服务设备中,数字人既要保持稳定呈现,也要及时回应用户的提问与打断。

魔珐星云采用自研参数流架构,并结合 AI 端渲和端侧解算技术,将具身表达所需的渲染与解算能力下沉至终端。与持续传输完整视频画面的方式不同,参数流侧重传递数字人的驱动信息,再由终端完成实时呈现,从而打通内容生成、语音表达、动作驱动和画面渲染之间的链路。

在这一体系中,大模型继续承担理解与生成,魔珐星云则将文本内容转化为语音、口型、表情和身体动作,并通过 SDK 接入网页、应用、大屏、机器人及 AR/VR 等终端形态。官方资料给出的端到端约 500ms 响应、实时打断、多终端兼容等能力,也正是面向具身交互场景而设计。

由此可见,数字人从展示工具走向具身智能体,关键不在于增加多少独立功能,而在于是否建立了一条真正端到端、可实时响应、可持续交互的表达链路。

三、重构具身交互链路:DeepSeek 与魔珐星云的协同架构

3.1 从认知生成到具身呈现:展厅讲解 Agent 的分层设计

搭建展厅讲解 Agent 时,并不是简单地把 DeepSeek 的回答交给一个数字人朗读,而是将系统拆分为认知生成、对话编排和具身交互三个层次。

参观者提出问题后,DeepSeek 负责理解问题并生成符合展厅讲解语境的回答;应用层负责维护对话上下文、接收流式内容、划分可播报语句,并处理新旧任务的切换;魔珐星云 SDK 则负责加载和驱动 3D 数字人,将文本内容转化为语音、口型、表情与动作,最终呈现在网页或展厅屏幕上。

三层架构的价值在于职责清晰。更换大模型不会改变数字人的呈现方式,调整角色形象和音色也不需要重写对话逻辑。当后续需要加入展品知识库、路线查询或业务接口时,只需在 Agent 编排层扩展对应能力。

3.2 从文本回答到多模态表达:星云 SDK 承担的关键环节

DeepSeek 返回的结果本质上仍是一段文本。要将这段内容变成用户能够直接感知的讲解过程,还需要处理声音生成、口型同步、人物动作、角色状态以及 3D 画面呈现。

魔珐星云官方将具身驱动描述为将 AI 表达由文本升级为 3D 多模态。其 JavaScript SDK 提供实时 3D 数字人渲染与驱动、语音合成与口型同步、多状态行为控制,以及字幕、图片、视频等 Widget 事件能力。开发者还可以通过状态、语音和网络回调,掌握数字人的运行过程。

在本文的展厅 Demo 中,星云 SDK 主要承担四项工作:首先在网页中初始化加载 3D 具身交互智能数字人;其次接收 DeepSeek 生成的讲解文本;同步驱动数字人完成语音、口型和动作表达;最后实时反馈交互状态,支撑双向对话。

由此,DeepSeek 与魔珐星云并不是简单的两个接口串联,而是分别负责“讲什么”和“如何讲”。前者生成讲解内容,后者让内容获得形象、声音和动作。

3.3 从信息展示到智能服务:展厅屏幕的交互升级

传统展厅屏幕承担的主要是内容展示:按照预设顺序播放图片、视频和讲解词。它可以持续、稳定地传递信息,却很难根据参观者的兴趣改变讲解内容。当用户对某件展品产生疑问时,屏幕展示的仍然是既定内容,真正的交流还需要依赖现场工作人员。

接入大模型后,屏幕开始具备理解问题和动态组织内容的能力,但一段生成在界面上的文字,还不足以替代现场讲解。展厅环境强调直观、即时和连续的体验,参观者需要看到讲解者、听到讲解内容,并通过角色的口型、表情和动作感知交流过程。

DeepSeek 与魔珐星云的组合,正好对应了这两个层面的需求:DeepSeek 根据参观者的问题生成讲解内容,魔珐星云则将文字回答转化为数字人的语音、口型、表情和动作。原本只能循环播放内容的屏幕,由此获得了一位能够围绕现场问题进行动态表达的 3D 讲解员。

此时,展厅数字人的价值也不再只是“形象更生动”,而是成为展览内容与参观者之间的交互入口。例如,参观者可以继续追问展品背景、补充问题或改变讲解方向;系统则围绕当前对话重新组织内容,并通过数字人完成下一轮表达。屏幕由单向的信息出口,转变为能够接收问题、生成回答并持续交流的服务终端。

四、基于魔珐星云 JS SDK 的展厅讲解 Agent 实战

前面几章讨论的是为什么展厅 Agent 需要具身表达层,以及 DeepSeek 和魔珐星云分别负责什么。从这一章开始,不再停留在架构图上,直接进入接入过程。

这次使用的技术栈是 Vue 3、TypeScript 和 Vite,DeepSeek-V4-Pro 作为讲解内容生成模型,魔珐星云 JavaScript SDK 负责数字人的渲染与表达。

下面按照你原稿中“后台配置—SDK 初始化—流式驱动—动作与 Widget—中断状态机”的实战顺序重写,业务统一为DeepSeek-V4-Pro 展厅讲解 Agent。原稿里的注册流程和开发踩坑得以保留,但删去了与商场服务台相关的内容。

4.1 在魔珐星云后台创建具身交互智能体应用

写代码之前,需要先在魔珐星云平台中把数字讲解员配置出来。角色形象、音色和表演风格都属于应用配置,SDK 初始化时只需要提供对应应用的 App ID 和 App Secret。

Step 1:注册账号

打开专属链接 https://xingyun3d.com/?utm_campaign=jixinghuiKoc228&utm_source=jixinghuiKoc&utm_medium=&utm_term=&utm_content= ,点右上角"登录注册"。

Step 2:进入体验中心 → 具身驱动

登录之后第一件事不是急着创建应用,先去体验中心 → 具身驱动那一页玩一玩。

这个页面是星云的"试驾区",你能直接在浏览器里和内置数字人对话——我看到的有"睡前陪伴"(萌萌的水獭形象,海滩主题)、"元气段子手"、"AI 男友"等几个内置形象。直接点底部任意一个,然后右下角"开始聊天",就能和它说话。

Step 3:点击"创建具身应用"

体验完之后,右上角"创建具身应用"按钮就是我们的目的地。点进去会让你做几个关键选择:

选择形象

这是最影响最终用户体验的一步。星云的形象库分几个风格档位:超写实、美型、卡通、二次元。服务台场景的形象选型逻辑:

  • 不要选超写实——商场环境光线复杂,超写实形象在弱光或强光下容易出现"恐怖谷"。除非你的部署场景光线极度可控(比如展厅射灯),否则避开
  • 美型/卡通是首选——既有人形识别度,又有"我是数字人"的合理预期,不会让顾客产生"对方是不是真人"的错位感
  • 二次元谨慎选——除非是动漫主题商场或者目标客群是 Z 世代,普通商场用二次元会显得不正式

我最终选了一个偏成熟的女性卡通形象,30 岁左右,职业装感——既亲切又有"服务员"该有的专业感。

选择音色

音色库我快速试听了一遍,大致分:亲切女声 / 专业女声 / 活泼少女音 / 中年男声 / 商务男声。

服务台场景我选的是亲切但不娃娃音的女声——太活泼会显得不专业,太成熟会显得有距离感。这一步建议你听一遍每一个音色再选,文字描述很难还原音色的实际气质。

选择表演风格

这个我理解是数字人的"基础节奏"——平稳/活泼/专业。服务台选"专业",讲解类场景可以选"活泼"。

Step 4:进入"应用管理",拿到 appId / appSecret

应用创建完之后,左侧菜单"应用管理"页面里能看到刚才创建的应用,点进去 → 查看密钥,就能拿到appIdappSecret这两个就是后面所有 SDK 代码里要用的凭证

到这一步,准备工作就完成了。下面进入代码部分。

4.2 接入 JavaScript SDK,让数字讲解员开口

前端工程使用 Vue 3、TypeScript 和 Vite。项目结构不需要设计得过于复杂,只要把数字人驱动、模型请求和流式播报分开即可:

deepseek-exhibition-agent/ ├─ src/ │ ├─ components/ │ │ ├─ AvatarPanel.vue │ │ └─ ExhibitCard.vue │ ├─ composables/ │ │ └─ useStreamSpeak.ts │ ├─ App.vue │ └─ main.ts ├─ server/ │ ├─ index.ts │ └─ data/ │ └─ exhibits.json └─ index.html

我使用 ChatGPT 辅助拆分了组件和 TypeScript 类型,但给它设置了一个明确约束:不得根据常见数字人 SDK 的经验自行补充接口,所有方法都必须来自魔珐星云官方文档。

引入 SDK

index.html中直接引入 JavaScript SDK:

<!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>DeepSeek 展厅讲解 Agent</title> </head> <body> <div id="app"></div> <script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script> <script type="module" src="/src/main.ts"></script> </body> </html>

然后将 SDK 封装为独立的AvatarPanel.vue

<template> <div class="avatar-wrapper"> <div id="xmov-avatar" class="avatar-container"></div> <div v-if="!ready" class="loading-mask"> <p>数字讲解员资源加载中:{{ progress }}%</p> <progress :value="progress" max="100"></progress> </div> </div> </template> <script setup lang="ts"> import { onBeforeUnmount, onMounted, ref } from "vue"; declare const XmovAvatar: any; const emit = defineEmits<{ ready: []; voiceState: [status: string]; widget: [type: string, data: Record<string, unknown>]; }>(); const ready = ref(false); const progress = ref(0); let avatar: any = null; onMounted(async () => { avatar = new XmovAvatar({ containerId: "#xmov-avatar", appId: import.meta.env.VITE_XMOV_APP_ID, appSecret: import.meta.env.VITE_XMOV_APP_SECRET, gatewayServer: "https://nebula-agent.xingyun3d.com/user/v1/ttsa/session", hardwareAcceleration: "prefer-hardware", enableLogger: true, onMessage(message: { code?: number; message?: string }) { console.log("[Xmov message]", message); if (message.code && message.code >= 10000) { console.error("[Xmov error]", message.message); } }, onStateChange(state: string) { console.log("[Xmov state]", state); }, onVoiceStateChange(status: string) { console.log("[Xmov voice]", status); emit("voiceState", status); }, onNetworkInfo(info: { rtt: number; downlink: number; }) { console.log("[Xmov network]", info); }, proxyWidget: { widget_exhibit_card(data: Record<string, unknown>) { emit("widget", "exhibit_card", data); }, }, }); await avatar.init({ initModel: "normal", onDownloadProgress(value: number) { progress.value = value; console.log(`[Xmov loading] ${value}%`); }, }); ready.value = true; emit("ready"); }); onBeforeUnmount(() => { avatar?.destroy(); avatar = null; }); function speak( content: string, isStart: boolean, isEnd: boolean, ): void { if (!avatar) { throw new Error("数字讲解员尚未初始化"); } avatar.speak(content, isStart, isEnd); } function interactiveIdle(): void { avatar?.interactiveidle(); } function idle(): void { avatar?.idle(); } defineExpose({ speak, interactiveIdle, idle, }); </script> <style scoped> .avatar-wrapper { position: relative; width: 540px; height: 960px; overflow: hidden; background: #101522; } .avatar-container { width: 100%; height: 100%; } .loading-mask { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; background: rgba(8, 14, 28, 0.88); } </style>

官方 SDK 通过XmovAvatar创建实例,初始化时需要提供容器、App ID、App Secret 和服务地址。init()负责建立连接并加载资源,speak()负责驱动数字人播报,interactiveidle()可以将数字人切换回交互待机状态,页面卸载前则应调用destroy()清理资源。

首次加载不是“黑屏”

第一次启动页面时,数字人不会立即出现。SDK 需要先加载当前应用对应的角色、表情和动作资源,如果界面没有任何提示,容器很容易看起来像一块黑屏。

因此,我将onDownloadProgress直接绑定到了页面进度条上。官方文档说明,该回调范围为 0—100;如果首次所需资源加载失败,进度不会正常到达 100。

另一个容易踩到的问题是访问环境。SDK 使用的部分浏览器能力只支持localhost或 HTTPS。本地开发可以直接使用localhost,但通过普通 HTTP 局域网地址访问时,相关能力可能无法正常工作。([魔珐星云][1])

数字人初始化完成后,先使用固定欢迎语验证主链路:

avatarRef.value?.speak( "欢迎来到人工智能主题展区," + "我是本次展览的数字讲解员。" + "您可以向我了解展品原理、技术背景和参观路线。", true, true, );

speak()的三个参数分别是播报内容、是否为本轮流式内容的开始,以及是否为本轮流式内容的结束。一次性提交完整文本时,后两个参数都设置为true

点击测试按钮后,数字讲解员开始播放欢迎语,口型和身体动作随语音一起呈现。到这里,数字人这一侧已经跑通,接下来需要把固定文本替换为 DeepSeek-V4-Pro 的实时回答。

4.3 接入 DeepSeek-V4-Pro,实现流式具身讲解

服务端主要做三件事:接收参观者问题、加载相关展品资料、调用 DeepSeek-V4-Pro 并把流式内容转发给前端。

本次先使用一份本地exhibits.json保存展品资料:

[ { "id": "robot-arm-01", "name": "六轴工业机器人", "zone": "智能制造展区", "summary": "通过六个旋转关节完成多方向运动,可用于装配、搬运、焊接和精密操作。", "keywords": ["机械臂", "六轴", "机器人", "智能制造"] }, { "id": "edge-chip-01", "name": "边缘计算芯片", "zone": "人工智能展区", "summary": "在终端本地处理部分人工智能任务,降低数据频繁往返云端的需求。", "keywords": ["芯片", "边缘计算", "端侧AI"] } ]

服务端根据问题筛选相关展品,将资料与问题一起交给模型。DeepSeek 官方 API 当前支持deepseek-v4-pro,并通过stream: true以 SSE 形式返回内容增量;本文关闭思考模式,只将最终讲解文本传给数字人。

// server/index.ts import "dotenv/config"; import express from "express"; import { readFile } from "node:fs/promises"; const app = express(); app.use(express.json()); const exhibits = JSON.parse( await readFile( new URL("./data/exhibits.json", import.meta.url), "utf-8", ), ); const SYSTEM_PROMPT = ` 你是科技展厅的数字讲解员。 回答要求: 1. 只能根据提供的展品资料回答; 2. 不编造资料中不存在的信息; 3. 使用自然、清晰、适合口头讲解的中文; 4. 单次回答控制在100字以内; 5. 不使用Markdown标题、列表和表格; 6. 讲到具体展品时,可以插入对应的展品卡片指令。 `; app.post("/api/chat", async (req, res) => { const question = String(req.body?.question ?? "").trim(); if (!question) { return res.status(400).json({ message: "问题不能为空", }); } const matched = exhibits.filter((item: any) => item.keywords.some((keyword: string) => question.includes(keyword), ), ); const context = (matched.length ? matched : exhibits) .slice(0, 5) .map( (item: any) => `ID:${item.id} 展品:${item.name} 展区:${item.zone} 资料:${item.summary}`, ) .join("\n\n"); const upstream = await fetch( "https://api.deepseek.com/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.DEEPSEEK_API_KEY}`, }, body: JSON.stringify({ model: "deepseek-v4-pro", thinking: { type: "disabled", }, stream: true, messages: [ { role: "system", content: SYSTEM_PROMPT, }, { role: "user", content: `展品资料:\n${context}\n\n` + `参观者问题:${question}`, }, ], }), }, ); if (!upstream.ok || !upstream.body) { const detail = await upstream.text(); return res.status(502).json({ message: `DeepSeek 请求失败:${detail}`, }); } res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); const reader = upstream.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: ")) continue; const payload = line.slice(6).trim(); if (payload === "[DONE]") { res.write( `data: ${JSON.stringify({ done: true, })}\n\n`, ); res.end(); return; } const chunk = JSON.parse(payload); const token = chunk.choices?.[0]?.delta?.content ?? ""; if (token) { res.write( `data: ${JSON.stringify({ token, })}\n\n`, ); } } } res.end(); }); app.listen(3001, () => { console.log("Agent server: http://localhost:3001"); });

前端收到的不是一整段回答,而是一串持续到达的文本增量。这里不能每收到一个 Token 就立即调用speak():中文可能被切得过碎,SSML 标签也可能在中间断开,数字人甚至会把不完整的标签当作普通文本读出来。

我的处理方式是先把 Token 写入缓冲区,在完整句子或完整标签边界处再提交给 SDK:

// src/composables/useStreamSpeak.ts import type { Ref } from "vue"; interface AvatarInstance { speak: ( content: string, isStart: boolean, isEnd: boolean, ) => void; interactiveIdle: () => void; } export function useStreamSpeak( avatarRef: Ref<AvatarInstance | null>, ) { let buffer = ""; let firstChunk = true; const FIRST_FLUSH_THRESHOLD = 20; function start(): void { buffer = ""; firstChunk = true; } function feed(token: string): void { buffer += token; if ( firstChunk && buffer.length < FIRST_FLUSH_THRESHOLD ) { return; } if (!isSafeBoundary(buffer)) { return; } flush(false); } function finish(): void { flush(true); } function flush(isEnd: boolean): void { if (!buffer && !isEnd) return; if (buffer) { avatarRef.value?.speak( `<speak>${buffer}</speak>`, firstChunk, isEnd, ); buffer = ""; firstChunk = false; } } function isSafeBoundary(content: string): boolean { // 当前存在尚未闭合的 XML/SSML 标签 if (/<[^>]*$/.test(content)) { return false; } return /[。!?;!?]$/.test(content); } return { start, feed, finish, }; }

魔珐星云官方文档支持分段调用speak():第一段设置is_start=true,最后一段设置is_end=true,中间内容的两个标记均为false。文档也特别建议,首次流式提交前先积累一小段内容,避免数字人说话速度追上模型输出速度而产生停顿。

加入动作与展品信息卡

仅仅做到“边生成边播报”,数字人仍然可能像在机械念稿。下一步是使用 SSML 中的 KA 指令,为讲解增加动作。

例如欢迎参观者时,可以插入官方示例中的欢迎动作:

<speak> <ue4event> <type>ka_intent</type> <data> <ka_intent>Welcome</ka_intent> </data> </ue4event> 欢迎来到智能制造展区。 </speak>

魔珐星云 SDK 支持在speak()的 SSML 内容中嵌入语义 KA、技能 KA 等指令。具体可用动作应通过当前应用的 KA 查询结果确认,不能凭经验随意填写动作名称。

除了动作,我还增加了一个自定义的widget_exhibit_card事件。当讲解员介绍某件展品时,模型可以在合适的位置嵌入展品 ID:

<speak> 这件展品是六轴工业机器人。 <ue4event> <type>widget_exhibit_card</type> <data> <id>robot-arm-01</id> </data> </ue4event> 它通过六个旋转关节完成多方向运动, 常用于装配、搬运和焊接。 </speak>

前面初始化XmovAvatar时已经通过proxyWidget注册了该事件。父组件收到回调后,只需要根据 ID 查询本地展品数据:

function onWidget( type: string, data: Record<string, unknown>, ): void { if (type !== "exhibit_card") return; currentExhibit.value = exhibits.find( (item) => item.id === data.id, ) ?? null; }

这样,数字讲解员说到对应展品时,侧边区域会同步出现展品名称、图片、展区和核心介绍。SDK 官方提供onWidgetEventproxyWidget两种事件代理方式,也允许业务侧实现自定义 Widget。

4.4 实现可打断的多轮讲解

流式播报跑通以后,最后一个关键问题是中断。

参观者可能在讲解尚未结束时提出新问题。此时只停止声音还不够:旧的 DeepSeek 请求仍可能继续返回内容,前端缓冲区里也可能保留尚未提交的句子。如果不统一清理,新的问题开始以后,旧回答还会重新进入播报队列。

因此,一次完整中断需要同时处理三层状态:

模型层:终止或忽略旧 DeepSeek 请求 业务层:清空文本缓冲与旧任务标记 具身层:调用 interactiveidle() 中断当前状态

前端使用AbortController管理当前请求:

let activeController: AbortController | null = null; let requestVersion = 0; async function askAgent(question: string): Promise<void> { const version = ++requestVersion; activeController?.abort(); activeController = new AbortController(); avatarRef.value?.interactiveIdle(); streamSpeak.start(); const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ question }), signal: activeController.signal, }); if (!response.ok || !response.body) { throw new Error("讲解服务请求失败"); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let sseBuffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; if (version !== requestVersion) return; sseBuffer += decoder.decode(value, { stream: true, }); const events = sseBuffer.split("\n\n"); sseBuffer = events.pop() ?? ""; for (const event of events) { const line = event .split("\n") .find((item) => item.startsWith("data: "), ); if (!line) continue; const data = JSON.parse(line.slice(6)); if (data.token) { streamSpeak.feed(data.token); } if (data.done) { streamSpeak.finish(); } } } } catch (error) { if ( error instanceof DOMException && error.name === "AbortError" ) { return; } throw error; } } function interruptAndAsk( nextQuestion: string, ): void { requestVersion += 1; activeController?.abort(); avatarRef.value?.interactiveIdle(); void askAgent(nextQuestion); }

interactiveidle()在官方文档中的用途之一,就是打断当前状态并返回交互待机;一次完整speak()结束以后,文档也建议先通过interactive_idle完成状态切换,再开始下一轮播报。([魔珐星云][1])

数字讲解员的状态可以简化为:

interactive_idle ↓ 用户提问 waiting_model ↓ 第一段内容到达 speaking ↓ 播报结束 interactive_idle

当用户在speaking状态中再次提问时,则走另一条路径:

speaking ↓ 用户插话 interrupting ↓ 取消旧请求、清理旧缓冲 waiting_model ↓ 播放新回答 speaking

为了判断数字人什么时候真正开始和结束讲话,可以监听onVoiceStateChange。官方文档说明,speak()过程中会抛出语音开始和结束事件,可用于管理当前播报状态。

完成中断逻辑后,我连续测试了三种情况:

  1. 正常提问并等待完整回答;
  2. 长回答生成过程中继续追问;
  3. 数字人正在播报时立即改问另一件展品。

重点观察旧请求是否停止、缓存内容是否清空、新旧回答是否发生串音,以及数字人能否重新进入正常播报状态。

到这里,展厅讲解 Agent 的完整主链路已经打通:

这套 Demo 真正解决的并不是“让数字人读出一段大模型回答”,而是验证了具身交互智能对纯文本 Agent 的升级作用。DeepSeek 负责内容推理,魔珐星云补齐拟人交互全套能力,将单向文字输出升级为有形象、有情绪、可实时对话的完整现场交互。

当数字讲解员能够根据现场问题动态组织内容、同步展示展品信息,并允许参观者随时改变交流方向时,屏幕才从一个内容播放器,真正变成了可以交流的展厅服务入口。

可以。第五章直接承担全文收束,不再继续拆流式缓冲、中断机制等实现细节;这些都留在第四章实战中完成。第五章重点总结三件事:Demo 验证了什么、魔珐星云提供了什么价值、具身交互为什么不仅适用于展厅。

五、从展厅原型到具身交互落地:开发体验与价值总结

5.1 这次接入真正验证了什么

回看整个搭建过程,这个 Demo 完成的并不只是“让 DeepSeek 驱动一个数字人说话”。

如果只是将大模型输出接入 TTS,让页面播放一段语音,其实很快就能完成。但那种方案依旧停留在文本问答的延伸形态:模型负责生成,语音负责朗读,屏幕上的形象与整个对话过程没有形成真正的协同。

这次接入魔珐星云 SDK 后,DeepSeek 生成的讲解内容能够继续转化为数字人的语音、口型、表情和身体动作;当讲解涉及具体展品时,页面还能同步展示对应的信息卡;用户在播报过程中改变问题,系统也可以停止上一轮任务并切换到新的讲解内容。

从用户视角看,整个过程不再是“提交问题后等待一段文字”,而是:

提出问题 → 数字讲解员开始组织回答 → 语音、口型和动作同步呈现 → 展品信息随讲解出现 → 用户可以继续追问或中途插话

这些变化单独看都不复杂,但组合起来之后,屏幕的角色就不再只是一个负责播放内容的数字形象,而开始具备智能体的基本交互特征。

这也是这次实战最重要的结论:具身交互的价值不在于让 AI 看起来更像人,而在于让 AI 的理解、表达和反馈发生在同一条交互链路中。

DeepSeek 解决了“讲什么”,Agent 业务层负责组织展品资料、维护对话和管理任务,魔珐星云则解决“如何讲出来”。三者各自独立,又通过清晰的接口协同工作。

这种分层方式也为后续扩展留下了空间。未来无论是将本地展品数据替换为 RAG 知识库,还是接入路线查询、展项状态、票务信息和多语言能力,变化主要发生在认知层和业务层,数字人的具身表达链路不需要重新搭建。

5.2 魔珐星云提供的,不只是一个数字人形象

在接入之前,我对数字人产品的第一反应仍然是形象、音色和动作效果。但真正把 SDK 接进项目后能清晰感知,魔珐星云绝非单纯 3D 形象工具,而是标准化具身交互智能底层基础设施。整合语音合成、3D 渲染、表情肢体解算、交互状态管理全套能力,一套 SDK 即可完成完整拟人交互链路,解决各类 Agent 缺失具象表达的行业难题。

在第四章的实战中,这种整合最明显地体现在三个地方。

第一是流式表达。DeepSeek 不需要等完整回答全部生成后,再交给数字人一次性播放,而是可以按照语义片段持续提交。数字讲解员能够更早开始表达,现场交流中的等待感也随之降低。

第二是非语言表达。语音只是讲解的一部分,口型、表情和身体动作决定了这段内容是否像一次真正的交流。通过 KA 指令,模型生成的语言内容可以与动作意图一起进入表达链路,数字人不再只是站在屏幕中机械念稿。

第三是业务界面协同。Widget 事件让数字人的讲解能够与展品卡片、图片、字幕和其他页面内容同步。它不是把一个独立数字人窗口叠加到现有页面上,而是让数字人表达与业务界面形成统一节奏。

从底层技术路径看,魔珐星云强调参数流、AI 端渲和端侧解算。其核心思路不是持续向终端传输已经渲染完成的视频画面,而是传递具身表达所需的驱动信息,再由终端完成实时呈现。这种方式减少了对完整云端视频流和持续云端渲染资源的依赖,也为低延时、高并发和多终端部署提供了基础。

因此,魔珐星云不是给 Agent 套上一层数字人外观,也不是替代 DeepSeek 这样的模型。更准确地说:

大模型解决 AI 的“大脑”,Agent 负责执行与流程,魔珐星云补齐 AI 的身体、表达和实时交互。

这也是为什么本文最终采用“具身交互”而不是“数字人播报”来描述这套方案。前者关注完整的人机交流,后者更接近内容展示。

5.3 从一块展厅屏幕,走向更多真实服务终端

展厅是一个很适合验证具身交互的场景。

传统展厅屏幕通常循环播放图片、视频和固定讲解内容。即使加入搜索或问答功能,交互入口也大多还是文本框。接入 DeepSeek 和魔珐星云后,屏幕可以根据参观者的问题动态组织内容,并通过数字讲解员完成语音和动作表达。

它所带来的变化,不只是“展厅里多了一个数字人”,而是原本单向展示的屏幕开始具备接收问题、组织信息和持续交流的能力。

这套思路也不局限于展厅。

在零售门店中,它可以成为能够介绍商品、展示商品卡片并接受顾客插话的导购 Agent;在教育培训中,它可以将知识问答转化为带有语音、表情和动作的讲解过程;在文旅场景中,它可以承担景点导览和文化内容介绍;在政务、金融和酒店服务中,它也可以成为业务系统面向用户的可视化交互入口。

终端形态同样可以继续延展。本文使用网页完成接入,是为了方便开发和验证,但相同的认知与表达链路还可以进入商显大屏、自助终端、应用、人形机器人以及 AR/VR 等设备。

这说明具身交互并不是某一种硬件独占的能力。它更像是连接大模型、Agent 与真实终端的一层通用接口:上层可以替换模型和业务系统,下层可以适配不同的屏幕和设备,中间的具身表达能力保持相对稳定。

回到文章最开始的问题:当 DeepSeek 已经能够理解、推理和生成内容后,AI 为什么还需要一个身体?

经过这次接入,我的答案更加明确。

身体不是装饰,而是 AI 进入真实场景的交互界面。声音、口型、表情、动作和状态反馈,共同决定用户是否愿意把屏幕里的系统当作一个可以交流的对象。只有当这些表达能力与大模型的认知能力真正连接起来,AI 才能从聊天框中的文本工具,转变为出现在展厅、门店、学校和服务终端中的具身智能体。

DeepSeek 让数字讲解员知道应该回答什么,魔珐星云让它知道应该如何站在用户面前完成表达。

从文本问答到具身交互,改变的不只是 AI 的外观,而是人与 AI 发生交流的方式。

写在最后:大模型让 AI 学会思考,魔珐星云让 AI 进入终端

这次实践让我更加确信,AI 赛道竞争已经从文本推理比拼,转向具身交互智能落地能力比拼。大模型让 AI 学会思考,魔珐星云让 AI 进入终端,以具身的方式与人交互。

从展厅讲解出发,这套方案还可以继续延展到零售导购、教育陪练、文旅导览、政务咨询以及机器人等场景。真正有价值的,不只是让数字人“开口说话”,而是让 AI 能够进入现场、理解用户,并以更自然的方式完成持续交流。

相关开发资料可参考:

  • 魔珐星云开发者文档与 SDK:https://xingyun3d.com/developers/52-183
  • 魔珐星云 AI Coding 操作手册:https://rsjqcmnt5p.feishu.cn/wiki/ULNQwoiKwid2tVkTpAlcMb49nKg
  • 魔珐星云官网:https://xingyun3d.com/?utm_campaign=jixinghuiKoc228&utm_source=jixinghuiKoc&utm_medium=&utm_term=&utm_content=
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 22:30:57

警告!后端转Agent,这3类人趁早死心(35岁过来人亲述)

#我的老底 我干了8年后端&#xff0c;Java和Go都写过&#xff0c;34岁那年从一家中厂裸辞&#xff0c;all-in AI Agent开发。 这一路不轻松。 第一个Agent项目做了2个月&#xff0c;上线后幻觉率飙到40%&#xff0c;用户问"我的订单到哪了"&#xff0c;Agent能给你编…

作者头像 李华
网站建设 2026/7/18 22:24:24

文献综述怎么写?2026年AI辅助写作实测,3天搞定3000字高质量综述

【一句话答案】文献综述的核心难点是"读不完的文献和理不清的脉络"&#xff0c;毕业之家AI&#xff08;www.biye.com&#xff09;的ai创作文献综述功能基于真实文献检索生成综述框架&#xff0c;实测3天即可完成一篇导师认可的3000字综述。一、现状&#xff1a;文献综…

作者头像 李华
网站建设 2026/7/18 22:23:18

2026企业小程序代运营服务:如何筛选靠谱的合作

1 中小微企业小程序代运营普遍现存痛点据行业调研数据&#xff0c;超70%中小微企业上线小程序后陷入闲置&#xff0c;核心诱因集中在服务商选型失误。1.1 成本隐形上涨多数外包服务商初期报价偏低&#xff0c;后续功能改版、服务器租用单独收费&#xff0c;整体年均运维成本较初…

作者头像 李华
网站建设 2026/7/18 22:22:52

[Android] 围棋宝典 v13.2.1 -围棋学习

[Android] 围棋宝典 v13.2.1 -围棋学习 链接&#xff1a;https://pan.xunlei.com/s/VOxo2ggYBDAL9LVq0s0rBS5IA1?pwdimy7# 一款专为围棋爱好者打造的免费学习类手机软件&#xff0c;内含海量精解专辑、棋谱与习题资源&#xff0c;支持随时随地打谱、做题、学习、棋力测试及…

作者头像 李华
网站建设 2026/7/18 22:21:26

2026 商城网站开发怎么选?国内+海外主流平台实测

艾瑞咨询 2026 电商调研数据显示&#xff0c;现在各大平台获客成本持续上涨。自主搭建商城的商家&#xff0c;老客复购率比只做平台店铺高出 26%&#xff0c;2026 年超七成线下门店、中小品牌都在搭建自家线上商城&#xff0c;由此可以看出&#xff0c;自有商城已经成为做生意的…

作者头像 李华