给个人知识库装了一个会说话的数字人前台
2026/8/25 5:06:20 网站建设 项目流程

一、你有没有遇到过这种情况?

上周我在找一个半年前记的笔记——关于微服务拆分的几个要点,当时写在 Notion 里了。打开搜索,翻了七八个页面,最后在一篇会议纪要的角落找到了。

说实话,挺崩溃的。

我电脑里攒了三年多的笔记、文档、书签、PDF 论文。每次想找一个东西,不是在 Finder 里搜文件名,就是在各种笔记软件里翻目录。更难受的是,这些东西之间明明有联系——那篇论文的观点刚好能佐证上周的项目决策、两个月前记的一个灵感跟现在的工作高度相关——但你就是想不起来,更别说关联起来了。

前段时间我在想:有没有可能,我把这些乱七八糟的知识"喂"给一个 AI,然后像问同事一样直接问它?

更进一步——能不能不只返回纯文本回答,而是拥有可视化的**具身交互智能体,**像真实知识顾问一样和我面对面沟通?

答案是可以。花了一个周末,我用魔珐星云 SDK + 本地 RAG 搭了一个原型。这篇文章就记录整个过程。


二、AI 的交互形态正在变,只是大多数人还没意识到

在说技术方案之前,我想先说一个趋势判断。

过去两年,大部分人对 AI 的使用停留在"聊天框思维"——打开 ChatGPT、Kimi、豆包,打字提问,看文字回答。这个模式当然有用,但它的天花板也很明显:

你获取的是信息,不是交互。

在真实场景里,人对信息的接收远不止文字。语气、表情、肢体动作——这些构成了面对面交流中 70% 以上的信息量。当一个 AI 只能用纯文本回答你时,它本质上还是一个搜索引擎,不是你的"伙伴"。

这就是"具身交互智能"要解决的问题——让 AI 从文本框里走出来,拥有身体、表情和声音,以更像"人"的方式与你交互。

大模型解决了 AI 的"思考"问题。但要让 AI 真正融入你的桌面、你的工作流、你的日常生活,它还需要一个能被看见、能说话、能实时回应的身体。


三、魔珐星云:让大模型"长"出身体

我在做这个项目之前,其实调研了一圈市面上的方案。

市面上不少传统虚拟载体产品大多属于视频生成模式:输入文案,云端渲染生成单向播放的念稿视频。少数可以做语音驱动,但延迟高达数秒,不支持中途打断。

而本项目需要的并不是单向念稿的虚拟形象,而是一套支持实时双向对话的知识顾问交互体系:支持随时插话打断、跟随上下文调整语气、多终端设备流畅运行。

后来我找到了魔珐星云

它做的事情其实很聚焦——专门补齐大模型在终端落地时缺少的"交互层"。用一句话概括:

大模型负责"大脑",Agent 负责"调度",星云负责"身体和表达"——让 AI 以 3D 具身的形态,自然地与人交互。

更具体来说,魔珐星云将多模态感知、智能体调度、依托 LAM3D 大模型 + KA 语义动作库的AI 端侧解算 + 参数流表达引擎封装为 SDK,实现一站式接入。开发者不用单独处理语音合成、口型对齐、表情手势同步等复杂底层模块,只需要关注大模型输出业务文本内容;星云负责把文本转化成语音、表情、语义手势协同的整套多模态交互输出。

这个定位在当下很关键。因为大模型的能力已经到了一个临界点——模型够聪明了,但"怎么让人舒服地跟它交互"这个问题,模型自己解决不了。


四、动手:搭建一个个人知识库数字人顾问

好了,理论聊完,下面进入正题——我具体做了什么,以及怎么做。

4.1 整体思路

架构其实不复杂:

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 知识库文档 │ → │ RAG 检索引擎 │ → │ 豆包 │ │ (本地文件) │ │ (向量搜索) │ │ 大模型生成 │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ┌──────────────┐ │ │ 魔珐星云 SDK │ ←──────────┘ │ 3D 数字人 │ │ 语音+表情+动作 │ └──────────────┘

流程

  1. 提前把你的笔记、文档做向量化处理,存入本地向量数据库

  2. 用户提问时,先从向量库检索最相关的内容片段

  3. 把检索结果作为上下文,连同用户问题一起发给 DeepSeek 大模型

  4. 大模型生成的回答,通过星云 SDK 驱动 3D 数字人"说"出来

