Infrastructure for the next generation of voice agents, designed to provide universal memory. It is divided into a left brain and a right brain, storing information and emotions respectively, while a fully streaming architecture eliminates latency at the fundamental level.
中文 | English
项目主页 🌐 /
技术报告 📖 /
VoiceMem Utils 🤗 /
VoiceMem Model Families 🤗 /
ChatMem-400K 🤗
我们带来 VoiceMem,为语音模型增加最后一个组件:灵魂,让它真正越来越懂你。VoiceMem 建立在「流式双脑」架构之上,提供精准、有情感、懂人格、低延迟且最便宜的记忆服务。本仓库将「永久保持全部开源」。
快速理解 VoiceMem:
## 🔥 News
## 🎬 Demo
注意: 播放前需要先取消静音。
https://github.com/user-attachments/assets/0d919f8c-e9ba-4fdb-8078-b049e4b99a28
## 📚 目录
## 🚀 快速开始
### 安装
git clone https://github.com/xzf-thu/VoiceMem.git cd VoiceMem # 安装记忆系统(含 ASR / 声纹 / 场景 / 情绪 / 本地 embedding 全套内置组件) pip install voicemem # 可选:用我们微调的 Qwen 回复模型 pip install "voicemem[slm]"
### 下载所需模型
pip install -U huggingface_hub hf download zhifeixie/VoiceMem_Default_Models_Env --local-dir ./models
### 基础用法
#### 作为离线记忆引擎运行
from voicemem import VoiceMem
vm = VoiceMem(
mode="normal",
openai_key="api_xxx",
top_k=5,
)
# 本地模型是懒加载的,先热起来,别让第一次调用去等加载
vm.warmup()
# 存:音频文件
# 内部跑 ASR / 声纹 / 场景 / 情绪感知 / Embedding 抽取
print("入库开始")
vm.ingest(audio="assets/input.wav") # 我是素食主义者,对坚果过敏。
print("入库结束")
# 查:写入慢是因为要抽事实、打标签、建图;查询走的是纯向量检索,跟写入无关
print("检索开始")
result = vm.search("我的饮食禁忌是什么?")
print("检索结束")
print(result.result_leftbrain, result.result_rightbrain)
# 存:左脑信息文本(无情感)
vm = VoiceMem(
mode="leftbrain_only",
openai_key="api_xxx",
top_k=5,
)
vm.ingest("我是素食主义者,对坚果过敏。")
result = vm.search("我的饮食禁忌是什么?")#### 以流式方式运行 VoiceMem
可以把 VoiceMem 的流式接口看作一个持续处理音频的 VAD 接口。
下面这段:先显式存一条事实,再喂一段问句音频,看记忆是怎么在人还没说完时就查好的;最后照例走一次入库判断。
import asyncio
import os
from pprint import pprint
import numpy as np
import soundfile as sf
from voicemem import VoiceMem
# 沿用上面那个 vm;单独跑这段就自己建一个
vm = VoiceMem(mode="normal", openai_key=os.environ["OPENAI_API_KEY"], top_k=5)
# 本地模型是懒加载的,先热起来,别让第一块音频去等模型加载
vm.warmup()
# 先存一条事实,等下那个问句才有东西可查
vm.ingest("我是素食主义者,对坚果过敏。")
SPEC_MIN_CHARS = 6
searching = False
def on_partial(text):
"""边说边出字。够长了就说明后台这一刻已经开查了。"""
global searching
print(f"\r[partial] {text}", end="", flush=True)
if not searching and len(text) >= SPEC_MIN_CHARS:
searching = True
print("\n[检索开始] 人还没说完,后台已经在查了", flush=True)
async def main():
# 这段音频里是一个问句:「我的饮食禁忌是什么?」
audio, sr = sf.read("assets/question.wav", dtype="float32")
pcm = (np.clip(audio, -1, 1) * 32767).astype(np.int16)
stream = vm.stream(src_rate=sr, vad_threshold=0.5, on_partial=on_partial)
step = int(sr * .032)
for i in range(0, len(pcm), step):
st = await stream.feed(pcm[i:i + step].tobytes())
if st.state != "turn_over":
continue
# VAD 确认这一轮说完了。记忆早在说话过程中就查好了,这里直接取,不再等
print("[检索结束]")
print("转写 ", st.transcript)
print("左脑 ", st.result_leftbrain)
print("右脑 ", st.result_rightbrain)
pprint({k: getattr(st, k) for k in
["speaker_id", "speaker_voiceprint", "emotion",
"entity", "schema", "text_embedding"]})
# 每一轮都要走一次入库判断
print("[入库] LLM 正在判断这句话值不值得入库…", flush=True)
res = vm.ingest(st.transcript)
print(f"[入库] 抽出 {res['facts_count']} 条事实 -> {res['memory_ids']}")
asyncio.run(main())### VoiceMem 交互式演示
演示代码在仓库里(pip 装的包只有库本身),先确认已经克隆并进入仓库目录。
python web/run.py
然后访问:
http://localhost:8787
Demo 默认把终端输出(含 Python logging 和 Uvicorn 的日志)保存一份到
results/logs/voicemem-时间-PID.log,每行带时间戳和 stdout/stderr 标记。
启动时终端会打印实际路径。指定文件或临时关闭如下。
python web/run.py --log-file results/logs/debug.log python web/run.py --no-file-log
回复模型的上下文由当前输入、本次会话尚未入库的对话和检索记忆组成。每轮对话先
进入内存 SessionBuffer;异步记忆写入完成并确认产生持久记忆后,对应 turn 从
SessionBuffer 移除。没有产生长期记忆的临时对话会保留到本次会话结束,不同
Memory Space 和不同 WebSocket 会话互相隔离。
播放期间的插话使用两阶段控制:VAD 首先暂停并保留音频队列;明确停止指令或稳定
ASR 文本确认后才清空队列并取消回复;附和、回声、无文字声音和单音节碎片会恢复
播放。候选静音回退和最长等待时间可分别通过 BARGE_REJECT_SILENCE_MS、
BARGE_CANDIDATE_TIMEOUT_MS 调整。
两种回复模式共用以 PCM 样本位置为基准的输出时间轴。浏览器 AudioWorklet 回报
实际渲染进度,打断时只把已经播放的回复写入 SessionBuffer。TTS 后端可选返回
TimedAudioChunk 提供文字对齐;普通 PCM 后端按分段音频长度和动态语速估算。
## 🧠 VoiceMem:基于流式双脑架构的记忆系统
VoiceMem 是一个面向实时语音智能体的记忆系统。
VoiceMem 不把所有记忆放进同一个检索数据库,而是将记忆拆分成两个互相配合的部分: