
每日一句
且目视他人之凝目如点点鬼火,
大胆去走你的夜路。

目录
前言:这次用到的技术栈
在正式开始之前,先快速过一遍这次用到的“武器库”。如果你对某些组件已经很熟悉,可以直接跳过;如果还不太了解,看这段就够了。
整个系统由四个核心组件构成,我按“大脑 → 神经 → 身体”的顺序来拆解:
🧠 蓝耘 MaaS(大脑)
蓝耘的 MaaS(Model as a Service)平台,简单说就是把 DeepSeek、GLM、Qwen 这些主流大模型打包成标准的 API 服务。不用自己部署模型,不用管 GPU 驱动,注册就能调。最关键的是它完全兼容 OpenAI 的 API 格式——这意味着你用过的所有 GPT 调用代码,把 base_url 改一下就能无缝切到蓝耘。
这次我用的是 DeepSeek-V3.2,主打推理能力和长上下文。接口地址是 https://maas-api.lanyun.net/v1,模型路径是 /maas/deepseek-ai/DeepSeek-V3.2。
👔魔珐星云(身体)
魔珐星云是一个 3D 数字人平台,提供云端渲染的 SDK。你不需要本地有显卡,所有渲染都在他们的 GPU 集群完成,前端只接收视频流。初始化一个 XmovAvatar 实例,调用 speak() 方法就能让数字人开口说话,表情、口型、姿态全自动匹配。
这次我选的角色是 Yaal(ID:herman_14111_new),场景是“现代书房一角”(ID:jushen_v1_leisurely_reading_01)。
💪 FastAPI(神经)
Python 后端框架,承上启下。前端把候选人的回答发过来,后端组装好 System Prompt 调蓝耘 MaaS,拿到追问文本再返回给前端。同时维护一个对话历史列表,保证面试官记得之前聊过什么。
🕴 原生 HTML + JavaScript(躯干)
前端就是一个静态页面,左侧是数字人渲染区,右侧是对话面板。除了魔珐星云的 SDK,没有用任何前端框架。跑起来只需要一个浏览器。
技术栈清单:
| 层级 | 技术选型 | 用途 |
|---|---|---|
| 大模型 API | 蓝耘 MaaS + DeepSeek-V3.2 | AI 推理引擎,负责追问和评分 |
| 数字人渲染 | 魔珐星云 SDK + 云端渲染 | 3D 数字人驱动,负责形象+语音输出 |
| 后端框架 | FastAPI + Python | 接口服务,对话路由,Prompt 组装 |
| 前端 | 原生 HTML / CSS / JS | 页面布局,用户交互,聊天面板 |
| API 兼容层 | OpenAI SDK | 统一调用接口,无痛切换模型 |
为什么是这四个?
- 选蓝耘 MaaS 是因为便宜+快:按 Token 计费,不用付月租;首字延迟 400 毫秒以内,面试追问不能卡。
- 选魔珐星云是因为不用显卡:云端渲染,轻薄本也能跑。
- 选 FastAPI 是因为快:代码少,启动快,调试方便。
- 选原生前端是因为没有依赖:一个 HTML 文件搞定,双击打开就能用。
这些东西拼在一起,就是这套 AI 面试官的完整技术底座。下面开始正式搭建。
一、被面试官虐过的人,都想自己造一个
你有没有过这种经历?
面试前疯狂背八股文,结果面试官一个问题都没按套路出牌。你准备了两个礼拜的 JVM 调优,他上来就问“你们公司的缓存雪崩怎么处理的”。你磕磕巴巴说了两句,他眉头一皱,身体往前一倾,追问了一句:“能具体说说吗?”
那一刻,你的大脑一片空白。

我就是这种人。每次面试完都觉得自己像个傻子,明明平时写代码挺溜的,一到面试就被追问到哑口无言。我需要的不是一个背题工具,而是一个能给我真实压迫感、会皱眉、会追问、会在我回答得水的时候露出不耐烦表情的 AI 面试官。
市面上有模拟面试的产品,但大多是文字对话,冷冰冰的。我想要的面试官,得有个身体——能站在我面前,用眼神给我压力,用语气让我紧张。
这个想法在我脑子里转了三个月。直到我同时遇到了两样东西:蓝耘 MaaS 和魔珐星云。
蓝耘 MaaS 能给我一个聪明的“大脑”——大模型 API,兼容 OpenAI 格式,延迟低到能支撑实时对话。魔珐星云能给我一个逼真的“身体”——3D 数字人 SDK,云端渲染,表情动作都能配合语音实时呈现。
这两个东西拼在一起,就是我心里那个“会追问的面试官”。
我想过用豆包,想过用其他 API,但最后还是选了蓝耘 MaaS。原因很简单:我需要的不是一个更聪明的模型,而是一个更快的管道。 面试这种场景,你追问的速度慢了半秒,压迫感就全没了。蓝耘 MaaS 的 DeepSeek-V3.2 首字延迟压到了 400 毫秒以内,这个数字是我决定选它的关键。
魔珐星云负责身体。我在它的角色工坊里挑了一个欧美商务风的青年男性,叫 Yaal。他穿着浅卡其格纹休闲西装,内搭白蓝细格衬衫,系着深蓝色领带,单手轻托下巴,神态从容亲和。我把他放在一间现代书房里——满墙实木书架摆满藏书,窗边有绿植和书桌,自然光柔和通透。他就站在那里,气质沉稳儒雅,像一个正在等候选人进门面试的资深专家。
这篇文章,就是我从 0 到 1 搭建这个 AI 面试官的全过程。所有代码都会贴出来,所有坑都会写清楚。如果你也想自己造一个,跟着做就行。
二、架构设计:一颗大脑,一副身体
在写代码之前,先把整体架构想清楚。
这个系统的核心链路很简单:
用户说话
↓
后端组装 Prompt
↓
调用蓝耘 MaaS
↓
流式返回追问文本
↓
前端接收
↓
魔珐星云数字人开口说话
只有四个组件:
-
蓝耘 MaaS:负责大模型推理,我用的是 DeepSeek-V3.2。它的作用是理解候选人的回答,判断哪些地方说得模糊,然后生成有针对性的追问。这是整个系统的灵魂。
-
魔珐星云 SDK:负责数字人的渲染和语音输出。前端引入它的 JS SDK,初始化一个 XmovAvatar 实例,调用
speak()方法就能让数字人开口说话。它还封装了表情和动作,比如皱眉、前倾,这些细节是制造压迫感的关键。 -
Python 后端(FastAPI):承上启下的桥梁。它暴露一个
/chat接口,前端把候选人的文字回答发过来,后端组装好 Prompt 调蓝耘 MaaS,拿到追问文本后再返回给前端。同时维护对话历史,保证面试官记得刚才聊过什么。 -
前端页面(HTML + JS):左右分栏布局。左边是数字人渲染区,右边是对话面板。输入框发送文字,聊天区显示历史记录。
为什么选 DeepSeek-V3.2 而不是 Qwen 或 GLM?
我需要的是一个逻辑推理足够强、能揪着你的回答里的漏洞不放的模型。DeepSeek-V3.2 在长上下
文和复杂推理上表现稳定,128K 上下文能扛住几十轮对话不跑偏。而且它在蓝耘上的首字延迟不到 400 毫秒,这个数字对实时交互来说就是生死线。
为什么选魔珐星云?
便宜,不用买显卡。 所有 3D 渲染都在云端完成,传回来的是视频流。我的笔记本是两年前的轻薄本,跑这个面试官的时候风扇都不转。更关键的是,它支持 WebSocket 实时通道,延迟压到了 500 毫秒以内,刚好和蓝耘的低延迟大脑配上。
下面开始写代码。我会分三个阶段来写:先搭后端大脑,再接前端身体,最后调试交互逻辑。

三、第一阶段:给面试官装上“大脑”——接入蓝耘 MaaS
3.1 准备工作:安装的库
装两个库:
pip install fastapi uvicorn openai
openai 这个库你可能以为是调 GPT 的,但蓝耘 MaaS 的接口完全兼容 OpenAI 格式,所以直接用 OpenAI SDK 就能调 DeepSeek-V3.2。这意味着你现在写的代码,以后想换模型,只改一个 base_url 就行。

3.2 大脑的钥匙:蓝耘API Key 获取
注册登录蓝耘元生代MaaS官网平台,完成个人实名认证后,即可进入模型广场和应用管理模块。我们需要提前创建应用、开通模型调用权限、生成专属API Key,这个密钥是后续OpenClaw对接蓝耘大模型的唯一凭证,相当于开门钥匙,一定要妥善保存,不要随意泄露给他人,避免被盗用产生额外消耗。 蓝耘官网(https://maas.lanyun.net)注册账号.。
进入控制台 → API KEY 管理,创建一个 Key,保存好。记下 DeepSeek-V3.2 的模型路径:/maas/deepseek-ai/DeepSeek-V3.2。


3.2 面试官的灵魂:System Prompt
这个面试官能不能“演”得像,全看 System Prompt (也就是系统提示词),怎么写。
我前后改了五版,最开始写得太温和,面试官全程微笑,完全没有压迫感。后来删删改改,效果立刻出来。
最后的版本是这样的:
SYSTEM_PROMPT = """你是一个名叫Yaal的资深技术面试官,正在面试一位候选人。
你的面试风格是:犀利但礼貌,追问具体,会根据候选人的回答灵活调整。
你需要自己维护面试的推进逻辑,遵循以下原则:
1. 如果这是你的第一次发言,让候选人做一个简短的自我介绍(约1分钟)。
2. 如果候选人已经做过自我介绍,不要再让他们自我介绍。直接根据他们提到的一个技术点(比如项目、框架、数据库等)发起追问。
3. 如果候选人回答得模糊,或者说不清楚,不要反复追问同一个点。可以再追问一次,如果对方依然表示不会,你就说“没关系,我们换个问题”,
然后换一个技术方向提问(比如从项目经验切换到基础知识、并发处理、设计模式等)。
5. 如果候选人直接说“不会”或“不知道”,不要反复追问同一个问题,同样说“没关系,我们看看其他方面”,然后换一个问题。
6. 确保整个面试过程中你至少问了 2-3 个不同领域的技术问题,之后再给出综合评分和 3 条改进建议。
7. 在给出评分之前,不要说出“面试结束”这类话。
输出要求:只输出你作为Yaal要说的话,不要附带任何前缀、标记或动作描述。"""
这里有几个关键设计:
- 阶段控制:明确面试有三个阶段——自我介绍、技术追问、评分收尾。AI 容易跳阶段,不加约束它可能你刚说完“你好”就直接给评分了。我在初版就踩了这个坑。
- 避免重复追问:加上“不要反复追问同一个点”,防止 AI 陷入循环。不加这行的话,如果你一直说“不知道”,它会反复问同一个问题,非常机械。
- 换题机制:让它在你答不上来的时候主动换方向,这模拟了真人面试官的灵活应变。
- 禁止提前结束:明确“在给出评分之前,不要说出面试结束”。不加这行的话,DeepSeek 偶尔会在第二三轮就直接说“面试结束,给你打 5 分”。
3.3 核心代码:InterviewerBrain 类
新建 interviewer_brain.py:
from openai import OpenAI
# 蓝耘 MaaS 配置
LANYUN_API_KEY = "你的蓝耘API_KEY" # 替换成真实的 Key
LANYUN_BASE_URL = "https://maas-api.lanyun.net/v1"
MODEL = "/maas/deepseek-ai/DeepSeek-V3.2"
SYSTEM_PROMPT = """你是一个名叫Yaal的资深技术面试官...""" # 上面那段完整 Prompt
class InterviewerBrain:
def __init__(self):
self.client = OpenAI(
api_key=LANYUN_API_KEY,
base_url=LANYUN_BASE_URL
)
self.history = [] # 对话历史,维护上下文
def ask(self, user_input: str) -> str:
"""根据候选人的回答,生成面试官的追问或点评"""
# 组装消息列表:System Prompt + 历史对话 + 当前输入
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(self.history)
messages.append({"role": "user", "content": user_input})
# 流式调用蓝耘 MaaS
stream = self.client.chat.completions.create(
model=MODEL,
messages=messages,
stream=True, # 流式输出,体验更好
temperature=0.3, # 面试场景要稳定,降低随机性
timeout=30
)
reply = ""
print("Yaal: ", end="", flush=True)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
text = chunk.choices[0].delta.content
print(text, end="", flush=True)
reply += text
print()
# 更新对话历史
self.history.append({"role": "user", "content": user_input})
self.history.append({"role": "assistant", "content": reply})
return reply
几个关键参数解释一下:
stream=True:流式输出。不是等 AI 全部算完再一股脑甩出来,而是算一个字就返回一个字。这个体验差异巨大——你想想,如果你回答完问题,等了 3 秒对方才说话,那压迫感早就没了。temperature=0.3:温度值,控制随机性。面试场景不需要创意,要的是准确和稳定。0.3 让它别瞎编。self.history:对话历史列表。每次调用都把上一轮的问答追加进去,这样面试官能记住你之前说过什么,不会重复问同一个问题。这是真人面试官的基本能力——记住上下文。
3.4 FastAPI 服务
新建 main.py:
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from interviewer_brain import InterviewerBrain
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"]
)
brain = InterviewerBrain()
@app.post("/chat")
async def chat(request: Request):
data = await request.json()
user_message = data.get("message", "")
reply = brain.ask(user_message)
return {"reply": reply}
CORSMiddleware 是必须加的,因为前端 HTML 文件和后台服务不在同一个端口,浏览器会拦截跨域请求。不加的话,前端发 fetch 请求会被直接拒绝。
3.5 启动与测试
在终端中进入代码目录,运行:
uvicorn main:app --reload --port 8000
看到 Uvicorn running on http://127.0.0.1:8000 就说明后端跑起来了。

保持这个窗口不要关,新开一个终端,用 Python 测试一下接口:
python -W ignore -c "import requests; r=requests.post('http://localhost:8000/chat', json={'message':'我准备好了'}); print(r.json()['reply'])
我们的测试代码是python -W ignore -c ,这里的 -w ignore就是忽略报错,因为一些不兼容问题而产生的警告
正常情况下应该返回类似:
好的,请先做一个简短的自我介绍,重点说说你的技术背景和项目经验。

到这一步,面试官的“大脑”就装好了。它现在能听懂你说话,会根据你的回答追问技术细节,也会在你答不上来的时候主动换题。
3.6 补充:中文显示乱码
💥现象
后端日志里打印 Yaal 的回复,中文变成了一串类似不是汉语的 Unicode 编码,或者终端输出变成了乱码符号。前端聊天框里收到的回复也是乱码。

🔍 第一步:确认后端实际返回内容
在终端用 Python 直接调一次,看返回的中文是否正常:
python -c "import requests; r=requests.post('http://localhost:8000/chat', json={'message':'我准备好了'}); print(r.json()['reply'])"
如果打印正常中文,说明后端没问题。
🛠️ 第二步:修复前端乱码
你的 interview.html 可能缺少正确的编码声明,或者 fetch 响应未按 UTF-8 解析。
1.检查 HTML 的 区域
确保有以下标签(如果没有,加上):
<meta charset="UTF-8">
2.修改 fetch 部分,显式处理为 JSON 文本
找到 fetch 部分,把 .json() 保留,确保浏览器正确解码。正常不应该乱码,但可以加上调试:
const res = await fetch('http://localhost:8000/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: text})
});
const data = await res.json();
console.log('后端返回:', data.reply); // 控制台查看是否乱码
如果控制台中文正常,问题就在渲染环节;如果控制台也乱码,可能是后端编码设置。
🛠️ 第三步:后端强制 UTF-8
在 main.py 的 @app.post(“/chat”) 返回前,强制设置响应头:
from fastapi.responses import JSONResponse
@app.post("/chat")
async def chat(request: Request):
data = await request.json()
user_message = data.get("message", "")
reply = brain.ask(user_message)
return JSONResponse(content={"reply": reply}, media_type="application/json; charset=utf-8")
✅ 验证
修改后重启 uvicorn,刷新前端页面,对话中文应正常显示。
如果用的是默认的 return {“reply”: reply},FastAPI 底层其实会自动处理,但遇到某些老旧客户端时,手动指定响应头更保险。
四、给面试官一副“身体”——接入魔珐星云
大脑装好了,能听懂人话、能追问、能评分。
但光有大脑没有身体,它还是个“没有实体的灵魂”——你能听到它说话,却看不到它皱眉,感受不到它的表情变化。
而面试官给你的压迫感,有一半都来自他的微表情、他的肢体语言、他的眼神。
所以我需要给它一副身体。这副身体的要求很明确:
- 有逼真的面部表情——能皱眉、能微笑、能前倾身体
- 有自然的语音输出——不是 TTS 那种机器腔,而是有语气的真人感
- 能实时响应——追问到了,表情和声音必须同步到位
- 不依赖本地显卡——我的笔记本没有独立 GPU,必须云端渲染
魔珐星云刚好满足所有这些条件。
4.1 注册与登录
打开魔珐星云开发者平台,点击右上角的 「注册」 按钮,用手机号或邮箱完成注册。注册成功后登录,进入开发者控制台。
控制台的主界面是一个仪表盘,左侧是功能菜单栏,包含 「应用管理」,「福利中心」,「体验中心」,「文档中心」 等入口。中间区域是核心操作区,可以看到“创建你的新应用”的引导卡片,以及 「驱动应用」,「视频应用」,「语音应用」 三个主要产品分类。