4.2 准备工作

要用到的工具和凭证

组件用途获取方式
魔珐星云 SDK3D 数字人驱动登录 xingyun3d.com → 应用中心创建应用,拿到appIdappSecret
DeepSeek API大模型回答生成DeepSeek 开放平台申请 API Key
ChromaDB本地向量数据库pip install chromadb即可
text2vec中文文本向量化pip install text2vec-base-chinese,离线可用
Python + Node.js后端 + 前端Python 3.10+ / Node.js 20+

我的本地环境

  • 系统:macOS 15(Windows 同样适用,代码跨平台)

  • 编辑器:VS Code + Cursor

  • Python 做 RAG 后端,Node.js 做前端页面

4.3 第一步:搭建知识库 RAG 后端

先把散落各处的文档"喂"给向量数据库。我自己的知识库内容大概有:

  • Notion 导出的技术笔记(约 200 篇,Markdown 格式)

  • 项目文档和复盘记录(约 50 个 PDF)

  • 手动记录的技术文章(手动导出了 30 多篇)

第一步是把这些内容向量化:

# rag_server.py import os import chromadb from chromadb.config import Settings import hashlib # ===== 文档加载器:支持 Markdown、PDF、TXT ===== def load_documents(doc_dir): """遍历目录,加载所有支持的文档""" docs = [] for root, _, files in os.walk(doc_dir): for f in files: path = os.path.join(root, f) ext = os.path.splitext(f)[1].lower() if ext == '.md' or ext == '.txt': with open(path, 'r', encoding='utf-8') as fp: docs.append({ 'content': fp.read(), 'source': f, 'path': path, }) elif ext == '.pdf': # 使用 PyPDF2 提取文本 try: from PyPDF2 import PdfReader reader = PdfReader(path) text = '\n'.join([p.extract_text() or '' for p in reader.pages]) docs.append({ 'content': text, 'source': f, 'path': path, }) except Exception as e: print(f"⚠️ 跳过 {f}: {e}") print(f"📄 共加载 {len(docs)} 篇文档") return docs # ===== 文本切分:按语义段落切分 ===== def split_text(text, chunk_size=500, overlap=100): """简单的中文段落切分,保证每个 chunk 语义完整""" # 优先按自然段落切分 paragraphs = text.split('\n\n') chunks = [] current = '' for p in paragraphs: p = p.strip() if not p: continue if len(current) + len(p) < chunk_size: current += p + '\n' else: if current: chunks.append(current.strip()) current = p + '\n' if current: chunks.append(current.strip()) print(f"✂️ 切分为 {len(chunks)} 个文本块") return chunks

然后是向量化和存储:

# 接上面的代码... from sentence_transformers import SentenceTransformer import numpy as np # ===== 向量化引擎 ===== # text2vec-base-chinese: 中文专用模型,本地运行,不需要联网 embedder = SentenceTransformer('shibing624/text2vec-base-chinese') def embed_texts(texts): """批量向量化""" return embedder.encode(texts, normalize_embeddings=True) # ===== ChromaDB 初始化 ===== client = chromadb.PersistentClient(path="./knowledge_db") collection = client.get_or_create_collection( name="my_knowledge", metadata={"hnsw:space": "cosine"} # 余弦相似度 ) # ===== 主流程:建库 ===== def build_knowledge_base(doc_dir): docs = load_documents(doc_dir) all_chunks = [] all_metadatas = [] for doc in docs: chunks = split_text(doc['content']) all_chunks.extend(chunks) all_metadatas.extend([{ 'source': doc['source'], 'path': doc['path'], }] * len(chunks)) # 生成向量 print("🧠 正在向量化...") embeddings = embed_texts(all_chunks) # 生成唯一 ID ids = [hashlib.md5(c.encode()).hexdigest()[:16] for c in all_chunks] # 写入数据库 collection.add( ids=ids, documents=all_chunks, metadatas=all_metadatas, embeddings=embeddings.tolist(), ) print(f"✅ 知识库构建完成!共 {len(all_chunks)} 条记录") # 建库(只需要跑一次) if __name__ == '__main__': build_knowledge_base('./my_docs')

