下拉查看上一篇

DiffMem:用 Markdown + Git 替代向量数据库——一个为对话式 AI 设计的轻量记忆后端


如果你做过对话式 AI 产品,多半经历过这样的纠结:明明只是想让聊天机器人「记住用户说过的话」,最后却要部署 Postgres + pgvector,加一套 embedding 服务,还要写一堆代码处理增删改查和过期清理。技术栈复杂度上升一个量级,运维负担也跟着上来——而真正需要被「记住」的事情,本质上不过是几十条事实和几段时间线。

DiffMem 给出了一个反潮流的答案:不用向量数据库,不用 BM25 索引,不用任何外部 DB。所有记忆就是普通 Markdown 文件,存在一个普通 Git 仓库里,检索靠 grep、版本管理靠 git log、回滚靠 git reset。听起来像是开发早期的临时方案,但它已经在生产环境跑了几个月——支撑一个叫 Annabelle 的对话式 AI 服务上数千个 WhatsApp 和 Messenger 用户。本文拆解它的设计取舍,以及为什么这套朴素方案在某些场景下反而比向量 DB 更合适。

Git 提交树作为 AI 记忆载体

核心设计:把记忆当成代码仓库

DiffMem 的核心思想用一句话概括:当前状态用 Markdown 表达,历史演化用 Git 保存。

每个用户得到一个独立的 orphan branch(孤立分支)——这是 Git 里一个很少用到的特性,可以创建一个完全没有共同祖先的分支。git checkout --orphan user/<user_id> 后这个用户的所有记忆就和其他用户的物理隔离,再 checkout 到独立的 worktree(工作树)里,多个用户的 Agent 可以并发读写而不互相干扰。

记忆按照「人 / 上下文 / 时间线」三个轴组织成目录:

worktree-user-123/
├── profile.md                    # 用户基本档案
├── index.md                      # 当前状态摘要
├── memories/
│   ├── people/                   # 关于具体人物的记忆
│   │   ├── sarah.md
│   │   └── boss-mike.md
│   └── contexts/                 # 项目、爱好、工作上下文
│       ├── coffee-business.md
│       └── marathon-training.md
└── timeline/
    ├── 2026-04.md                # 按月聚合的时间线
    └── 2026-05.md

当前状态文件(current files)默认加载,提供 Agent 即时可用的上下文;历史演化沉淀在 Git 提交图(commit graph)里,需要时用 git log、git diff、git blame 按需访问。这种**「显式当前 + 按需历史」**的分层,让 token 消耗维持在一个可控的范围内。

Writer Agent 和 Retrieval Agent

DiffMem 的运行时由两个 LLM-driven 的子 Agent 协作完成。

Writer Agent 在每次对话结束时被触发。它读完整段 transcript,识别出涉及的实体(人物、地点、项目等),决定哪些信息应该被记下来,然后做几件事:更新或新建对应的 memories/people/<name>.md 文件,给 timeline/YYYY-MM.md 追加一条时间线条目,更新 index.md 让摘要保持最新。所有改动作为一次原子 commit 提交,commit message 由 LLM 生成(类似 "Add Sarah's coffee preference + 2026-04-22 marathon training session")。