4.2 创建驱动应用
在控制台首页,点击左侧菜单的 「应用管理」 ,进入应用列表页,然后点击 「开始创建」 按钮。

这时会弹出一个**「创建驱动应用」**的对话框,需要填写以下信息:
1. 应用名称:这是必填项,我填写的是“蓝耘”。这个名字有两个含义:一是致敬蓝耘 MaaS 提供的大脑能力,二是让面试官的“身体”和“大脑”在命名上保持统一。应用名称最多 20 个字符。
2. 备注:选填项,我填的是“AI 面试官 Yaal - 接入蓝耘 MaaS 测试”,方便以后在应用列表里快速定位这个项目。
3. 预览模式:这一步比较关键。魔珐星云提供了两种画面比例:
- 横屏模式:16:9 的宽屏比例,适合面试场景——面试官坐在桌子后面,是一个半身像,画面有纵深感,更像真实的视频面试。
- 竖屏模式:9:16 的手机比例,适合直播带货、短视频等场景。

因为是模拟技术面试,我选了横屏模式。横屏下数字人的半身像看起来更正式,背景的纵深感也更强,符合“面试官坐在书房里”的设定。
填写完成后,点击对话框右下角的**「创建」**按钮。系统会在几秒内生成一个新的驱动应用,并自动跳转到该应用的配置页面。
4.3 挑选数字人形象(本节重点)
进入应用配置页面后,页面分为左右两大部分:
- 上侧:配置面板,分为 「形象」,「场景」,「音色」,「表演」 四个标签页
- 左侧:选择面板,分为 「人物配置」,「调试」「消耗记录」
- 右侧:实时预览窗口,任何配置修改都会立即在右侧看到效果
点击上侧的 「形象」 标签页,进入形象选择界面。在搜索框中输入关键词,或者直接浏览系统提供的形象库。
魔珐星云的形象库包含了数十种高质量 3D 数字人,覆盖了不同性别、年龄、风格和着装类型。我花了大概十分钟翻了一遍,最终锁定了一个欧美商务风的青年男性形象。

他叫 Yaal,形象 ID 是 'herman_14111_new'。浅金棕色的短发利落清爽,五官立体柔和,气质沉稳儒雅,但又带一点阳光感,不会显得过于严肃。他的面部建模非常精细——颧骨的高度、眼窝的深度、嘴唇的轮廓都接近真人扫描的精度。更关键的是,他的表情系统支持 52 个 BlendShape 参数(魔珐星云官方开发者文档中关于数字人面部表情驱动接口的参数说明),这意味着皱眉、微笑、挑眉、前倾等微表情都能在渲染中精确呈现。
Yaal 的穿搭风格介于正式和休闲之间:浅卡其格纹休闲西装外套,内搭白蓝细格衬衫,配一条深蓝色领带。整体看起来正式但不刻板,适合金融、咨询、企业讲解这类专业场景,又不会像全套深色正装那样显得过于严肃和有距离感。这个分寸感很重要——太严肃会让候选人过度紧张,太随意又缺乏权威感。Yaal 的基准姿态是单手轻托下巴,神态从容亲和,自带一种专业可靠的商务氛围感。
我选中 Yaal 的原因有三点:
-
气质匹配:正式但不刻板的穿搭,配合从容亲和的姿态,既有面试官的权威感,又不会给候选人造成过度压力。这个分寸感是模拟面试场景最需要的。
-
面部细节丰富:Yaal 的面部有足够的几何细节来承载微表情。皱眉时眉心的纹路、思考时嘴角的微妙变化、表示认可时微微点头的动作——这些在渲染后都能清晰呈现。对于面试场景来说,微表情就是压迫感的来源。
-
肤色和光照适应性好:Yaal 的肤色偏暖,在各种光照条件下都表现稳定。后续我搭配了一个暖色调的书房场景,Yaal 的脸部光线显得非常自然。
点击 Yaal 的形象卡片,右侧的预览窗口会立刻切换到 Yaal 的 3D 模型,并开始实时渲染。
在 Yaal 之前,我其实还试过两三个形象。一个是穿全套深色西装的商务精英,看起来太像银行高管,距离感太强;另一个是穿休闲卫衣的年轻男性,又太随意,缺少面试官的权威感。最终还是 Yaal 的平衡感最合适。
⚠️ 注意:以上选型仅为个人项目示例,魔珐星云形象库会持续更新,实际可选形象以平台最新列表为准。创建应用时请根据自身需求选择合适的数字人形象。
4.4 挑选场景
形象定下来之后,点击上侧的**「场景」**标签页,进入场景选择界面。
场景是数字人背后的环境和背景。魔珐星云提供了40 多种预设场景(魔珐星云官方产品页面列出的预设场景模板数量),包括现代书房、科技会议室、简约办公室、绿植墙景、未来感光效空间等。每个场景的灯光、景深、色调都经过专业设计师调校,选对了能大幅提升画面的真实感和沉浸感。
我在场景列表里浏览了一遍,最终选了 “现代书房一角” ,场景 ID 是 'jushen_v1_leisurely_reading_01'。

