AI 知识库
为什么个人知识库不用向量库:Index-first 文件系统 RAG 的四步路由
「上 RAG」现在已经成了一种条件反射:有文档,就切片;有切片,就做 embedding;有向量,就建库。但如果你的知识库只是几十到几百个 Markdown 文件——比如一整套自己的笔记——那么这套流程里的每一环,都在增加成本而不是增加准确率。pickled-fish 的 skills/kb-rag 走了另一条路:Index-first 文件系统 RAG,靠 REGISTRY → PACK.md → rg → Read 四步把检索做完。
先看规模:个人知识库到底有多大#
把「RAG」当成一个不可分割的技术栈,是很多过度工程的起点。它其实由几件事组成:文本切分、向量化、向量存储、相似度检索、结果重排。这些组件在大规模、多来源、异构的场景里是必需品;而个人知识库的画像恰好相反:
- 总量小。几十到几百个 Markdown,压缩后可能还没一张手机照片大。
- 结构清楚。文件本身就是分好类的:一个主题一个目录,一个主题一个包。
- 作者只有一个。术语统一,不需要跨语料做语义对齐。
- 更新频繁。改一段话是常事,改动之后索引要跟着重建。
在这样的规模上,「全量读一遍」和「精准检索一段」之间的差别,往往只是几秒钟和几毛钱。真正拉开差距的不是检索速度,而是检索结果能不能解释、能不能复现。
向量库的四个隐性成本#
不是说向量库不好,而是它在小规模下要额外付出四笔账:
| 成本 | 具体表现 | 后果 |
|---|---|---|
| 切分破坏语义 | 固定长度切片会把一段完整论述切成两半 | 检索回来的「证据」缺了前提,结论跟着偏 |
| 依赖外部组件 | 需要跑 embedding 模型,本地或云端 | 离线不可用、隐私边界变模糊、多一层故障点 |
| 索引与原文漂移 | 改了文档但没重建索引 | 检索到的是旧内容,而且很难察觉 |
| 结果不可解释 | 「相似度 0.83」不是一个能追问的理由 | 出了错无法定位是切分问题还是模型问题 |
反过来,文件系统检索的成本几乎为零,而且有一个向量库很难做到的特性:每一条检索结果都可以被完整复述。是哪个文件、哪一行、用什么命令找到的——全部可复现。
四步路由:Index-first 长什么样#
Index-first 的核心思想是「先索引,再展开」——和网页从目录点到正文的过程一样:
REGISTRY.md ← 先看路由表:这个问题该找哪个 pack
↓
PACK.md ← 打开那个包的门牌:它覆盖什么、不覆盖什么
↓
rg 关键词 ← 在包里用文本搜索定位到候选段落
↓
Read 段落 ← 只读命中的那几段,而不是整个文件
四步的关键在于每一步都在缩小范围。到第三步时,搜索空间已经从「所有笔记」缩到「一个包」;到第四步时,读进上下文的只剩几段。整个过程中,上下文预算花在「确认找到的东西对不对」上,而不是花在「把可能的都搬进来」上。
flowchart TD Q["问题"] --> R1["REGISTRY.md:按意图选 pack"] R1 --> R2["PACK.md:确认覆盖什么、不覆盖什么"] R2 --> R3["rg 关键词:在包内定位候选段落"] R3 --> R4["Read:只读命中的那几段"] R4 --> C{"本轮深读的 pack 数"} C -->|"未满 2 个"| R1 C -->|"已满 2 个(上限)"| OUT["输出 1 到 2 个关键点,标注 packs_used"]每一跳都在缩小范围:从“所有笔记”收到“一个包”,再收到“几段”。回路不是可以一直转的 —— 深读到第 2 个 pack 就必须停下来作答,并写出 packs_used。 这套流程在仓库里被描述为 progressive disclosure(渐进式展开):先给索引,再给正文。它同时解决两个问题——检索精度和上下文预算。而这两者在向量方案里经常是互相拉扯的:提高召回就得放进更多切片,放进更多切片就更难判断哪一条才真正相关。
REGISTRY.md:一张路由表,不是索引#
knowledge/REGISTRY.md 是整套机制的第一入口,它的定位很明确:意图 → pack id 的路由表。它不是把所有文档列一遍的目录,而是回答「遇到这类问题,去哪个包里找」。
仓库 README 里给了一张很直观的对照表,我把它抄在这里,因为它比任何解释都更能说明「路由」两个字的意思:
| 你说 | Agent 实际打开 |
|---|---|
| 导出某某的聊天 | skills/wechat-mac-export/SKILL.md → wxecho export |
| 这一大段 chat.txt 什么意思 | local-chat-pipeline.md → 再选理论 / 实操 pack |
| 她什么意思 / 怎么回 / 挽回 | 默认 skills/qingsheng/ |
| 用依恋 / Gottman / NVC 看 | knowledge/packs/attachment-ecr · lovelab · nvc |
| 谈判式沟通、难开口 | knowledge/packs/howtotalk/(13 个子框架) |
| 长期关系怎么维护 | knowledge/packs/partner/ |
| 换一套我自己的笔记 | scripts/add-pack.sh → knowledge/custom/<id> |
这张表的存在意味着两件事。第一,检索的第一跳是确定性的:不是「猜哪个文档相似」,而是「按意图查表」。第二,路由表是可维护的:加一个知识包时,只需要在表里加一行,而不是重新训练或重新构建任何东西。
PACK.md:一个包的边界声明#
第二步是打开 PACK.md。设计意图上,它是每个知识包的门牌,回答三个问题:这个包覆盖什么、不覆盖什么、入口文件在哪。Agent 先读完门牌,再决定要不要真的展开里面的内容——这正是一次「先索引,后正文」的展开。
「不覆盖什么」这一条尤其重要。知识库真正的风险从来不是「找不到」,而是「找到了不该用的那一套」。一个包如果只声明自己覆盖什么,Agent 就会默认它在越界的问题上也有发言权;把边界写清楚,越界时才有「没有依据」这个正确答案。
说明
这里要老实说明一句:
PACK.md的字段与模板,各包可以有自己的写法,具体以仓库里对应 pack 的实际内容为准。本文描述的是它在四步路由里承担的角色,不是一份可以直接照抄的 schema。
为什么 rg 够用,而且比相似度更好用#
第三步是 rg——ripgrep,一个文本搜索工具。在「个人知识库」这个规模上,它比向量检索有三个明确优势:
| 维度 | rg 关键词检索 | 向量相似度检索 |
|---|---|---|
| 结果可解释 | 命中哪一行、哪个文件,一目了然 | 只有一个相似度分数 |
| 可复现 | 同一条命令永远给同样的结果 | 受模型版本、切分方式影响 |
| 更新成本 | 改文件即生效,零重建 | 需要重新切分与向量化 |
| 运行依赖 | 一个本地二进制 | embedding 模型 + 向量库 |
| 隐私 | 数据不出本机,也不需要联网 | 取决于是本地模型还是云服务 |
它的短板也很清楚:依赖关键词命中。所以在这套设计里,关键词不是「碰运气」,而是由前两步兜住的——REGISTRY 已经把范围收到一个包里,PACK.md 又告诉你包里的术语大概长什么样,剩下的搜索在一个小得多的空间里做,命中率自然完全不同。换句话说,Index-first 把语义理解交给了前两步的人工结构,把最后一步留给确定性的文本匹配。
预算控制:同轮最多深读 2 个 pack#
第四步是 Read——真正把命中的段落读进上下文。这里有一条硬约束:同一轮里最多深读 2 个 pack,并且必须标注 packs_used。
这条规则一次解决三个问题:
- 上下文不爆炸。知识包是可以很厚的,无限展开的后果是每一轮都在为「可能有用」付费。
- 结论必须收敛。只允许两个来源,就等于要求回答聚焦在 1–2 个关键点上,而不是把十种理论并列摆出来。
- 过程可追溯。
packs_used是一份自证材料:这一轮到底动了哪些包,白纸黑字写出来。结论可疑时,可以直接回到那两个包去核对,而不是怀疑「它是不是偷偷读了别的」。
把这条约束和四步路由放在一起看,会发现整套设计的取向非常一致:宁可少读一点、读准一点,也不要读多、读杂。
已经捆好的知识包#
仓库里预设了一批 MIT 许可的知识包,放在 knowledge/packs/ 下,路由表直接指向它们:
| Pack | 路径 | 具体能力 |
|---|---|---|
psych-foundations | knowledge/packs/psych-foundations/ | 依恋 / Gottman / NVC / Sternberg 等课程地图 → 跳转表 |
attachment-ecr | knowledge/packs/attachment-ecr/ | ECR-R 依恋风格对话评估 |
dating-master | knowledge/packs/dating-master/ | 语用学 · 依恋 · 信号博弈 |
lovelab | knowledge/packs/lovelab/ | Gottman 四骑士 / NVC / 认知扭曲 / 动力 |
chatrel | knowledge/packs/chatrel/docs/ | Sternberg · Knapp · 社交渗透 · EWS 等方法论 |
rel-consult | knowledge/packs/rel-consult/ | 关系咨询协议 + 伦理护栏 |
nvc | knowledge/packs/nvc/ | 非暴力沟通四步改写 |
howtotalk | knowledge/packs/howtotalk/ | 13 套沟通 / 谈判 / 影响力 SKILL |
partner | knowledge/packs/partner/ | 伴侣画像、场景话术、关系健康指标 |
qingsheng | skills/qingsheng/ | 怎么回、挽回、展示面、Autopilot 子命令 |
注意 qingsheng 不在 knowledge/packs/ 下,而在 skills/ 下——它是技能而不是知识包,但同样可以被路由表指向。这个细节说明路由表的粒度是「可以独立展开的单元」,而不是「目录树的位置」。此外还有一批可选的远端大库,出于体积考虑没有默认 vendoring,在 REGISTRY.md 的 D 节里列出。
换成我自己的笔记#
要换成自己的知识库,不需要改代码,只需装一个包:
bash ~/.cursor/skills/pickled-fish/scripts/add-pack.sh psych-summaries \
https://github.com/cognitivetech/Psychology-Summaries
它既接受 git 地址,也接受本地目录,装进去的位置是 knowledge/custom/<id>。这个目录默认不进公开 git——因为里面装的往往是自己整理的私有笔记,公开仓库里不该出现它们。装好之后,在 REGISTRY.md 里补一行路由即可。
整个扩展动作的成本就是「一行路由 + 一个目录」,没有重建索引、没有重新向量化、没有等待任务跑完。这也是 Index-first 在个人场景下最实际的一个好处:知识库的维护成本和它的大小成正比,而不是和它的大小成平方。
什么时候仍然该上向量库#
把这套方案说成万能是不诚实的。下面这几种情况,向量检索确实更合适:
- 语料规模真的很大。成千上万份文档,人工结构已经维护不过来。
- 几乎没有稳定术语。同一个概念在文档里有十种写法,关键词命中率会掉到不可用。
- 需要跨语言检索。用中文问题找英文材料,这正是 embedding 的强项。
- 原文本身是非结构化长文本。没有天然的目录层级可供路由。
反过来说,只要满足「规模可控、结构清楚、术语统一、改动频繁」这四条,文件系统索引的性价比就明显更高——而个人知识库通常四条全中。
小结#
Index-first 文件系统 RAG 的四步是 REGISTRY → PACK.md → rg → Read,配套的硬约束是同轮最多深读 2 个 pack,并强制标注 packs_used。它没有用任何新技术,只是把「先看目录、再翻正文」这个人类查资料的习惯,写成了 Agent 可以稳定执行的流程。
值得带走的判断是这一条:检索质量的上限,往往由知识库的结构决定,而不是由检索算法的先进程度决定。先把「意图 → 包 → 文件 → 段落」这条路径修好,再考虑要不要引入向量;顺序反过来的话,你会得到一个很快、但说不清为什么的检索系统。