
给 Agent 加记忆,非得先搭一套向量库吗?
摘要
Memvid 是一个用 Rust 写的开源记忆层:数据、向量索引、全文索引和时间索引,全部打包进一个 .mv2 文件。不用起服务,不用配数据库,拷走文件就拷走了记忆。
它想解决什么问题
给 AI Agent 做长期记忆,常见路线是一整套 RAG 管道:切块、嵌入、向量库、检索、重排,每一环都是一个要部署、要升级、要排障的组件。
Memvid 的思路是反过来:别让记忆依赖一堆外部服务,让它就是一个可以随身携带的文件。官方的说法是 "serverless, single-file memory layer",数据、嵌入、检索结构和元数据都在里面,检索直接对文件进行。
截至写作时,它在 GitHub 上约有 1.7 万星、1400 多个 fork,采用 Apache 2.0 协议,主语言是 Rust。
核心设计:Smart Frames
Memvid 借鉴了视频编码的思路。注意,它不存视频,而是把记忆组织成一串只追加、不可变的 Smart Frame。
每个 Smart Frame 存着内容本身,加上时间戳、校验和与基础元数据。这个设计带来几个直接好处:
- 只追加写入:新记忆不会改动、也不会损坏旧数据
- 可查询历史状态:能看到某个时间点的记忆长什么样
- 时间线视角:可以回看知识是怎么一步步演变的
- 崩溃安全:已提交的帧不可变,配合内嵌 WAL 做崩溃恢复
- 高效压缩:沿用了视频编码里的一些压缩思路
结果就是一个"可以倒带的记忆时间线"。
一个文件里装了什么
.mv2 文件按顺序包含:4KB 的文件头、内嵌 WAL(崩溃恢复用,按容量分档,最大 64MB)、压缩后的数据段、Tantivy 全文索引、HNSW 向量索引、时间索引,最后是记录各段偏移的目录。
README 特别强调:没有 .wal、.lock、.shm 之类的旁路文件。要搬走记忆,复制一个文件就够了。
官方给出的性能数据
README 的 Benchmark 部分写了几组数字:
- 在 LoCoMo 基准(10 段、每段约 2.6 万 token 的长对话)上,官方称准确率领先同类记忆系统 35%("+35% SOTA",基线口径 README 未展开)
- 多跳推理高 76%,时间推理高 56%(对比行业平均)
- 延迟 P50 为 0.025ms,P99 为 0.075ms
这些都是项目方自己公布的数据,评测用 LLM 当裁判。它声称评测代码开源、可复现,但我没有独立复跑。选型前建议用自己的数据跑一遍,别直接照抄这些数字。
面向人群
- 想给 Agent 加长期记忆,但不想维护向量数据库的开发者
- 需要离线、本地运行的 AI 应用,比如隐私敏感的个人知识助手
- 希望记忆可回溯、可审计的场景,比如排查 Agent 为什么这么答
- 用 Rust 做底层、也想接 Node.js 或 Python 的团队
快速上手
官方提供四种入口:
| 形式 | 安装命令 |
|---|---|
| 命令行 | npm install -g memvid-cli |
| Node.js SDK | npm install @memvid/sdk |
| Python SDK | pip install memvid-sdk |
| Rust 库 | cargo add memvid-core |
下面用 Rust 走一遍最小流程。先在 Cargo.toml 里按需打开功能:
toml
[dependencies]
memvid-core = { version = "2.0", features = ["lex", "vec", "temporal_track"] }
常用的功能开关有这些:
| 开关 | 作用 |
|---|---|
lex |
基于 Tantivy 的 BM25 全文检索 |
vec |
HNSW 向量检索,本地 ONNX 文本嵌入 |
temporal_track |
解析"上周二"这类自然语言日期 |
pdf_extract |
纯 Rust 的 PDF 文本提取 |
clip |
CLIP 图像嵌入,用于图片检索 |
whisper |
Whisper 音频转写 |
encryption |
口令加密的 .mv2e 胶囊 |
然后创建记忆、写入、提交、检索:
rust
use memvid_core::{AclEnforcementMode, Memvid, PutOptions, SearchRequest};
fn main() -> memvid_core::Result<()> {
// 创建一个新的记忆文件
let mut mem = Memvid::create("knowledge.mv2")?;
// 写入文档并附带元数据
let opts = PutOptions::builder()
.title("Meeting Notes")
.uri("mv2://meetings/2024-01-15")
.tag("project", "alpha")
.build();
mem.put_bytes_with_options(b"Q4 planning discussion...", opts)?;
mem.commit()?;
// 检索
let response = mem.search(SearchRequest {
query: "planning".into(),
top_k: 10,
snippet_chars: 200,
uri: None,
scope: None,
cursor: None,
temporal: None, // 开了 temporal_track 才有这个字段
as_of_frame: None,
as_of_ts: None,
no_sketch: false,
acl_context: None,
acl_enforcement_mode: AclEnforcementMode::Audit,
})?;
for hit in response.hits {
println!("{}: {}", hit.title.unwrap_or_default(), hit.text);
}
Ok(())
}
两个细节:写入之后要调用 commit();另外 README 原版示例用 ..Default::default() 补齐 SearchRequest 的其余字段,但在当前的 memvid-core 2.0.140 里这个结构体并没有实现 Default,照抄会编译失败,所以上面按官方 examples/basic_usage.rs 的写法把字段写全了。
进阶:本地嵌入与模型一致性
开启 vec 后可以用本地 ONNX 模型生成嵌入,默认是 bge-small-en-v1.5(384 维,README 标注约 120MB)。模型文件需要自己下载放到 ~/.cache/memvid/text-models 目录,库不会代你下载。
另一个值得留意的细节是模型绑定:
rust
mem.set_vec_model("bge-small-en-v1.5")?;
绑定是持久的,会写进文件的索引清单。之后再用另一个模型名调用 set_vec_model,会直接报 ModelMismatch,而不是让你把两种嵌入悄悄混进同一个索引。混用嵌入模型是向量检索里很隐蔽的坑,这个设计能把它提前拦住。
如果不想本地跑模型,也可以开 api_embed,用 OpenAI 的 text-embedding-3-small(1536 维)等云端嵌入。
需要留意的地方
- v1 已废弃:早期基于二维码视频的记忆方案已经弃用。README 明确提醒,如果你读到的资料还在讲二维码,那是过时的。
- 嵌入模型要手动准备:本地嵌入的 ONNX 文件需要自己下载。
- 性能数字是自测:见上文,选型时自己验证。
结尾:三行速查
- 记忆就是一个
.mv2文件,复制即迁移,没有旁路文件。 - 写入后记得
commit();SearchRequest没有Default,字段要写全。 - 用向量检索时先
set_vec_model绑定模型,防止嵌入混用。