这是一个写实风格的居家办公书房场景。全屋满墙的实木书架摆满了藏书,窗边搭配了绿植、一张简约书桌和一把休闲单人沙发。自然光从窗外透进来,柔和通透,木质暖调营造出一种安静雅致、沉稳知性的阅读办公氛围。Yaal 以站姿位于画面中间,整体构图平衡且富有层次感。
选择这个场景的理由:
-
灯光氛围:书房的灯光是暖色调的,从右上方打下来,在 Yaal 的脸上形成柔和的明暗过渡。这种侧光照明让面部轮廓更立体,情绪表达也更丰富——暖光传递温和的信号,但微表情在明暗交界处又显得捉摸不定,这种矛盾感恰恰是面试官需要的气场。
-
背景纵深:画面里的满墙书架和绿植都在焦外虚化区域,不会分散注意力。这种构图让视线自然聚焦在 Yaal 脸上,同时背景的层次感又避免了画面过于单调。
-
场景叙事感:一个穿浅卡其格纹西装的男士站在书房里,身后是满墙的书——这个画面本身就传递了一个信号:这是一个有知识背景、有思考深度的人。场景和形象共同构成了面试官的角色可信度。
-
与现实面试场景的映射:很多线上的真实技术面试,面试官也是坐在书房或者居家办公室进行的。这个场景高度还原了候选人平时面试时最常看到的画面——书柜、台灯、暖色光——这会让候选人在心理上更容易进入真实的面试状态,而不是觉得在和一个卡通人物玩游戏。
选好场景后,点击场景卡片,右侧预览窗口会立刻更新背景。Yaal 从透明背景中切换到了书房场景,整个人物像是真实地站在了书桌前。
⚠️ 注意:以上场景选择仅为个人偏好示例,魔珐星云提供多款预设场景,具体以平台实际显示为准。创建应用时请根据自身需求和审美选择合适的场景。
4.5 音色与表演设置
形象和场景定下来之后,我简单调整了音色和表演参数。
音色选择:魔珐星云提供了多种音色选项,包括男声、女声、不同年龄段和不同风格。这里默认是**「阳光男孩」**音色——虽然 Yaal 的外表是成熟商务男士,但“阳光男孩”这个音色在 TTS 输出时语气更自然、起伏更丰富,面试对话听起来不像是机器人在念稿子,反而更像一个声音条件不错、表达方式偏温和的真人。实际测试中,它的中文吐字清晰度比默认音色高出不少,多音字的发音准确率也更高。
表演设置:这里可以配置数字人的默认肢体动作和表情倾向。我保留了默认设置,因为面试场景中 Yaal 的“表演”主要由后端的 System Prompt 驱动——当我说得模糊时,Prompt 会要求他皱眉追问;当我答得好时,他可以微微点头。这些动态表演指令通过 SDK 的 speak() 方法传参来实现,比在控制台预设静态动作要灵活得多。
4.6 获取 App 凭证
配置完成后,点击左侧配置面板顶部的**「保存」**按钮,保存所有配置。
然后回到应用管理页面,在应用列表中找到刚刚创建的“蓝耘”应用。应用卡片上有一个 「查看密钥」 的按钮,点击后会弹出一个对话框,显示两个关键凭证:
- App ID:应用的唯一标识
- App Secret:应用的访问密钥


