我用本地助手最大的挫败感,不是它笨,而是它「不认识我」。
昨天我告诉它「我在北京工作、专注本地部署场景、不喜欢废话」,今天开一个新会话,它又客客气气地问我「请问您的使用场景是?」。每次都失忆,每次都从零开始自我介绍——这种关系,谈不上「助手」,顶多是个反复重置的搜索框。
云端大模型靠什么记住你?靠把你过去的对话历史一股脑塞回上下文。可这条路对本地小模型是死路:8GB 卡上的 9B 模型,上下文本来就紧张,你还要把上万 token 的历史每次都灌进去,速度崩、显存崩、效果也崩。
所以本地助手要长期记忆,不能靠「塞历史」,得靠「记要点」。这就是今天的主角——mem0。
一、mem0 是什么,为什么能全本地跑
mem0(开源 Python 包 mem0ai,当前版本 v2.0.7,Apache 2.0 许可)做的事,一句话概括:它不保存你说过的每句话,而是从对话里抽取出关键事实,存成向量;下次需要时,只把相关的三五条捞回来注入。
它天生是给「记忆」这件事设计的中间层。默认它会调云端 LLM 和 embedding,但关键在于——它的 LLM、embedder、向量库三个部件全都可以指向本地:
LLM 指向本地 Ollama(负责判断哪些是值得记的事实) embedder 指向本地 Ollama 的 embedding 模型(把事实转成向量) 向量库用本地 Qdrant 或 Chroma(把向量存起来)
三个部件全在本机,零 API key,数据一个字节都不出机器。对我们这些既想要记忆、又不想把私人信息发给云端的人来说,这才是能用的方案。
二、装起来:三行命令
先装包,再让 Ollama 备好两个模型——一个当大脑,一个当「翻译成向量」的工具:
pip install mem0ai ollama
# 一个 LLM(负责抽取事实)
ollama pull qwen3.5:9b
# 一个 embedding 模型(负责转向量)
ollama pull nomic-embed-text
ollama pull 用最新版 Ollama 即可,不用纠结版本号。qwen3.5:9b 权重约 6.6GB,nomic-embed-text 很小,两个加起来 8GB 显存装得下——这套组合是我给 8GB 卡定的甜区,后面会算账。
三、可跑代码:给助手第一段记忆
下面这段是完整能跑的。核心是一个 config 字典,把三个部件都指向本地:
from mem0 import Memory
config = {
"vector_store": {
"provider": "chroma", # 也可换成 "qdrant"
"config": {
"collection_name": "my_assistant",
"path": "./mem0_db", # Chroma 落地目录
"embedding_model_dims": 768, # 必须和 embedder 输出维度一致
},
},
"llm": {
"provider": "ollama",
"config": {
"model": "qwen3.5:9b",
"ollama_base_url": "http://localhost:11434",
},
},
"embedder": {
"provider": "ollama",
"config": {
"model": "nomic-embed-text",
"ollama_base_url": "http://localhost:11434",
},
},
}
m = Memory.from_config(config)
# 写入记忆
m.add("我在北京工作", user_id="张三")
m.add("我只有一张 8GB 显卡,别推荐大模型", user_id="张三")
m.add("回答尽量简短,不要客套", user_id="张三")
# 取出这个人的全部记忆
print(m.get_all(user_id="张三"))
跑完你会看到,mem0 把这几句话消化成了结构化的记忆条目——注意它存的不是原句,而是抽取后的事实(比如「工作地点:北京」「硬件:8GB 显卡」)。这一步就是本地 qwen3.5:9b 在干活。
四、把记忆接回对话:只注入相关的几条
光存不够,得让助手在回答时用上。逻辑是:用户提问 → 先去记忆库检索最相关的几条 → 只把这几条拼进 prompt → 再问模型。
import ollama
defchat_with_memory(question, user_id="张三"):
# 1) 检索相关记忆(不是全部,是最相关的几条)
hits = m.search(question, user_id=user_id, limit=5)
memories = "\n".join(f"- {h['memory']}"for h in hits["results"])
# 2) 只把这几条注入 prompt
prompt = f"""已知关于这位用户的事实:
{memories}
用户提问:{question}
请结合上面的事实回答。"""
resp = ollama.chat(model="qwen3.5:9b",
messages=[{"role": "user", "content": prompt}])
answer = resp["message"]["content"]
# 3) 把这轮对话再交给 mem0,让它决定要不要记新东西
m.add(f"用户问:{question}\n助手答:{answer}", user_id=user_id)
return answer
print(chat_with_memory("给我推荐个本地模型"))
关键在第 1 步的 limit=5:无论记忆库里存了 5 条还是 5000 条,每次注入的永远只有最相关的几条。这跟「把全部历史塞回去」是两种完全不同的哲学。
五、为什么这件事对小模型「尤其」关键
这不是锦上添花,对本地小模型是生死线。
mem0 官方做过一个对比实验,结论很扎心:同一个 4B 小模型,给它「精简后的记忆」,它能稳定产出合法的 JSON 结构;而给它「原始的长对话历史」,它直接崩掉、输出乱套。
原因不难理解——小模型的注意力是稀缺资源。你塞进去一万 token 的历史,真正有用的可能就三句话,剩下的全是噪声,模型在噪声里迷路(这就是我之前那篇讲长上下文时提过的「中间忘记」现象,模型越小越严重)。而 mem0 先替它把噪声筛掉,只留下干净的几条事实,小模型反而能干得漂亮。
大模型靠蛮力也能从长历史里捞信息,小模型不行——它必须被喂「精挑细选」的记忆。 这就是 mem0 这类工具在本地场景比在云端更有价值的根本原因。
六、8GB 显存账本
把这套跑起来到底吃多少显存?我按 8GB 卡的组合算一遍:
qwen3.5:9b | ||
nomic-embed-text | ||
| 合计 | 8GB 内可跑 |
这里有个省事的点:Chroma 可以完全在 Python 进程内跑,不需要 Docker,落地成一个本地文件夹就行,适合个人助手和轻量场景。如果你要多进程共享、或数据量很大,再换成 Qdrant(需要单独起服务)。
七、mem0 的两个部件选型对照
个人玩、装记性——先上 Chroma,别一上来就折腾 Docker。
八、最容易踩的坑:维度对不上
这是我第一次跑时卡了最久的地方,也是绝大多数人会中招的坑:
config 里的 embedding_model_dims 必须和你的 embedding 模型实际输出维度严格一致。nomic-embed-text 输出的是 768 维,所以上面配的就是 768。如果你写成 1536(那是别的模型的维度),或者换了个 embedding 模型却忘了改这个数字,向量库要么报错、要么静默存进脏数据,检索结果全乱。
记住一个原则:换 embedder,必查维度,同步改 embedding_model_dims。 这一条能帮你省下一晚上。
九、什么时候用 mem0,什么时候不用
装记性也不是万能钥匙,分清楚场景:
✅ 个人助手记住你的偏好 / 身份 / 长期设定 → mem0 的主场 ✅ 客服、陪伴类 Agent 记住每个用户 → 按 user_id隔离,天生契合✅ 小模型 + 需要跨会话记忆 → 越是小模型越该用 ❌ 一次性总结一份长文档 → 那是长上下文 / 分段处理的活,不需要记忆库 ❌ 一整个知识库的检索问答 → 那是 RAG 的活,走我之前写过的那套知识库方案更合适
简单说:mem0 管「关于用户的事实」,RAG 管「关于文档的知识」。两者可以并存,但别混用。
十、行动建议
✅ 想让本地助手认识你:今天就 pip install mem0ai ollama,把上面第三、四节的代码跑一遍,先存三条关于自己的事实✅ 8GB 卡用户:直接用 qwen3.5:9b+nomic-embed-text+ Chroma 这套,进程内免 Docker,最省心✅ 建 Agent / 客服机器人:用 user_id给每个用户单独建记忆,天然多租户⚠️ 换 embedding 模型时:第一件事就是核对并改 embedding_model_dims,否则全盘皆错❌ 别拿它当 RAG 用:文档检索走知识库,事实记忆走 mem0
给助手装上记性之后,你会发现关系变了——它开始「接得住上文」,不再每次让你重新自我介绍。
夜雨聆风