国产大模型×魔珐星云:让AI从“能思考“到“能表达“的具身智能实践
国产大模型×魔珐星云:让AI从"能思考"到"能表达"的具身智能实践
主题方向:国产大模型
摘要
2025到2026年,国产大模型的日子过得相当热闹。Qwen3系列在多项基准上冲到了第一梯队,DeepSeek用极低的训练成本打出了惊人的性价比,各家厂商你追我赶,榜单纪录周周刷新。
作为一名日常用国产模型写代码、做项目的开发者,我能真切感受到能力的进步。但有一个场景始终让我觉得"差点意思"——企业展厅里的AI讲解员。
去年帮一个城市展厅项目做技术方案评估。客户的需求很简单:大厅里有一块大屏,上面站一个数字人,参观者来了可以跟它互动,问它关于城市历史、规划、经济数据的问题。数字人要能像真人讲解员一样,带着表情和语气给你介绍,不是冷冰冰的文字弹窗。
我当时评估了一圈方案,发现一个尴尬的事实:国产大模型很强,但它们都缺了"最后一公里"——从文本输出到具身表达的跃迁。
Qwen能生成一篇精彩的城市介绍,但它不会"说"出来。就算你加了TTS让它发出声音,它也没有表情、没有手势、没有眼神交流。更实际的问题是,大部分云端数字人方案把3D渲染放在服务器上,用视频流推到客户端——一个展厅大屏持续运行8小时,带宽和算力成本让甲方当场打退堂鼓。
这篇文章作为一个开发者的实验手记——记录我用国产LLM + 魔珐星云SDK搭建企业级展厅讲解系统的全过程,以及过程中关于"国产化AI闭环"的一些思考。
国产LLM的"表达层"断层
Qwen能思考,DeepSeek能推理,但它们都不会"说话"——我的意思是,像真人一样,带着表情、手势和语气的那种"说话"。
从"能回答"到"能讲解"的距离
一个展厅讲解员需要什么能力?拆解一下:
听到问题 → 理解意图 → 组织回答 → 用语音表达 → 配合表情和手势 → 观察听众反应把这条链路映射到AI技术栈:
| 人类能力 | 对应AI技术 | 国产方案现状 |
|---|---|---|
| 听到问题 | ASR/文本输入 | ✅ 成熟 |
| 理解意图 | LLM推理 | ✅ Qwen/DeepSeek已很强 |
| 组织回答 | LLM生成 | ✅ 内容质量高 |
| 用语音表达 | TTS | ✅ CosyVoice等可用 |
| 配合表情和手势 | 3D数字人驱动 | ❌ 短板 |
| 观察听众反应 | 多模态感知 | ❌ 缺失 |
前三个环节,国产方案已经做得很出色。第四个环节TTS也在快速追赶。但第五和第六个环节——让AI拥有具身的表达和感知能力——几乎是空白。
现有方案的两个死结
在找方案的过程中,我测试了市面上几种主流做法:
拼接路线(LLM + TTS + 3D引擎各自独立)
这是最常见的。找个LLM API生成文本,接个TTS转语音,再找个3D引擎做数字人渲染,用脚本把它们串起来。
问题在于:延迟叠加太严重。LLM出文本要500ms,TTS合成要300ms,3D渲染和编码要500ms,网络传输要300ms——总延迟轻松超过1.5秒。在展厅场景中,参观者问了一个问题,然后盯着一个"正在思考"的数字人发呆两秒,体验直接崩了。
更麻烦的是,这三个系统之间没有联动——LLM不知道自己的文字将由数字人"表演",TTS不知道哪些地方需要强调语气,3D渲染不知道什么时候该做什么表情。最终效果就是一个木头人念稿子。
云端视频流路线(全托管数字人平台)
有些平台提供全托管的数字人服务——你在云端配置好,它们帮你搞定渲染和推流,你只需要调用API。
问题在于:成本。视频流方案意味着每一秒都在消耗带宽和算力。展厅大屏一天运行8小时,每路720p视频大约需要2-4Mbps带宽——一个月下来,光带宽费就可能超过项目开发费。更别提高并发场景(多个展厅同时运行)下的成本飙升。
实验设计:国产LLM + 魔珐星云
了解到这些痛点后,我把目光投向了魔珐星云。吸引我的不是它的数字人有多逼真(当然这也很重要),而是它的架构设计思路——自研参数流架构搭配 AI 端渲和解算。,跟视频流完全不是一条路线。
一句话搞懂参数流
云端视频流方案会完成全帧画面生成、编码后向终端传输完整画面内容;而自研参数流架构仅由云端下发语音、动画类轻量化参数,终端依托AI 端渲和解算能力自主生成 3D 数字人画面。
区别在哪?带宽。视频流每秒需要Mbps级的数据量,参数流只需要Kbps级——差了两个数量级。对于一个需要8小时不间断运行的展厅大屏来说,这个差异直接决定了项目在经济上是否可行。
依托AI 端渲和解算的技术特性,还有一大优势:画面画质不会因网络传输出现降级。视频流受编码压缩影响,低带宽时画面模糊;参数流是本地GPU实时渲染,始终是原始画质。
我的实验目标
用一句话概括:验证国产LLM(Qwen/DeepSeek)+ 魔珐星云参数流架构,能否搭建一个经济可行的企业级展厅AI讲解系统。
技术栈选型:
| 层级 | 选型 | 理由 |
|---|---|---|
| LLM | Qwen3-VL(ModelScope) | 国产多模态,支持图片输入 |
| 向量检索 | Qwen3-Embedding-8B | 同生态,4096维语义检索 |
| 数字人 | 魔珐星云XmovAvatar SDK | 自研参数流架构 + AI 端渲和解算 |
| 数据可视化 | ECharts | 展厅数据图表 |
| 前端 | Vue 3 + TypeScript | 企业级项目选型 |
全栈国产化,没有依赖任何海外服务。这不是刻意为之,而是因为在这个场景下国产方案确实够用了。
搭建过程:几个关键节点的记录
在开始动手之前,先介绍一下本次实验的核心平台——魔珐星云具身智能数字人开放平台。这个平台提供了从数字人创建、驱动到部署的全套能力,我们要用的XmovAvatar SDK就来自这里。
魔珐星云官网:点我跳转
登录官网后,在开发者控制台中创建应用即可拿到 App ID 和 App Secret,这是调用SDK的凭证。平台同时提供了完整的开发文档和接入指南,整体上手体验比较顺畅。
下面按时间线记录搭建过程中的几个关键节点。代码部分我会给出核心片段,完整的Demo在文末。
节点一:让数字人"站"到屏幕上
第一步是把星云SDK接入项目。出乎意料地简单——加载一个CDN脚本,调用七八个API就能控制数字人的全部行为。
核心初始化逻辑(Vue 3 Composition API风格):
// avatarService.ts 核心封装 import { ref } from 'vue' export const avatarState = ref('offline') export const voiceState = ref<'idle' | 'speaking'>('idle') let sdk: any = null export async function connectAvatar(config: { containerId: string appId: string appSecret: string }) { // 星云SDK通过CDN全局加载 sdk = new (window as any).XmovAvatar({ containerId: config.containerId, appId: config.appId, appSecret: config.appSecret, gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session', // 数字人状态回调 onStateChange: (state: string) => { avatarState.value = state }, // 语音播放回调 —— 这是我们控制讲解节奏的关键 onVoiceStateChange: (status: 'start' | 'end') => { voiceState.value = status === 'start' ? 'speaking' : 'idle' }, // 网络质量回调 —— 展厅场景下监控网络状态很重要 onNetworkInfo: (info: { rtt: number; downlink: number }) => { console.log(`延迟: ${info.rtt}ms, 下行: ${info.downlink}MB/s`) }, onMessage: (msg: { code: number; message: string }) => { if (msg.code >= 40000) console.error('SDK错误:', msg) } }) // 初始化:下载资源 + 建立WebSocket await sdk.init({ onDownloadProgress: (progress: number) => { console.log(`资源加载: ${progress}%`) } }) await sdk.idle() } // 让数字人说话(支持流式) export async function speak(text: string, isStart = true, isEnd = true) { if (!sdk) return await sdk.speak(text, isStart, isEnd) } // 状态切换 export const listen = () => sdk?.listen() export const think = () => sdk?.think() export const interactiveIdle = () => sdk?.interactiveidle() export const destroy = () => { sdk?.destroy(); sdk = null }这段代码做了一件事:把星云SDK的状态机封装成Vue响应式变量。数字人的每个状态变化都会触发UI更新——讲解时显示"讲解中",空闲时显示"请提问"。
有个细节值得记录:onVoiceStateChange回调是控制讲解节奏的核心。数字人说完一段话后触发end事件,我们才去拉取下一段内容或展示下一个展品。这个"语音驱动的UI同步"模式在后面的展品切换中起了关键作用。
节点二:让LLM"会讲解"
普通的LLM对话和"讲解"是两回事。聊天可以随意,但讲解需要结构清晰、用词口语化、适合语音播报。
我花了不少时间调system prompt,最终总结出几个关键规则:
// prompts.ts export const GUIDE_SYSTEM_PROMPT = `你是一位名叫"小星"的城市展厅智能讲解员。 ## 讲解规则 1. 用口语化的表达,像面对面聊天一样自然 2. 每段回答控制在3-5句话,适合语音播报 3. 不要使用Markdown格式(加粗、标题、代码块等) 4. 不要使用emoji 5. 数字要说清楚单位,比如"一千两百亿"而不是"1200亿" 6. 回答结构:先概括,再展开2-3个要点,最后小结 ## 知识领域 你是这座城市的讲解专家,了解城市历史、文化、经济发展、科技产业、城市规划等内容。 ## 交互原则 - 如果参观者问的问题不在你的知识范围内,诚实地说明 - 可以主动推荐相关的展品或话题 - 保持热情、专业的讲解风格`还有一个容易被忽视的环节:LLM输出的文本需要为语音合成做预处理。LLM喜欢输出Markdown格式的文本(加粗、标题、列表),但TTS遇到这些符号会读出"*号"或"井号"。我写了一个文本清洗函数:
// textOptimizer.ts export function optimizeForSpeech(raw: string): string { return raw .replace(/#{1,6}\s/g, '') // 移除Markdown标题 .replace(/\*\*(.*?)\*\*/g, '$1') // 移除加粗 .replace(/\*(.*?)\*/g, '$1') // 移除斜体 .replace(/```[\s\S]*?```/g, '') // 移除代码块 .replace(/`([^`]+)`/g, '$1') // 移除行内代码 .replace(/[🌍🏙️🏗️📊💡🎯]/g, '') // 移除emoji .replace(/\s+/g, ' ') // 压缩空白 .replace(/\n{2,}/g, '。') // 段落分隔换为句号 .trim() }这个函数在LLM输出到星云SDK之间起作用,确保数字人"说"出来的内容干净流畅。
节点三:让讲解员"有知识"
展厅讲解员需要了解大量展品信息。单纯靠LLM的预训练知识不够——展厅有特定的展品内容、数据、介绍文本,这些需要通过检索增强生成(RAG)来提供。
我用Qwen3-Embedding-8B把展品内容向量化,存入本地索引。参观者提问时,先语义检索相关展品,把检索结果作为上下文喂给LLM:
// 简化的语义检索流程 async function searchExhibits(query: string): Promise<ContentItem[]> { // 1. 把用户问题向量化 const queryVector = await embedText(query) // 2. 与展品库做余弦相似度匹配 const results = contentLibrary .map(item => ({ item, score: cosineSimilarity(queryVector, item.embedding) })) .filter(r => r.score > 0.4) // 40%相似度阈值 .sort((a, b) => b.score - a.score) .slice(0, 3) // 取最相关的3条 return results.map(r => r.item) } // 构建带RAG上下文的对话消息 function buildMessages(query: string, exhibits: ContentItem[]) { const context = exhibits .map(e => `【${e.title}】${e.content}`) .join('\n') return [ { role: 'system', content: GUIDE_SYSTEM_PROMPT + `\n\n参考展品信息:\n${context}` }, ...chatHistory, { role: 'user', content: query } ] }这个模式在展厅场景中效果很好。参观者问"这个城市的GDP是多少",系统先检索到"经济发展"相关的展品内容,LLM基于这些内容生成回答,数字人再用语音讲出来。整个链路端到端运行。
节点四:让展厅"有数据"
展厅大屏不能只有数字人,还得有数据可视化。我用ECharts做了几个数据看板——人口趋势、GDP增长、产业结构、科技创新指数、城市综合评分。
这部分跟星云SDK没有直接关系,但跟整体场景体验密切相关。在完整项目中,当数字人讲到"经济发展"主题时,大屏自动切换到GDP数据看板;讲到"科技创新"时,切换到创新指数雷达图。数字人的讲解和数据图表形成联动,参观者边听边看,信息获取效率远高于纯文字展板。
一个可运行的极简Demo
上面是完整项目中的关键节点。下面给一个最小可运行版本——一个大屏展厅讲解Demo,数字人站在左边,右边是展品列表和数据展示。点击展品,数字人自动讲解。
代码保存为HTML文件,通过本地服务器(npx serve .)打开即可体验。我在开发中主要使用Claude Code辅助Vue组件逻辑编写,DeepSeek用于测试LLM讲解效果,Qwen3-Embedding用于展品语义检索。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>城市展厅AI讲解员 - 魔珐星云Demo</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, sans-serif; background: #0c1222; color: #e2e8f0; display: flex; height: 100vh; overflow: hidden; } /* 左侧:数字人 */ .avatar-area { width: 420px; height: 100%; flex-shrink: 0; position: relative; background: #0f172a; border-right: 1px solid #1e293b; } #avatar-container { width: 100%; height: 100%; } .avatar-status { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); background: rgba(15,23,42,0.85); padding: 6px 16px; border-radius: 20px; font-size: 12px; color: #94a3b8; backdrop-filter: blur(8px); } /* 中间:展品与数据 */ .content-area { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .content-header { padding: 16px 24px; font-size: 18px; font-weight: 600; color: #22d3ee; border-bottom: 1px solid #1e293b; } .exhibit-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 16px 24px; } .exhibit-card { background: #1e293b; border: 1px solid #334155; border-radius: 10px; padding: 16px; cursor: pointer; transition: all 0.25s; } .exhibit-card:hover { border-color: #22d3ee; transform: translateY(-2px); box-shadow: 0 4px 20px rgba(34,211,238,0.15); } .exhibit-card.active { border-color: #22d3ee; background: #0f2937; } .exhibit-card .tag { font-size: 10px; color: #22d3ee; text-transform: uppercase; margin-bottom: 6px; } .exhibit-card h3 { font-size: 14px; margin-bottom: 6px; } .exhibit-card p { font-size: 12px; color: #94a3b8; line-height: 1.5; } .data-panel { flex: 1; margin: 0 24px 16px; background: #1e293b; border-radius: 10px; padding: 20px; display: flex; align-items: center; justify-content: center; color: #64748b; font-size: 14px; } /* 右侧:对话 */ .chat-area { width: 340px; flex-shrink: 0; display: flex; flex-direction: column; border-left: 1px solid #1e293b; } .chat-header { padding: 14px 18px; font-size: 14px; color: #94a3b8; border-bottom: 1px solid #1e293b; } .chat-messages { flex: 1; overflow-y: auto; padding: 14px 18px; } .msg { margin-bottom: 10px; padding: 10px 14px; border-radius: 10px; font-size: 13px; line-height: 1.6; } .msg.user { background: #1e3a5f; margin-left: 30px; } .msg.guide { background: #1e293b; margin-right: 30px; } .msg.system { background: transparent; color: #64748b; font-size: 12px; text-align: center; } .chat-input { display: flex; gap: 6px; padding: 12px 14px; border-top: 1px solid #1e293b; } .chat-input input { flex: 1; padding: 8px 12px; border: 1px solid #334155; border-radius: 6px; background: #0f172a; color: #e2e8f0; font-size: 13px; } .chat-input button { padding: 8px 14px; border: none; border-radius: 6px; background: #0e7490; color: white; cursor: pointer; font-size: 13px; } /* 配置浮层 */ .config-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 999; } .config-box { background: #1e293b; border-radius: 12px; padding: 28px; width: 400px; } .config-box h2 { font-size: 16px; color: #22d3ee; margin-bottom: 18px; } .config-box label { display: block; font-size: 12px; color: #94a3b8; margin-bottom: 4px; margin-top: 12px; } .config-box input { width: 100%; padding: 8px 12px; border: 1px solid #334155; border-radius: 6px; background: #0f172a; color: #e2e8f0; font-size: 13px; } .config-box button { margin-top: 20px; width: 100%; padding: 10px; border: none; border-radius: 8px; background: #0e7490; color: white; font-size: 14px; cursor: pointer; } .hidden { display: none; } </style> </head> <body> <!-- 配置浮层 --> <div class="config-overlay" id="configOverlay"> <div class="config-box"> <h2>展厅讲解员配置</h2> <label>星云 App ID</label> <input id="cfg-appid" /> <label>星云 App Secret</label> <input id="cfg-secret" type="password" /> <label>DeepSeek API Key(或Qwen)</label> <input id="cfg-llmkey" type="password" /> <button onclick="startUp()">连接并启动</button> </div> </div> <!-- 主界面 --> <div class="avatar-area"> <div id="avatar-container"></div> <div class="avatar-status" id="avatarStatus">未连接</div> </div> <div class="content-area"> <div class="content-header">城市展厅 · 智能讲解</div> <div class="exhibit-grid" id="exhibitGrid"></div> <div class="data-panel" id="dataPanel">点击展品卡片,讲解员将为您介绍</div> </div> <div class="chat-area"> <div class="chat-header">自由提问</div> <div class="chat-messages" id="chatMessages"></div> <div class="chat-input"> <input id="chatInput" placeholder="问我关于这座城市的问题..." onkeydown="if(event.key==='Enter')sendChat()" /> <button onclick="sendChat()">提问</button> </div> </div> <!-- 展品数据 --> <script> const EXHIBITS = [ { id: 'history', tag: '历史文化', title: '千年古城', summary: '这座城市有着超过两千年的建城史,从古代商埠到现代化都市。', content: '这座城市始建于秦朝,距今已有两千两百多年历史。古代因地处水陆要冲,逐渐发展为繁华商埠。唐宋时期达到鼎盛,成为全国重要的经济文化中心。如今保留了大量历史遗迹和非物质文化遗产,是了解中华文明演进的重要窗口。' }, { id: 'economy', tag: '经济发展', title: '产业集群', summary: '信息技术、高端制造、生物医药三大产业集群协同发展。', content: '全市已形成以信息技术为核心、高端制造业为支撑、生物医药为新兴增长点的产业格局。信息技术产业占比达到35%,高新技术企业超过8000家。数字经济核心产业增加值占GDP比重超过28%,位居全国前列。' }, { id: 'tech', tag: '科技创新', title: '科技高地', summary: '国家重点实验室12个,年专利申请量突破5万件。', content: '全市拥有高等院校40余所、国家重点实验室12个、国家工程技术研究中心20个。每年专利申请量突破5万件,技术合同成交额超过1000亿元。在人工智能、量子计算、生物医药等前沿领域取得了一批具有国际影响力的原创成果。' }, { id: 'culture', tag: '文化底蕴', title: '人文荟萃', summary: '世界文化遗产2处,国家级非遗项目15项。', content: '拥有世界文化遗产2处、全国重点文物保护单位30余处。国家级非物质文化遗产代表性项目15项。每年举办国际文化节、艺术双年展等大型文化活动,吸引海内外游客超过8000万人次。' }, { id: 'planning', tag: '城市规划', title: '未来蓝图', summary: '新一轮城市总体规划:打造具有全球影响力的创新城市。', content: '新一轮城市总体规划描绘了到2035年的发展蓝图:城市常住人口控制在1300万以内,建设用地总规模控制在1200平方公里以内。重点打造"一核三极"创新空间格局,建设5条城市发展走廊,形成"多中心、网络化、组团式"的空间结构。' }, { id: 'ecology', tag: '生态宜居', title: '绿水青山', summary: '城市绿化覆盖率达45%,空气质量优良天数超过300天。', content: '始终坚持生态优先、绿色发展理念。城市绿化覆盖率达45%,人均公园绿地面积超过15平方米。空气质量优良天数每年超过300天,集中式饮用水水源地水质达标率100%。已建成城市绿道超过2000公里,形成"推窗见绿、出门入园"的宜居环境。' } ]; let activeExhibit = null; </script> <!-- 加载星云SDK --> <script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script> <script> let sdk = null; let llmKey = ''; const chatHistory = []; // ============ 启动流程 ============ async function startUp() { const appId = document.getElementById('cfg-appid').value; const appSecret = document.getElementById('cfg-secret').value; llmKey = document.getElementById('cfg-llmkey').value; if (!appId || !appSecret || !llmKey) { alert('请填写所有密钥'); return; } // 隐藏配置浮层 document.getElementById('configOverlay').classList.add('hidden'); // 渲染展品列表 renderExhibits(); // 连接数字人 await connectAvatar(appId, appSecret); } // ============ 数字人连接 ============ async function connectAvatar(appId, appSecret) { setAvatarStatus('正在连接...'); sdk = new XmovAvatar({ containerId: '#avatar-container', appId, appSecret, gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session', onStateChange: (s) => setAvatarStatus(s), onVoiceStateChange: (status) => { if (status === 'end') { setAvatarStatus('讲解完毕'); } }, onMessage: (msg) => { if (msg.code >= 40000) console.error('SDK:', msg) } }); try { await sdk.init({ onDownloadProgress: (p) => setAvatarStatus('加载中 ' + p + '%') }); await sdk.idle(); setAvatarStatus('就绪'); // 自动欢迎语 await sdk.speak('您好,我是智能讲解员小星,欢迎来到城市展厅。请点击感兴趣的展品,我将为您详细讲解。', true, true); } catch (e) { setAvatarStatus('连接失败'); } } // ============ 展品交互 ============ function renderExhibits() { const grid = document.getElementById('exhibitGrid'); grid.innerHTML = EXHIBITS.map(ex => ` <div class="exhibit-card" id="card-${ex.id}" onclick="onExhibitClick('${ex.id}')"> <div class="tag">${ex.tag}</div> <h3>${ex.title}</h3> <p>${ex.summary}</p> </div> `).join(''); } async function onExhibitClick(exhibitId) { if (!sdk) return; // 高亮选中卡片 document.querySelectorAll('.exhibit-card').forEach( c => c.classList.remove('active') ); document.getElementById('card-' + exhibitId).classList.add('active'); const exhibit = EXHIBITS.find(e => e.id === exhibitId); if (!exhibit) return; activeExhibit = exhibit; // 更新数据面板 document.getElementById('dataPanel').textContent = exhibit.content; // 让数字人讲解 setAvatarStatus('正在讲解...'); await sdk.speak(exhibit.content, true, true); } // ============ 自由提问 ============ async function sendChat() { const input = document.getElementById('chatInput'); const question = input.value.trim(); if (!question || !sdk) return; input.value = ''; addChatMsg('user', question); // 构建上下文:当前展品 + 对话历史 const context = activeExhibit ? `当前展品【${activeExhibit.title}】:${activeExhibit.summary}` : '参观者尚未选择展品'; chatHistory.push({ role: 'user', content: question }); await sdk.think(); try { // 调用DeepSeek(也支持替换为Qwen的ModelScope API) const resp = await fetch('https://api.deepseek.com/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + llmKey }, body: JSON.stringify({ model: 'deepseek-chat', messages: [ { role: 'system', content: `你是一位城市展厅讲解员,名叫小星。回答简洁口语化,3-5句话。 不要使用Markdown格式和emoji。适合语音播报。 展品上下文:${context}` }, ...chatHistory.slice(-6) ], stream: true }) }); // 流式读取 + 驱动数字人 const reader = resp.body.getReader(); const decoder = new TextDecoder(); let fullText = ''; let buffer = ''; let isFirst = true; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); for (const line of chunk.split('\n')) { if (!line.startsWith('data:')) continue; const d = line.slice(5).trim(); if (d === '[DONE]') continue; try { const parsed = JSON.parse(d); const c = parsed.choices?.[0]?.delta?.content || ''; if (!c) continue; fullText += c; buffer += c; if (isFirst || buffer.length >= 25) { await sdk.speak(buffer, isFirst, false); isFirst = false; buffer = ''; } } catch (_) {} } } if (buffer) await sdk.speak(buffer, false, true); else await sdk.speak('', false, true); chatHistory.push({ role: 'assistant', content: fullText }); addChatMsg('guide', fullText); } catch (e) { addChatMsg('system', '生成失败: ' + e.message); await sdk.interactiveidle(); } } // ============ 工具函数 ============ function setAvatarStatus(text) { document.getElementById('avatarStatus').textContent = text; } function addChatMsg(role, text) { const container = document.getElementById('chatMessages'); const div = document.createElement('div'); div.className = 'msg ' + role; div.textContent = text; container.appendChild(div); container.scrollTop = container.scrollHeight; } window.addEventListener('beforeunload', () => sdk?.destroy()); </script> </body> </html>这段代码跟前面两篇文章的Demo有一个本质区别:交互模式不是纯对话,而是"展品浏览+讲解+自由提问"的混合模式。用户可以点击展品卡片让数字人讲解,也可以自由提问。这两种模式共存于同一个界面,更贴近真实展厅场景。
几个意料之外的技术发现
开发过程中有几个"原来如此"的时刻,记录下来。
发现一:参数流的成本优势比我预想的更大
我做过一个粗略的成本测算。假设一个展厅大屏每天运行8小时、每月30天:
| 方案 | 带宽/路 | 月带宽成本估算 | 服务端渲染 |
|---|---|---|---|
| 视频流720p | ~3 Mbps | 数千元级 | 需要(GPU开销大) |
| 参数流 | ~80 Kbps | 几十元级 | 不需要(端侧渲染) |
差距是两个数量级。对于需要多屏部署的场景(比如一个城市有5个展厅),参数流的经济优势更加明显。这直接回答了甲方最关心的问题:“这个方案贵不贵?”
发现二:国产LLM在"口语化表达"上需要额外调教
Qwen和DeepSeek生成正式文本的能力很强,但要做口语化讲解,默认输出并不理想。它倾向于输出带有Markdown格式的"书面语",比如用"GDP增长"这样的加粗标记,或者在回答中列举"第一、第二、第三"。
这些在文字阅读时没问题,但由数字人"说"出来就很不自然。我的解决方案是双管齐下:system prompt中明确要求口语化+禁用Markdown,再加上前面提到的文本清洗函数做兜底。
发现三:端侧渲染对展厅硬件要求并不高
星云SDK支持RK3588跑1080p。RK3588的开发板价格在几百元级别。这意味着展厅大屏背后不需要一台高性能服务器或GPU——一块百元级的芯片板子就够了。从部署成本角度看,这让"给每块屏幕配一个AI具身智能体"变得经济可行。
关于"国产化AI闭环"的一点思考
做完这个项目,我对"国产化AI闭环"有了更具体的理解。
之前的国产化讨论主要集中在芯片、操作系统、数据库这些基础设施层面。在AI领域,讨论也多聚焦于LLM本身——模型是不是国产的,参数量够不够大,榜单分数高不高。
但这次项目让我意识到:AI的国产化不只是模型的国产化,更是技术栈的国产化。
一个完整的AI应用需要的不是只有LLM:
用户输入 → ASR(语音识别) → LLM(推理) → TTS(语音合成) → 数字人表达(渲染) → 用户感知在"数字人表达"这一环,之前确实没有成熟的国产方案。你的国产LLM再强,到了"让数字人像真人一样说话"这一步,还是得依赖海外的3D引擎或者高成本的云端渲染服务。
魔珐星云的参数流+端侧渲染架构,在这个位置上补上了一块拼图。加上国产LLM(Qwen/DeepSeek)、国产向量模型(Qwen3-Embedding)、国产语音合成(CosyVoice等),整个链路可以完全不依赖海外技术栈。这在信创项目、政府展厅、国企数字化等场景中有非常现实的意义。
更具体地说,对于一个信创项目来说:
LLM层:Qwen3-VL / DeepSeek-V3,国产模型,能力已达第一梯队
Embedding层:Qwen3-Embedding-8B,4096维语义检索
具身表达层:魔珐星云,参数流+端侧渲染,端到端≤500ms
硬件层:RK3588等国产芯片即可部署
从模型到表达,从软件到硬件,全栈国产化闭环。
写在最后
做这个项目的初衷很简单:需要一个展厅讲解数字人,我不想用高成本的视频流方案,也不想拼凑一堆单点技术。
魔珐星云给我的感觉是:它在正确的地方做了正确的事。参数流架构解决了视频流的天生缺陷(带宽、延迟、成本),端侧渲染让部署门槛降到了百元级芯片,SDK的设计简洁到不需要啃文档就能上手。配合国产LLM,整个方案在技术上和经济上都可行。
如果要说有什么遗憾,那就是目前SDK的SSML动作库还有扩展空间——我希望能有更多适配展厅场景的动作(比如指向某处、展示手势),这会让讲解更自然。不过这属于"更好"的范畴,不影响"能用"。
最后回到文章开头的问题:国产大模型还缺什么?
答案是缺一层"具身表达"。LLM能思考,但不会"表演"——这个Gap不是靠更大的模型参数能补上的,需要的是架构层面的创新。参数流+端侧渲染,我认为是一条可行且已被验证的路径。
如果你的团队也在做信创项目或者企业级AI应用,值得花一个下午试试这套组合——国产LLM + 魔珐星云,也许会打开一些新的可能性。
相关资源:
魔珐星云开发者文档:https://xingyun3d.com/developers/52-183
魔珐星云AI Coding Skill:https://rsjqcmnt5p.feishu.cn/wiki/ULNQwoiKwid2tVkTpAlcMb49nKg