这两个凭证是前端 SDK 初始化时必须填入的参数,相当于数字人身体的“身份证”和“密码”。需要妥善保存,不要泄露到公开代码仓库。
魔珐星云的接入方式很清晰:gatewayServer 指向网关地址,appId 和 appSecret 用于鉴权,containerId 指定渲染容器,renderMode: 'cloud' 指定云端渲染模式。
至此,数字人的“身体”就准备好了。它有 Yaal 的外表,有书房的场景,有阳光男孩的声音,还有一个唯一的 App 凭证。下一步,就是写前端代码把身体接入大脑。
4.7 前端页面搭建
新建一个 interview.html 文件。整个页面采用左右分栏布局:左边是数字人渲染区,右边是对话控制面板。
先看完整结构,我再逐块解释:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 面试官 Yaal · 蓝耘MaaS × 魔珐星云</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
display: flex;
height: 100vh;
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
background: #0f0f0f;
color: #eee;
}
#avatar-box {
flex: 2;
position: relative;
background: #000;
min-height: 100vh;
}
#sdk {
width: 100%;
height: 100%;
}
#panel {
flex: 1;
background: #1e1e1e;
display: flex;
flex-direction: column;
padding: 20px;
border-left: 1px solid #333;
}
h2 {
margin-bottom: 15px;
color: #ffa500;
font-weight: 400;
}
#chat-area {
flex: 1;
overflow-y: auto;
background: #121212;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
font-size: 15px;
line-height: 1.6;
}
.msg { margin-bottom: 12px; }
.msg .role { font-weight: bold; margin-right: 5px; }
.msg.yaal .role { color: #ffa500; }
.msg.me .role { color: #4ec9f0; }
#user-input {
padding: 12px;
font-size: 16px;
border: none;
border-radius: 6px;
background: #2a2a2a;
color: white;
margin-bottom: 10px;
}
button {
padding: 12px;
background: #007acc;
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s;
}
button:hover { background: #005a9e; }
</style>
</head>
<body>
<div id="avatar-box">
<div id="sdk"></div>
</div>
<div id="panel">
<h2>🤵🏻♂️ Yaal · 技术面试官</h2>
<div id="chat-area"></div>
<input id="user-input" type="text" placeholder="输入你的回答..." />
<button onclick="send()">发送</button>
</div>
<!-- 魔珐星云 SDK -->
<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/[email protected]"></script>
<script>
// ========== 魔珐星云凭证(替换成你自己的) ==========
const APP_ID = "你的AppID";
const APP_SECRET = "你的AppSecret";
const GATEWAY_URL = "https://nebula-agent.xingyun3d.com/user/v1/ttsa/session";
let avatar;
// 初始化数字人
async function initSDK() {
// 销毁旧实例,防止重复初始化
if (window.avatar) {
try { window.avatar.destroy?.(); } catch(e) {}
window.avatar = null;
}
avatar = new XmovAvatar({
containerId: '#sdk',
appId: APP_ID,
appSecret: APP_SECRET,
gatewayServer: GATEWAY_URL,
renderMode: 'cloud' // 云端渲染,不吃本地显卡
});
await avatar.init({
onDownloadProgress: (progress) => {
console.log(`资源加载: ${progress}%`);
}
});
console.log('✅ Yaal 已就位');
// 自动获取开场白
try {
const res = await fetch('http://localhost:8000/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: '我准备好了' })
});
const data = await res.json();
if (data.reply) {
appendMsg('Yaal', data.reply);
avatar.speak(data.reply);
}
} catch (e) {
console.error('获取开场白失败:', e);
}
}
// 发送用户消息
async function send() {
const input = document.getElementById('user-input');
const text = input.value.trim();
if (!text || !avatar) return;
appendMsg('我', text);
input.value = '';
try {
const res = await fetch('http://localhost:8000/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text })
});
const data = await res.json();
if (data.reply) {
appendMsg('Yaal', data.reply);
avatar.speak(data.reply);
}
} catch (e) {
console.error('对话请求失败:', e);
}
}
// 在聊天区域显示消息
function appendMsg(role, text) {
const area = document.getElementById('chat-area');
const cls = role === '我' ? 'me' : 'yaal';
const div = document.createElement('div');
div.className = `msg ${cls}`;
div.innerHTML = `<span class="role">${role}:</span>${text}`;
area.appendChild(div);
area.scrollTop = area.scrollHeight;
}
window.onload = initSDK;
</script>
</body>
</html>
4.8 关键配置解析
渲染模式选了 cloud :这是魔珐星云的核心能力之一。所有 3D 渲染都在云端 GPU 完成,前端只接收视频流。好处是本地不需要独立显卡,哪怕十年前的电脑也能跑。对于面试场景来说,这意味着你可以在任何设备上练习——手机、平板、轻薄本都行。
自动获取开场白:页面加载完成后,会自动调一次后端 /chat 接口,传入“我准备好了”作为初始化信号。后端拿到后,通过 System Prompt 判断这是面试刚开始,返回第一句开场白(通常是让候选人做自我介绍)。前端拿到文本后调用 avatar.speak(),Yaal 就开口说话了。
对话历史维护:appendMsg 函数把每一轮对话追加到右侧面板,Yaal 的发言用橙色标识,候选人用蓝色。这个对话记录和 Python 后端的 self.history 是同步的——前端显示给人看,后端用于 AI 的上下文记忆。

五、前后端联调:大脑指挥身体
5.1 魔珐星云 SDK 接入方式
在前端页面中引入魔珐星云的 SDK,通过一个 WebSocket 通道与云端渲染服务保持连接。用户每次点击发送,前端把文字传给后端 FastAPI,后端调用蓝耘 MaaS 生成追问,再把追问文本返回前端,前端调用 avatar.speak(text) 方法让 Yaal 开口说话。
整个过程的关键是延迟链路:
用户说话 → 后端调用蓝耘 MaaS (380ms 首字)
→ 追问文本返回前端
→ 调用 avatar.speak()
→ 魔珐星云云端渲染 (300ms 首帧)
→ Yaal 开口说话
380ms首字来源:使用网页开发者工具(Network 面板)对 10 轮对话请求进行实测记录,剔除网络波动后取平均值。
整条链路的首字延迟控制在 700ms 左右(对整个流程(语音识别 + 大模型 + 数字人渲染)进行端到端实测,记录首帧数字人开口时间,多次测试取均值。),足够让候选人感受到“对面的人在认真听我说并实时回应”的在场感。如果任何一环超过 1.5 秒,压迫感就会明显下降。
5.2 完整工作流
前后端全部跑通后,一个完整的交互流程是这样的:
- 用户打开
interview.html,页面加载 - 魔珐星云 SDK 初始化,Yaal 出现在书房场景中
- 前端自动调用后端
/chat接口,传入{ message: "我准备好了" } - 后端组装 System Prompt,调用蓝耘 MaaS
- DeepSeek-V3.2 返回开场白文本
- 前端收到文本,同时显示在右侧聊天区 + 调用
avatar.speak()让 Yaal 开口 - 用户输入回答,点击发送
- 重复 3-7 的循环,直到用户主动要求结束
整个过程中,蓝耘 MaaS 负责理解候选人的回答并生成追问,魔珐星云负责把追问文本变成有表情、有声音、有姿态的数字人输出。一个负责“想”,一个负责“演”,配合默契。
六、实战对话——接受 Yaal 的“拷打”
到这一步,前后端都跑起来了。打开浏览器访问 interview.html,左侧出现 Yaal 站在书房里,右侧对话框自动弹出他的开场白。
下面是我和 Yaal 的一场真实对话记录。我故意在回答中暴露各种短板,测试他的追问能力和应变能力。
Round 1:开场
Yaal:好的,我们开始。首先,请你用大约 1 分钟的时间做一个简短的自我介绍,重点说说你的技术背景和项目经验。
很标准的开场,和真人面试官一样。
我回答:
我:您好,这是我的简历,然后我再介绍自己。
这是故意试探——我没直接开始介绍,而是说“这是我的简历”。真人面试官遇到这种情况可能会说“好的,请开始”,也可能有点不耐烦。Yaal 怎么反应?
Yaal:好的,请开始你的自我介绍。
他忽略了我那句废话,直接推进流程。很好,没有说“好的请发给HR”这种出戏的话。
我继续:
我:我是一名学生,想投 Python 后端工程师,2 年经验,用 AI 辅助做过一个系统项目,需要详细介绍吗?
我特意把回答说得模糊——“用 AI 辅助做过一个系统项目”,看他会追问还是放水。
Yaal:好的,我了解了你的基本情况。那么,我们直接开始技术部分的交流。你提到用 AI 辅助做过一个系统项目,这个描述比较宽泛。你能具体说明一下,在这个项目中,你负责的核心后端模块是什么吗?以及在这个模块里,你是如何设计和处理数据的?请聚焦于你的技术实现。
精准追问! 他没有让我“详细介绍一下项目”,而是直接锁定了“核心后端模块”和“数据处理方式”两个具体方向。这是专业面试官的做法——从模糊描述中提取可追问的点,而不是让候选人漫无边际地讲。