接下来是检索 API:

# rag_search.py — 提供 FastAPI 检索接口 from fastapi import FastAPI from pydantic import BaseModel import chromadb from sentence_transformers import SentenceTransformer import requests app = FastAPI() # 复用模型和数据库 embedder = SentenceTransformer('shibing624/text2vec-base-chinese') client = chromadb.PersistentClient(path="./knowledge_db") collection = client.get_collection("my_knowledge") DEEPSEEK_KEY = "sk-your-key" class QueryRequest(BaseModel): question: str top_k: int = 5 @app.post("/api/ask") async def ask(req: QueryRequest): # 1. 将用户问题向量化 q_embedding = embedder.encode( [req.question], normalize_embeddings=True ).tolist() # 2. 从知识库检索最相关内容 results = collection.query( query_embeddings=q_embedding, n_results=req.top_k, ) # 3. 拼接检索结果作为上下文 context_parts = [] for doc, meta in zip( results['documents'][0], results['metadatas'][0] ): context_parts.append( f"[来源: {meta['source']}]\n{doc}" ) context = '\n\n---\n\n'.join(context_parts) # 4. 调用 DeepSeek 大模型,生成回答 system_prompt = f"""你是一个个人知识库 AI 助手。 请根据以下知识库内容回答用户问题。如果知识库中没有相关信息,请诚实告知。 回答要口语化、简洁,100-200 字以内。 知识库内容: {context}""" response = requests.post( 'https://api.deepseek.com/v1/chat/completions', headers={ 'Authorization': f'Bearer {DEEPSEEK_KEY}', 'Content-Type': 'application/json', }, json={ 'model': 'deepseek-chat', 'messages': [ {'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': req.question}, ], 'temperature': 0.5, 'max_tokens': 400, }, ) data = response.json() answer = data['choices'][0]['message']['content'] # 5. 返回回答 + 引用来源 sources = list(set( m['source'] for m in results['metadatas'][0] )) return { 'answer': answer, 'sources': sources, } if __name__ == '__main__': import uvicorn uvicorn.run(app, host='0.0.0.0', port=8765)

4.4 第二步:接入魔珐星云数字人

RAG 后端跑起来之后,下一步就是把文字回答变成"有人对你说话"的体验。

前端部分直接复用星云的 JS SDK,核心就三件事:

① 初始化数字人

<!-- index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>智脑AI助手</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f0f0f; color: #e0e0e0; display: flex; height: 100vh; overflow: hidden; } .avatar-side { flex: 1; background: #1a1a2e; position: relative; } .avatar-side #sdk-container { width: 100%; height: 100%; } .chat-side { width: 420px; display: flex; flex-direction: column; border-left: 1px solid #2a2a3e; background: #14141f; } .chat-header { padding: 20px; border-bottom: 1px solid #2a2a3e; display: flex; justify-content: space-between; align-items: center; } .chat-header h2 { font-size: 16px; font-weight: 600; } .status-badge { font-size: 12px; padding: 4px 12px; border-radius: 20px; background: #1a3a1a; color: #4ade80; } .chat-messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; } .message { max-width: 85%; padding: 12px 16px; border-radius: 12px; font-size: 14px; line-height: 1.7; } .message.user { align-self: flex-end; background: #2563eb; color: white; border-bottom-right-radius: 4px; } .message.assistant { align-self: flex-start; background: #1e1e30; border: 1px solid #2a2a3e; border-bottom-left-radius: 4px; } .message .source-tag { display: inline-block; font-size: 11px; margin-top: 8px; padding: 2px 8px; border-radius: 4px; background: #2a2a4e; color: #94a3b8; } .chat-input-area { padding: 16px 20px; border-top: 1px solid #2a2a3e; display: flex; gap: 8px; } .chat-input-area textarea { flex: 1; background: #1a1a2e; border: 1px solid #2a2a3e; border-radius: 8px; color: #e0e0e0; padding: 10px 14px; font-size: 14px; resize: none; outline: none; font-family: inherit; } .chat-input-area textarea:focus { border-color: #2563eb; } .chat-input-area button { padding: 10px 20px; background: #2563eb; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; } .chat-input-area button:hover { background: #1d4ed8; } .chat-input-area button:disabled { background: #374151; cursor: not-allowed; } </style> </head> <body> <div class="avatar-side"> <div id="sdk-container"></div> </div> <div class="chat-side"> <div class="chat-header"> <h2>🧠 个人知识库顾问</h2> <span class="status-badge" id="status-text">初始化中</span> </div> <div class="chat-messages" id="chat-messages"></div> <div class="chat-input-area"> <textarea id="user-input" placeholder="问问你的知识库...比如'微服务拆分的关键原则'" rows="2"></textarea> <button id="send-btn">发送</button> </div> </div> <script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script> <script src="app.js"></script> </body> </html>