Retrieval Agent 在每次对话开始时介入。它不是简单地把所有 Markdown 都塞进 context window——那样很快就会超长。它是一个多轮 ReAct 风格的 explorer:先看 index.md 拿到摘要,然后根据当前查询决定要不要 grep 某个关键词、要不要 git log 某个文件、要不要打开某个具体的 memories/people/*.md。这套 shell 命令驱动的检索模式很有意思——它本质上是在让 LLM 模拟一个程序员翻 Git 仓库找答案的过程,每一步都可解释、可验证。

这套方案在生产中跑了几个月之后,作者总结的几个意外收益:

  • Token 效率:默认只加载当前文件,历史按需读取,比直接喂向量 DB 召回的 top-k chunk 更省 token。
  • 可调试性:所有数据都是人类可读的 Markdown,运营人员可以直接打开仓库 review,不需要任何特殊工具。
  • 导出无痛:用户要求导出全部数据,git bundle 一下扔给他就行。

为什么放弃向量数据库

作者在 README 里花了不小的篇幅解释「为什么不用 RAG / 向量 DB」。核心理由有四点。

第一,语义相似不等于语义相关。在对话场景里,「我儿子最近开始练马拉松」和「我妻子最近在跑步」向量距离可能很近,但放在用户档案里指代的是完全不同的人。靠余弦相似度做 top-k 召回经常出现张冠李戴。

第二,对话记忆天生有结构。用户说过的事情可以清晰地归类到「关于人」「关于项目」「按时间」三个轴;用扁平的向量空间表达本身就是信息损失。Markdown 文件 + 目录结构反而能更自然地表达这种层级。

第三,运维成本远高于价值。一个对话式 AI 产品在早期阶段,每个用户的记忆量也就几十到几百条。为了召回这点数据部署 pgvector + embedding 服务,加上索引重建、过期清理、向量维度升级等等长期负担,性价比很低。

第四,可审计性是合规刚需。客服、咨询类应用越来越需要回答「Agent 凭什么这么回答用户」的问题。Git 仓库的 commit message 天然就是审计日志,向量 DB 要做到这点得额外搭一套元数据系统。

何时该用、何时别用

我自己对照看下来,DiffMem 适合以下产品形态:

  • C 端对话式应用,每个用户独立账号,记忆量在百量级(用户档案、偏好、近期对话摘要)。Annabelle 这种 WhatsApp / Messenger 机器人是典型样例。
  • 运营人员要能直接查阅记忆库的客服、CRM 场景。
  • MVP 阶段,希望避免引入向量 DB 的额外组件,先把产品功能跑通。
  • 强合规要求的场景,需要每条记忆都有可追溯的修改历史。

但有几类场景不适合:

  • 海量历史文档召回。如果记忆量在十万条以上,且查询模式是「按语义相似找历史案例」,向量 DB 仍然是更好的选择。
  • 超大规模并发。orphan branch + worktree 模型在用户数到万级以上时,文件系统操作的开销会变得明显。
  • 结构高度异构的非文本数据。DiffMem 假设记忆主要是文本;图像、音频、表格数据没法很自然地落到 Markdown 里。

部署:Docker Compose 一键起

DiffMem 的部署反映了它「轻量」的设计哲学——docker-compose.yml 一份就够:

git clone https://github.com/Growth-Kinetics/DiffMem.git
cd DiffMem
cp .env.example .env
# 填入 OPENROUTER_API_KEY(用来跑 Writer/Retrieval Agent 的 LLM)
docker compose up -d

也支持 Coolify 一键部署。Python 库的用法也很直接:

from diffmem import DiffMemory

memory = DiffMemory(
    worktree_path="/data/worktrees/user-123",
    user_id="user-123",
    api_key="sk-or-..."
)

# 对话结束时
memory.process_and_commit_session(
    conversation_text="<完整对话文本>",
    session_id="2026-05-22-am"
)

# 新对话开始时
context = memory.get_context([
    {"role": "user", "content": "上次你提到的那个咖啡机推荐还有效吗?"}
])

可选的 GitHub 镜像功能让你能把每个用户的记忆库异步同步到 GitHub 私有仓库,作为备份和审计 trail——这一点也是向量 DB 很难做到的。

总结

DiffMem 不会取代向量数据库,但它打开了一扇被忽视的门:当数据量没那么大、结构没那么乱、可审计性比相似度召回更重要时,把 Markdown + Git 当成"数据库"是个值得认真考虑的选项。它的代码量很小,部署门槛极低,所有概念都是程序员日常熟悉的 Git 操作——这本身就降低了引入新基础设施的认知成本。

如果你正在做对话式 AI 的 MVP,或者已经被现有的向量 DB 方案的复杂度折磨到怀疑人生,花一个下午把 DiffMem 跑起来值得一试。它可能不是「最先进」的方案,但很有可能是「刚刚好」的方案。

参考来源

评论

上滑查看下一篇