在折腾 AI Agent 的记忆系统时,我最终没有选择 RAG,而是选了 Obsidian。这篇文章说清楚为什么,以及怎么做的。
RAG 是什么
Retrieval-Augmented Generation,检索增强生成,2020 年由 Facebook 提出,核心思想很直接:不让大模型单独回答问题,而是先从知识库检索相关文档,再把检索结果和问题一起交给模型生成。
标准 pipeline 是这样:
Query → Embedding Model → Vector → Top-K Similarity Search → Retrieved Docs → LLM → Response实际架构通常更复杂,会有这几个组件:
- Embedding Model:将文本映射到向量空间,常用 text-embedding-ada-002、bge、jina 等
- Vector Database:存储向量并提供相似度检索,Pinecone/Milvus/Qdrant/Chroma 等
- Chunking Strategy:文档切分方式,fixed-size、sentence、paragraph、recursive character 等
- Reranker:初筛之后再做一次语义重排序,提升召回精度
- Metadata Filtering:按时间、标签、来源等条件过滤候选文档
常见 RAG 范式:
| 范式 | 说明 |
|---|---|
| Naive RAG | 直接向量检索 + 生成,chatbot 场景最常见 |
| Retriever-Reranker | 先召回 100 条,用 cross-encoder 重排取 Top 5 |
| Hybrid Search | 关键词检索 + 向量检索结果取并集 |
| Self-Rerank | 用 LLMs own reasoning 筛选相关文档 |
| Agentic RAG | 让 LLM 自己决定查什么、查几次、怎么组合 |
| GraphRAG | 用知识图谱组织实体关系,提升跨文档推理能力 |
RAG 的实际问题
技术选型不能只看概念先进,要看实际踩坑。RAG 在个人场景的问题:
检索质量不可控
向量相似度是语义近似,但不是相关性强。一篇讲"缓存穿透"的笔记和一篇讲"Redis 缓存"的笔记,embedding 后距离很近,但实际完全无关。chunk 边界更是玄学——同一个段落,切在不同位置,召回率差一倍。
调参是个无底洞:chunk_size 128/256/512/1024,overlap 0/64/128,top_k 3/5/10/20,embedding 模型 bge-large/zephyr/jina,每个组合都要跑评估。没有成熟的 evaluation framework 之前,基本靠玄学调参。
上下文污染
召回来的文档质量参差不齐,top-k 排序差的片段会稀释正确答案。大模型有 lost-in-the-middle 问题,中间的上下文容易被忽略。token 消耗也高,一次检索可能召回 8k token,频繁调用成本可观。
维护成本
文档更新要重新 embedding,embedding 模型更新要重新索引全量数据,向量数据库本身要运维。这些都是隐性成本。
黑盒,无人工干预空间
RAG 的检索逻辑是隐式的、概率的。我想手动建立两条笔记之间的关联?做不到。我想标记某条知识优先级更高?做不到。我想在笔记里加批注说明"这条已过时"?做不到。
为什么选 Obsidian
核心认知是:Agent 的记忆管理和 RAG 的知识检索,解决的是不同问题。
RAG 解决的是"从大规模非结构化文档中找出相关内容",适合企业知识库、代码库搜索、长文本问答。
Agent 记忆系统需要的是:稳定、可解释、可干预、可持久化、以显式结构组织的人类知识。
这正好是 Obsidian 的设计目标:
- 双向链接
[[笔记]]:显式建立笔记间关联 - 标签
#tag:主题分类 - YAML frontmatter:结构化元数据(状态、优先级、创建时间、复习周期)
- 纯文本 .md:不依赖任何服务,永久可读
- 文件夹结构:人类可理解的知识分类
RAG 靠"语义相似性"组织知识,是概率的、隐式的。
Obsidian 靠"人类主动组织"的知识网络,是确定的、显式的。
对于一个个人 Agent,这套东西还有一个额外好处:文件在本地,我用 AI 读写,你在桌面端用 Obsidian 打开,两者完全兼容,插件正常工作。
整体架构
~/.hermes/├── obsidian-memory.json # 多 vault 配置(路径、默认 vault)├── obsidian-memory.log # 操作日志├── scripts/obsidian/│ ├── mem.py # CLI 入口: new|review|orphans|list|status|config│ ├── review.py # 遗忘曲线引擎(基于 SM-2 算法)│ ├── orphans.py # 孤儿笔记发现(无入链的笔记)│ └── batch.py # 多 vault 批量操作└── templates/obsidian/ # 笔记模板 ├── memory.md # 标准记忆模板 ├── inbox.md # 收件箱模板 ├── session.md # 会话摘要模板 ├── person.md # 人物模板 ├── project.md # 项目模板 ├── topic.md # 主题知识模板 └── review.md # 回顾记录模板多 vault 支持:
// ~/.hermes/obsidian-memory.json{ "vaults": { "voidfarer": { "path": "/home/agentuser/dev/voidfarer", "default": true }, "blog-writer": { "path": "/home/agentuser/blog-archive", "default": false }, "blog-archive": { "path": "/home/agentuser/Documents/Blog Archive", "default": false } }}mem.py 是统一入口,batch.py 遍历所有 vault 执行操作。vault 之间互相隔离,但可以手动建立跨 vault 链接。
遗忘曲线驱动复习
记忆管理用改良的 SM-2 算法:
STAGES = { "new": {"interval": 1, "next_review": "1天后"}, "learning": {"interval": 3, "next_review": "3天后"}, "reviewing": {"interval": 7, "next_review": "7天后"}, "strong": {"interval": 14, "next_review": "14天后"}, "mastered": {"interval": 30, "next_review": "30天后"},}
# 超过 90 天未复习 → 标记为 stale(降级回 new)# 每次复习通过 → 升级状态# 复习失败 → 降一级每次 cronjob 触发时,review.py 扫描所有 vault:
for vault in vaults: for note in find_notes_by_status("new|learning|reviewing|stale"): if note.due_today(): review(note) # 更新下次复习时间 log_review(vault, note)孤儿笔记发现
orphans.py 用 grep -r '\[\[' 扫描所有 md 文件,构建反向链接索引:
def build_inlink_index(vault): """扫描所有 md 文件,找出每个笔记被哪些笔记链接""" index = defaultdict(list) for file in vault.md_files: links = extract_wikilinks(file.content) for link in links: index[link].append(file.path) return index
def find_orphans(vault, inlink_index): """入链为 0 的笔记 = 孤儿""" orphans = [] for file in vault.md_files: if file.basename not in inlink_index: orphans.append(file) return orphansClaude 的代码搜索也用 grep
有意思的是,Anthropic 官方在 Claude 的代码搜索方案里也选了类似思路。他们的 Claude Code 产品里有个内置工具叫 grep,专门用来做代码搜索和文件内容匹配——本质上就是正则 + grep,没有用向量检索。
行业都在吹 RAG,Claude 却回归基础 grep。这里有个本质洞察:代码是结构化的、精确匹配比语义近似更重要。搜索 useState 的用法、找一个函数定义、查某个 error message——这些都是 exact match 场景,grep 比向量检索准得多。
这个逻辑放到记忆管理上是一样的:笔记之间的链接关系是确定的,人名、项目名、技术术语,都是精确匹配。真正需要的不是"语义相似的文档",而是"链接到这条笔记的所有内容"。
所以 orphans.py 用 grep -r '\[\[' 做反向链接索引,而不是跑 embedding。这不是偷懒,是选对了工具。
孤儿笔记不代表无用,可能是新笔记还没整理,也可能是需要主动建立关联。每周发现一次,人工决定是删除还是补充链接。
快速创建笔记
mem new "RAG 的 chunking 策略对比" \ --type topic \ --tags RAG,LLM,技术 \ --vault voidfarer \ --review 7生成的文件:
---uid: 20250427-rag-chunking-strategiestype: topictags: - RAG - LLM - 技术created: 2025-04-27review_status: newnext_review: 2025-05-04aliases: []related: []status: new---
# RAG 的 chunking 策略对比
## 问题背景
## 主流方法
## 优缺点对比
## 参考资料多仓库管理
~/Documents/├── Obsidian Vault/ # 主要记忆库(用户偏好、项目决策、技术积累)├── Blog Archive/ # 博客原文归档(82篇,用于文风分析)└── Writer's Studio/ # 写作工作区(句式库、话题库、创作草稿)博客归档和写作工作区是两个独立的 vault:
- Blog Archive:只读,GitHub 每周同步一次新文章,不主动修改
- Writer's Studio:增量积累,文风画像、句式库、话题库都在这里
两个 vault 共享同一套 mem.py CLI,切换只需 --vault blog-archive 或 --vault blog-writer。
定时任务
| 任务 | 频率 | 内容 |
|---|---|---|
obsidian-memory-review |
每 12h | orphans 发现 + 遗忘曲线复习 |
blog-archive-sync |
每天 | 同步 GitHub 新文章到 Blog Archive |
writer-style-refresh |
每月1日 | 重新跑全量文风分析,更新风格手册 |
什么时候不用这套
这套方案不适合:
- 超大规模文档库(百万级文档请用 RAG)
- 纯代码搜索场景(用 Sourcegraph 而不是笔记)
- 需要语义推理的隐式关联发现(GraphRAG 更强)
适合的是:个人知识管理、需要人工干预、强调可解释性、不想运维向量数据库。