② 核心交互逻辑

// app.js const CONFIG = { appId: 'YOUR_APP_ID', appSecret: 'YOUR_APP_SECRET', gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session', ragApiUrl: 'http://localhost:8765/api/ask', }; let avatar = null; let isReady = false; let isSpeaking = false; // ===== 初始化数字人 ===== avatar = new XmovAvatar({ containerId: '#sdk-container', appId: CONFIG.appId, appSecret: CONFIG.appSecret, gatewayServer: CONFIG.gatewayServer, hardwareAcceleration: 'prefer-hardware', onStateChange(state) { isSpeaking = (state === 'speak'); updateStatus(isSpeaking ? '回答中...' : '就绪'); // 说话结束后自动回到待机 if (!isSpeaking) { setTimeout(() => avatar?.interactiveidle(), 600); } }, onMessage(msg) { if (msg?.type === 'error') { console.error('[SDK Error]', msg); updateStatus('异常'); } }, enableLogger: false, }); avatar.initModel = 'normal'; avatar.init((progress) => { if (progress >= 100) { isReady = true; updateStatus('就绪'); avatar.interactiveidle(); } }); // ===== 知识库问答 ===== async function askKnowledge(question) { if (!question.trim() || !isReady) return; appendMessage('user', question); document.getElementById('user-input').value = ''; // 如果正在说话,打断 if (isSpeaking) { avatar.interactiveidle(); await sleep(200); } updateStatus('检索中...'); try { // 1. 调用 RAG 检索 + 大模型生成 const res = await fetch(CONFIG.ragApiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question }), }); const data = await res.json(); // 2. 显示回答 + 引用来源 let displayText = data.answer; if (data.sources?.length) { displayText += `\n\n📎 参考:${data.sources.join('、')}`; } appendMessage('assistant', displayText); // 3. 驱动数字人说话(只说回答部分,不说引用) updateStatus('回答中...'); avatar.speak(data.answer, true, true); } catch (err) { console.error(err); appendMessage('assistant', '抱歉,检索时出了点问题,请重试。'); avatar.idle(); updateStatus('就绪'); } } // ===== UI 工具 ===== function appendMessage(role, text) { const container = document.getElementById('chat-messages'); const div = document.createElement('div'); div.className = `message ${role}`; div.innerHTML = text .replace(/\n/g, '<br>') .replace(/📎 参考:(.+)/, '<span class="source-tag">📎 $1</span>'); container.appendChild(div); container.scrollTop = container.scrollHeight; } function updateStatus(text) { document.getElementById('status-text').textContent = text; } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // ===== 事件绑定 ===== document.getElementById('send-btn').addEventListener('click', () => { const input = document.getElementById('user-input'); askKnowledge(input.value); }); document.getElementById('user-input').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); askKnowledge(e.target.value); } });

4.5 第三步:跑起来

启动命令很简单,开三个终端窗口:

# 终端1:启RAG检索服务 cd rag-backend python rag_search.py # → Uvicorn running on http://0.0.0.0:8765 # 终端2:前端静态服务(可以用任意方式,我用的 npx serve) cd frontend npx serve . # → Serving on http://localhost:3000 # 终端3(可选):首次建库 cd rag-backend python rag_server.py # 只需要跑一次

然后浏览器打开http://localhost:3000,等数字人加载完(大概 3-5 秒),就可以开始问了。

五、实测效果:问几个真问题试试

搭完之后,我拿自己的知识库做了一轮测试。下面是真实的问答记录:

测试 :具体知识检索

我问:“我的技术栈是什么?”

数字人停顿了大约 0.8 秒,开口回答:

感受:这个回答跟我当初记的内容吻合,而且它把我的两篇笔记关联起来了——一篇是讲微服务的,一篇是讲 DDD 的。这是纯关键词搜索做不到的。


六、开发者视角:几个真实感受

6.1 RAG + 具身交互 = 1+1 > 2

做这个 Demo 之前,我觉得 RAG 的交互就是"搜一下、返回一段文字"。但加上数字人之后,体验完全不一样了。

具体来说,变化在于:

-等待被"服务"的感觉:纯文字回答的时候,你等着文本一行行吐出来,感觉是在等一个工具的输出。但数字人开口说话的时候,更像是一个同事在回答你

-回答质量感知提升:同一个回答内容,用语音 + 表情 + 口型同步的方式呈现,比纯文字看起来更可信。这可能是心理学层面的——我们对"人说出来"的信息天然更信任

-自然的多轮衔接:因为数字人有"正在听"和"正在说"的状态切换,你潜意识里会被引导成对话模式,而不是搜索引擎模式

6.2 SDK 集成比想象中顺滑

魔珐星云的 JS SDK 大概是我用过的最简单的多媒体 SDK 之一了。真正干活的方法就三四个:

  • new XmovAvatar({...})— 创建实例

  • .init(callback)— 初始化

  • .speak(text, isStart, isEnd)— 让数字人说话

  • .interactiveidle()— 进入待机/打断

对于一个不需要深究 3D 渲染、音频编解码的前端开发者来说,这个门槛非常低。我算了一下,从开始写第一行代码到数字人在页面上说出第一句话,大概花了 20 分钟。

6.3 延迟表现

从用户按下发送键到数字人开口,整体链路延迟:

环节耗时
文本向量化 + ChromaDB 检索~50-100ms
DeepSeek API 生成回答~600-1500ms
星云 TTS + 口型驱动 + 渲染~300-500ms

|端到端总计|~1-2 秒|

星云自己的链路(TTS → 驱动 → 渲染)稳定在 500ms 以内,瓶颈主要在大模型生成。如果在本地跑一个小模型(比如 Qwen2.5-7B 量化版),总延迟能压缩到 800ms 以内。

这背后是星云的AI 端渲和端侧解算技术在起作用——画面渲染在本地设备上完成,不需要等云端回传视频流。再加上参数流架构,整个交互链路极短。


七、还可以怎么玩?

这个 Demo 只是一个起点。基于同样的技术栈,其实可以做很多有意思的东西:

进阶玩法 1:接入你的浏览器书签

写一个 Chrome 插件,自动抓取你收藏的网页内容做向量化。以后你想找一个"上次看过的关于 React 性能优化的文章",直接问数字人就行。

进阶玩法 2:工作日报生成器

把每天的 Slack/飞书消息、Git 提交记录、会议纪要都喂给知识库。下班前跟数字人说一句"帮我总结今天干了什么",它给你生成日报。

进阶玩法 3:代码库问答

把你的项目代码做 Code Embedding。遇到不懂的业务逻辑,不用翻代码,直接问数字人"这个订单状态机是怎么流转的?"

这些场景的共同点是:你每天都会遇到,搭建门槛不高,但体验提升巨大。


八、写在最后

回头看我做这个项目的初衷——“不想再翻几百篇笔记找一个信息”——确实被解决掉了。但过程中更大的收获是:我意识到AI 的交互形态在悄悄发生变化

大模型已经足够聪明了。DeepSeek、Qwen、GLM,这些模型的理解和生成能力对于日常使用来说绰绰有余。接下来的竞争点不在于"谁的模型更聪明",而在于"谁的交互体验更像人"。

具身交互这件事,不是锦上添花的花活。它解决的是一个根本问题:当 AI 从"工具"变成"伙伴",人跟 AI 之间需要的不再是命令行和文本框,而是自然的、有温度的多模态交互。

如果你的工作或项目里也有类似的场景——需要让 AI 从对话框里走出来,以更自然的方式服务用户——不妨去魔珐星云看看

技术栈

  • 大模型:DeepSeek-V3

  • 向量模型:text2vec-base-chinese(本地离线)

  • AI Coding 工具:Cursor

  • 具身交互平台:魔珐星云 JS SDK

  • 运行环境: Python 3.11 / Node.js 20

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询