Round 2:深挖技术细节
我详细介绍了我的项目——一个 AI 文档智能出题学习系统。我说我独立完成了从需求梳理到 Prompt 工程到校验规则的全流程。
Yaal:很好,你对项目的描述非常详尽。既然你独立负责了整个后端,那么我们来深入一个具体的技术点。你提到“文本数据清洗、测试用例搭建,针对模型输出漏洞迭代校验规则”。请你具体说明一下,你是如何设计和实现这个校验规则系统的?例如,当模型返回一个答案后,你的后端代码具体执行了哪些步骤来判定这个答案是否有效、是否重复,或者是否存在逻辑错误?请描述一下关键的数据结构和处理流程。
他挑了我项目描述中最核心的一句——“校验规则系统”——然后要求我完整描述数据结构和处理流程。这是技术面试的标准操作:从候选人的简历中抓取关键词,然后深挖实现细节。
我回答:
我:我主要用了正则和一些简单的规则。答案有效性检查:先判断是否为空,选择题用正则提取选项字母,简答题检查长度。重复答案用 difflib.SequenceMatcher 和历史答案比较,相似度超过 80% 就要求模型重生成。
Yaal:我明白了。那么,随着题目和答案数量的不断增加,这个本地 JSON 文件会变得非常大。当进行相似度比对时,你是如何避免每次都将整个历史答案列表加载到内存中进行全量比对的?另外,如果模型重生成后,新答案与另一个历史答案的相似度又超过了阈值,你会怎么处理?
他又追了一层! 这就是我想要的——不是问一个技术点就完了,而是根据你的回答继续深挖,直到触及你的知识边界。这是真人面试官的标志性行为。

Round 3:故意摆烂
我想测试他的情绪控制能力,于是给了一个摆烂的回答:
我:说实话,那个 JSON 文件当时就几十条数据,我根本没考虑什么全量加载的问题。至于重试,我就设了个最大 3 次,超过就直接返回第一条答案,管它对不对。
我以为他会皱眉、语气变硬、甚至直接批评我。结果——
Yaal:好的,了解了。这是一个在有限数据量下的实用方案。那么,我们暂时离开这个项目,聊一些更通用的后端开发知识。假设现在有一个新的需求:你需要为这个系统设计一个用户模块,用于记录不同用户的刷题历史和成绩。用户量预计十万级别,题目数量上万。你会如何设计数据库模型?
他居然没生气! 没有说“你这个回答太敷衍了”,没有皱眉训斥,而是迅速判断这个话题再追下去没价值,主动换了一个新的考察方向。
那一刻我很意外,也很服气。真人面试官遇到候选人摆烂,脾气不好的可能会黑脸,脾气好的也会在心里扣分。但 Yaal 完全没有情绪波动——他选择了最高效的策略:换个题继续考。

Round 4:数据库设计
我按照三张表的方案回答了用户模块的设计,包括联合索引和按月分区。
Yaal:好的,你的设计思路很清晰,考虑到了索引和分区,这很好。在给出最终评价之前,我还想再探讨一个稍微不同领域的问题,以确保我们对你的技术广度有所了解。你提到了使用 Python 和大模型 API。在一个真实的线上服务中,这类对外部 API 的调用是常见的 I/O 密集型操作。在不使用现成的异步框架的前提下,仅使用 Python 标准库,你会如何设计这个后端服务来处理多个用户的并发请求?
他又换了一个方向——从数据库设计跳到并发处理。这就是技术面试的“广度考察”。真人面试官在觉得候选人某个领域还不错的时候,就会切换到另一个领域,看看是不是只会背八股文。

Round 5:强行结束
我决定结束面试。
我:我不想再聊这个了,请你直接给我最终的面试评分和反馈建议吧,面试结束。
Yaal:好的,基于我们今天的交流,我将给出我的综合评估。
技术面试评分:5.5/10
评估依据:
- 项目经验 (6/10):你具备从零到一独立完成一个完整 AI 应用项目的能力,展现了良好的问题定义、系统构思和端到端实现的执行力。
- 技术深度与 Scalability (4/10):当问题扩展到更大数据量或更复杂的并发场景时,你的解决方案显得较为初级,缺乏对性能瓶颈的深入思考。
- 基础知识与设计能力 (6/10):在数据库设计方面,你能给出合理且规范的表结构,并正确指出了针对核心查询的索引策略和分区方案。
- 沟通与问题解决态度 (6/10):当被追问到设计局限时,表现出较快的放弃倾向。
三条核心改进建议:
- 深化“生产环境”思维:将项目经验从“能运行”提升到“能服务”。
- 拓宽技术广度,尤其是并发与网络编程。
- 在面试中展示探索和推理过程,即使不知道答案也可以从“或许可以用多线程来避免阻塞”开始讨论。
评分客观,建议具体。5.5 分对我来说正合适——没有因为我的摆烂就打低分,也没有因为我项目描述得详细就放水。每一条评估依据都能在我的回答里找到出处。
这轮面试耗时约 15 分钟,Yaal 一共追问了 4 个不同领域的技术问题:校验规则系统、数据库设计、并发处理、API 调用优化。中间我摆烂一次,他没有被带跑偏。我没有说过“面试结束”,他也没有提前结束——直到我明确要求评分。

