首页 关于我 项目 博客 图谱 简历 联系 English
返回列表
2026年10月16日

CommunicationPet 桌宠身体整合

给已经能听会说的语音桌宠装上一个真正的身体:一个开源项目负责 VRM 渲染、骨骼和表情,原来的语音链路继续负责耳朵、嘴和大脑。

#Three.js#VRM#Electron#WebSocket#TTS#LLM

WhisperaPet 已经能听、能想、能说,但它一直是个能量球。这次的升级是给它装上身体:一个能站在桌面上、说话时嘴会动、听指令会做动作的角色。

分工:两个大脑只留一个

身体来自开源项目 HAST-SEC(混合自适应语义时序骨骼控制与估计系统),它提供 VRM 角色渲染、53 骨骼 IK 解算、26 类动作、13 种表情和 5 个口型通道。

  • WhisperaPet 负责耳朵、嘴和大脑:VAD、ASR、DeepSeek、GPT-SoVITS 的音色与人格。
  • HAST-SEC 负责身体和脸:渲染、骨骼、动作、表情。

HAST-SEC 自带一个 0.8B 的文本模型用来解析动作意图,整合之后不再参与——动作意图改由已有的 DeepSeek 产出。所以它那套 GGUF 和 llama-server(约 2GB)根本不用下载,化身只需要 three 依赖和一个 VRM 模型。

数据流

用户说完一句话之后分成两路并行:

  • 主回复:DeepSeek 流式输出 → 切句 → GPT-SoVITS 合成 → 播放音频。
  • 动作意图:一个并行的 DeepSeek 小请求 → WebSocket 下发 assistant.motion → 化身窗口执行。

两路并行是有意的。动作意图通常比第一块 TTS 音频更早返回,所以角色会在开口之前就开始动,端到端延迟没有增加。

做了什么

  • 口型从音频反推:不额外跑一套语音识别,直接分析正在播放的 TTS 音频,从频谱推出 5 个口型权重,约 30Hz 推给渲染层。这是唯一一条真正与语音同步的通道。
  • 三种表现分开驱动:嘴跟着音频走,表情跟着真实说话状态走(说话期间顶住,说完约 1.2 秒回到中性),身体动作按动作计划里的秒数走。最后一项故意不与语音同步——用户说「抬起右手」时,那是指令本身,应该立刻发生,而不是等角色把回复念完。
  • 角色取代能量球:不再单开窗口,而是以内嵌 <webview> 铺在主窗口背景层上,和原来的着色器能量球互斥显示,用对话区右侧一个按钮切换。首次切换要等十几秒加载 VRM 与策略,之后来回切是瞬时的。
  • embed 模式:给 HAST-SEC 加了一层「只剩角色」的显示模式——透明底、隐藏它自带的全部面板和按钮,并关掉它自己的自主聊天与浏览器朗读,让它专心当一具身体。
  • VRM 0.x 兼容:修正了骨骼校验,让 VRoid 导出的 0.x 模型能被正常导入。

踩过的坑

  1. VRM 0.x 的拇指骨改过名:0.x 里叫 thumbProximal,1.0 才叫 thumbMetacarpal。校验代码读的是改名前的原始 JSON,于是把合法的 VRoid 角色误判成「骨骼不完整」直接拒绝导入。
  2. VRoid 没有 ARKit 表情通道:HAST-SEC 原本把 happy / angry 这类表情转成 ARKit 细通道(mouth_smile_1、sp_cheek 等),但绝大多数 VRoid 导出只有标准 VRM 表情通道,结果是表情完全消失。现在两条路并行:有细通道的走细通道,只有标准通道的走标准预设。
  3. 求解器失败会导致永久冻结:姿态求解器有个「连续找不到可行解」的失败标志,研究版遇到它会直接暂停——对研究演示合理(让人看见失败),但对桌面宠物是灾难:主循环一停,动作轨道的时间就不再推进,那个不可解的姿态永远释放不掉,之后每次恢复都立刻再失败。而 embed 模式把界面和重置按钮都藏了,没有自救路径。现在改成自动复位,连续失败超过 5 次才退回暂停。
  4. <webview> 不接受百分比尺寸:用 % 或默认尺寸时,guest 的视口会永远卡在 150px(HTML 替换元素的默认高度),角色被压成一条。必须按窗口像素显式设定,并在 resize 时同步。
  5. 动作意图的 max_tokens 被推理 token 吃满:设成 240 时正文被截断、JSON 残缺、动作静默丢失(实测一句话要用掉 457 个推理 token)。改成 1536 之后正常。
  6. 主窗口被遮挡时口型掉到 1Hz:Electron 默认给后台窗口降频,渲染层推口型的频率跟着掉。关掉两处 backgroundThrottling 才稳住。

现在的状态

整合已经跑通:说话有口型、表情会变、指令能触发动作。还没做完的是动作精度——目前只有 26 个「方向 + 高度」的离散动作键,没有「把手放在胸口上」这类位置目标,只能落到最近的一个近似动作。

另外两个已知限制:化身加载 VRM 要十几秒,这段窗口期内到达的动作会被丢弃;动作意图是每轮话语解析一次,回复很长时表情不会中途改变。