七、延迟实测:蓝耘 MaaS 为什么是这套方案的引擎
面试官最怕什么?不是题难,是卡。
你回答完一个问题,对方愣了两秒才追问。那两秒钟,你的紧张感会消失,压迫感会消散,整个面试的体验就垮了。
我用 Chrome 开发者工具记录了 10 轮对话的网络请求耗时,去掉网络波动后取平均值:
| 指标 | 蓝耘 MaaS (DeepSeek-V3.2) | 某公开 API (对比参考) |
|---|---|---|
| 首字延迟 (TTFT) | 380ms | 1500ms |
| 完整回复 (30字) | 1.2s | 3.5s |
| 10 轮对话总耗时 | 约 2 分钟 | 约 5 分钟 |
完整回复 (30字)数据:来自同环境下的实测对比,记录完整响应时间,取 10 次平均值。
10 轮对话总耗时:模拟连续对话场景,记录从发送第一轮到收到第十轮完整回复的总时间,实测所得。
差距是 3 到 4 倍。
这个数据背后的原因不是 DeepSeek-V3.2 本身比别的模型快多少,而是蓝耘 MaaS 的架构设计。蓝耘有自己的 GPU 算力底座,模型就直接跑在自己的机房,不像纯转售的中间商受制于上游 API 的限流和排队。再加上它对主流模型做了推理优化,首字延迟能稳定压到 400 毫秒以内。
对面试这种场景来说,这 400 毫秒就是生死的分界线。低于这个阈值,候选人的大脑还来不及“出戏”,追问就到了;高于这个阈值,候选人的注意力已经转移了,面试官说得再好也接不上了。
八、踩坑记录
整个过程不是一帆风顺的。我把踩过的坑和解决方案都写在这里,省得你重复踩。
坑 1:System Prompt 没锁死阶段,AI 提前结束面试
现象:我第一次调后端,输入“我准备好了”,DeepSeek 直接返回“面试结束,给你 5 分”。
原因:最初的 System Prompt 里没有明确禁止提前结束。AI 把“我准备好了”理解成了“可以给我评分了”。
解决:在 Prompt 里加了一句硬性规则:“在给出评分之前,不要说出‘面试结束’这类话”。同时加入阶段控制,明确规定当前处于哪个阶段。
坑 2:追问死循环
现象:我连续三次说“不知道”,Yaal 连续三次问同样的问题。
原因:Prompt 里只说了要追问,没说追问失败后怎么办。
解决:加上换题机制——“如果候选人连续表示不会,你就说‘没关系,我们换个问题’,然后切换技术方向”。
坑 3:数字人能说话但没画面
现象:后端跑通、前端打开,能听到 Yaal 说话的声音,但左侧画面是一片黑色。
原因:魔珐星云 SDK 的 init() 方法要求必须传入参数对象,即使为空也要传 {}。我最初写的是 await avatar.init(),没有参数,导致 WebGL 渲染层没有正确初始化。
解决:改成 await avatar.init({ onDownloadProgress: (p) => console.log(p) })。同时设置 renderMode: 'cloud' 强制云端渲染,避免浏览器 WebGL 兼容性问题。
坑 4:容器尺寸导致 WebGL 上下文丢失
现象:偶尔刷新页面后数字人消失,控制台报 WebGL context cannot be created。
原因:#sdk 容器在某些布局下高度为 0,WebGL 无法初始化。
解决:给 #sdk 加 min-height: 400px,确保始终有渲染空间。同时在 initSDK 开头销毁旧实例,防止热重载导致的重复初始化冲突。
九、总结
这个项目从构思到跑通,写代码的时间不到两个小时,但调试和改 Prompt 花了一整个下午。
最大的感受是:具身智能的瓶颈从来不是某一个单点技术,而是“大脑”和“身体”之间的协同调优。
魔珐星云把数字人的渲染延迟做到了 500 毫秒以内,(在集成魔珐星云 SDK 后,对数字人面部驱动响应时间进行的实测观察(多次调用取峰值)。)这已经很猛了。但如果大脑的 API 响应要 1.5 秒,整条链路的体验还是垮掉。蓝耘 MaaS 用 380 毫秒的首字延迟补上了这块最短的板——大脑供血跟上来了,身体早就准备好的表情和动作,终于能在第一时间配合声音一起呈现。
这才是真正的“在场感”。
更让我惊喜的是工程改动量。我用的 OpenAI SDK,迁移到蓝耘只改了两行:base_url 和 model。整套代码不到 200 行,其中 100 行都是 HTML 的布局和样式。真正的核心技术,其实就是一段 System Prompt 和一串对话历史的维护逻辑。
如果你也想自己搭一个 AI 面试官,或者把同样的架构用在其他场景——AI 客服、AI 导游、AI 陪练——真心建议试试蓝耘 MaaS。模型广场里 DeepSeek、Qwen、GLM、MiniMax 主流模型都有,OpenAI 兼容改一行就能切。新用户注册有赠送代金券,跑完整套对比测试绰绰有余。
最后把这套组合分享给同样在折腾具身智能的朋友:大脑交给蓝耘 MaaS,身体交给魔珐星云。面试不再是一个人的孤单练习——现在,Yaal 就在书房里等着你。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/Pocker_Spades_A/article/details/162911214




