给 AI 一份外置记忆:我的个人知识库部署全记录
AI 编码助手什么都好,就是记性差:每次新会话都是一张白纸,上个月踩过的坑,这个月能原样再踩一遍。最近我把一套个人知识库跑顺了——Qdrant 负责存和搜,一个开源 MCP 服务器把它封装成 AI 助手随叫随到的检索工具,日常的定稿决策、踩坑结论、可复用配置都以「条目」为单位沉淀进去,随用随查。整套系统不需要 GPU、不需要大内存,任何一台能跑 Docker 的常开设备(NAS、云服务器、本地电脑皆可)都能服役。本文记录架构、选型、部署与运维的全过程,以及那个最不能反悔的决定:embedding 模型。
为什么不记在笔记软件里
先交代动机。我用 AI 助手干活的日常是:排一个下午的坑,最后结论就那么几行——「选 X 不选 Y,因为 A 和 B」。这些结论有价值,但它们的宿命通常是被聊天记录淹没,几周后连我自己都找不着,更别说助手。
笔记软件解决不了这个问题。不是存不进去,而是检出来靠人:你得记得它存在、记得关键词、愿意去翻。而这套知识库的核心用户不是「我」,是「AI 助手」——我在指令里写一条「动手前先查知识库」,它每次会话都会自觉先检索,命中就直接复用结论,不再重蹈覆辙。记忆的价值不在于存,在于「检得到」;当检索者也是 AI 的时候,这件事第一次变得全自动。
形态上还有一个关键取舍:不搞「文档库做 RAG」,而是存「结论条目」。整本文档切碎了喂向量库,是给「读文档」场景设计的;而运维经验天然是短小、主题集中、自包含的条目——一条命中即可直接用,不需要任何「上下文拼装」工程。
为什么不用 AI 助手自带的记忆功能
动手之前,我先问了自己一个问题:各家 coding agent 都在推内置记忆——会话自动总结、项目级 memory 文件,开箱即用,为什么还要自部署一套?
我的答案有四条,每条都关于「通用」:
- 跨 agent 通用。内置记忆绑定在单一工具上:换一个编码助手,记忆就被丢在旧工具的角落里。自部署知识库走 MCP 标准协议,任何支持 MCP 的客户端都能接上同一个库——工具会换代,记忆不搬家。
- 跨环境通用。我的开发环境横跨 Windows 和 WSL,各工具的记忆文件散落在不同环境、不同目录里,互相看不见。知识库是一份集中存储:无论从哪个环境发起会话,连上的都是同一个库、同一份记忆。
- 跨项目通用。内置记忆大多跟着项目目录走,是项目级的;「本机工具链的约定」「跨项目的查证方法论」这类通用经验在里面没有容身之处。我的分法是:默认集合装跨项目的通用知识,领域集合装单个项目的决策与踩坑——分得开,也查得通。毕竟「上个项目的教训」常常正是「这个项目最该防的坑」。
- 召回质量。内置记忆为了开箱即用,检索实现通常从简;自部署则可以把检索质量当成一等公民来经营——自己选更强的 embedding 模型,用长上下文免切片,中文语义召回的差距用过才知道。这也是后文那扇「单行门」的由来:正因为检索质量是自部署的核心红利,embedding 模型的选型才值得郑重其事。
一句话总结:内置记忆属于「那个工具」,自部署的记忆属于「我」——工具、环境、项目都可以换,记忆跟着我走。
架构:两个组件,外加一条纪律
整套系统只有两个服务:
- Qdrant:开源向量数据库,负责存向量、按相似度检索;
- mcp-server-qdrant:Qdrant 官方的 MCP 服务器,往 Qdrant 前面加了一层——内部跑 fastembed 在本机把文本算成向量,对外把「存」「查」封装成两个 MCP 工具(store / find)。
AI 助手(任何支持 MCP 的客户端)通过这两个工具读写知识库,数据流一张图:
我(自然语言)──────────┐
▼
AI 助手(会话中)
│ MCP:store / find
▼
mcp-server-qdrant
└─ fastembed:文本 → 向量(CPU 本地推理)
│ REST
▼
Qdrant(向量 + 条目,持久化)
这里有一个我很坚持的架构决定:服务端不跑任何生成式模型。检索归服务端,生成归客户端——AI 助手本来就是生成端,知识库只做「存取向量」这一件事。好处是服务端的资源需求骤降到家用设备水平:没有推理集群、没有大内存,一个向量库加一个小嵌入模型足矣。
于是「部署在哪」只剩一道送分题,判断标准三条:常年在线(助手随时要查)、能跑 Docker(两个容器的事)、你对数据的隐私预期。NAS 胜在数据在手、内网零延迟;云服务器胜在公网可达、异地可用;本地常开的电脑胜在零成本。三者没有标准答案。我家里正好有一台常年在线的 NAS,能跑 Docker、数据又在手边,三个条件一次凑齐——这个轻量服务就交给它了。这套系统对硬件的唯一要求是「活着」,一台 NAS 的余力绰绰有余;外网访问靠反代套上 HTTPS 和 API key。
配置层面值得抄的就这么几项(mcp-server-qdrant 侧):
QDRANT_URL # Qdrant 服务地址(或 QDRANT_LOCAL_PATH 直连本地目录)
QDRANT_API_KEY # 访问凭据
COLLECTION_NAME # 默认读写的集合
EMBEDDING_MODEL # 嵌入模型(fastembed 模型名,本机 CPU 推理)
传输方式由启动参数决定:默认 stdio,客户端拉起即用;也有 SSE / streamable-http,适合常驻一份、多设备多客户端共享——我选的是后者。凭据只存在于客户端配置和 .env 里,服务端日志与脚本输出对凭据值只字不提,这条后面还会以更严格的形式出现。
最贵的决策:embedding 模型是一扇单行门
如果整套系统只允许我警告一件事,那就是:embedding 模型的选择是一扇单行门。
原理不复杂。向量库里存的每条记录都带着模型的「世界观」,写入和查询必须出自同一个模型,向量空间才对得上;而库里所有存量向量都是当前模型嵌出来的——换模型,等于全库重嵌,等于实际上清库重建。日常使用里你几乎感知不到它的存在,但它的迁移成本是整座系统的地价。
所以第一条红线是:**所有写入路径、以及查询路径,统一同一个模型。**我有两条写入路径(下节讲),每一条都钉死同一个模型名;脚本侧还加了一道维度护栏——集合的向量维度和模型输出对不上就直接拒写,宁可报错也不写脏库。
选型我按三问来:
- 中文效果:嵌入模型的检索质量有公开基准(C-MTEB)可查,选头部而不是网红;
- 长上下文:上下文窗口决定了「一条结论要不要切碎」,8192 token 意味着我的条目永远不用切片——一条完整结论一次嵌入,召回回来就是全文,没有「把碎片拼回去」的工程;
- CPU 开销:嵌入要在服务端常驻跑,模型必须小到无感。
最终落在 jinaai/jina-embeddings-v2-base-zh(768 维,中英双语,8192 token):C-MTEB 约 62 分,对照当时另一个候选 bge-small-zh-v1.5 的 57.82;家用低压 CPU 实测开销可以忽略——单条查询 12–20ms,全库重建约 5 条/秒,常驻内存多 0.7GB。
单行门既然推不开,就得把掉头路修好。换模型的既定流程:
- 全量导出 payload 备份(下节的导出脚本);
- 新模型建一个临时集合,无损灰度;
- 拿一组固定的基线问题在旧库和新库上重放,对比命中质量;
- 数字说话之后,删旧集合、重建、灌回;
- 出正式的基线对比报告,连同回滚备份一起归档。
这套流程里最值钱的道具是基线问题集。它把「感觉新模型好像更好」变成一张可对比的表——感觉会骗人,命中列表不会。
数据模型:正文一层,元数据四件套
每条知识在库里就是两层 payload:
{
"document": "条目正文:自包含,存结论不存过程",
"metadata": {
"project": "来源项目或领域",
"type": "decision | pitfall | howto | methodology | reference",
"date": "2026-09-15",
"source": "出处"
}
}
- 正文一条一主题、自包含——半年后脱离当时的会话上下文也要能看懂,禁止「如上所述」「刚才的方案」;
type五个值各有分工:定稿决策、踩坑与解法、可复用做法、查证方法论、外部资料指针。实践里有个副产品规律:入库时想不清它是什么类型,多半说明它不值得存;- 集合按领域分(一个默认的通用集合,加上边界清晰的领域集合),但要克制:语义检索天然跨主题,集合一多,客户端路由就多一个出错点。集合名也定死、不许随手现造——「顺手建个新集合」是熵增的开始。
一个实现细节顺带交代:mcp-server-qdrant 建集合用的是命名向量(fast-<模型短名>),将来若有第三方工具直连 REST 读写,要对上这个名字。我的脚本会自动探测集合的向量名并校验维度,属于前面那道护栏的一部分。
运维:两条写入路径,一份「去向量」备份
日常路径:会话内沉淀
日常用法是会话驱动的:干完活,我对助手说「把这些沉淀进知识库」,它盘点本次会话的结论级信息,过一遍纪律,逐条入库。纪律比工具重要,我的清单固化成了一个 skill,核心是四条:
- 三问过滤:换个说法还会被问到吗?grep 五秒能找到吗(代码里、文档里一搜就有的,不占库容)?半年后还成立吗(绑死版本号、临时状态的不存)?三问全过才入库;
- 敏感红线:密码、API key、带凭据的连接串,命中即弃——不脱敏、不折衷。凭据轮换之后,脱敏值毫无存储价值;
- 查重前置:MCP 的 store 只有「新增」没有「更新」,重复写入就是永久冗余,所以入库前必须换一种措辞先检索一遍;
- 宁缺毋滥:一条不合格就不硬凑。库的信噪比是生命线——检索命中一堆过时冗余条目,AI 反而被带偏,这比没有知识库更糟。
顺带一个设计取向:MCP 工具面刻意保持极小,就 store 和 find 两个,连元数据过滤都没暴露给 AI(下节)——工具越少,误用面越小。
批量路径:脚本直写
第二条路是一对本地脚本直打 Qdrant 的 REST API。它存在的理由很朴素:不能把数据命运拴在一条通道上——批量迁移、全库重建、MCP 故障时的应急写入,都得有一条不依赖会话的路。
入库脚本的设计要点,全是前文红线的落地:
- 用 fastembed 本地嵌入,模型与服务端同一个;payload 结构与 MCP 服务端完全一致,两条路径的写入可互换;
- 点 ID 用
uuid5(集合名 + 正文)确定性生成:同一份条目 JSON 重复执行是幂等的——Qdrant 按 ID upsert,覆盖而不重复。全库重建因此变成一件没有心理负担的事; - 维度护栏(上文);集合不存在时默认报错退出,确认名字合法后显式
--create才建——防手滑造集合; - 写入后可跟一个
--verify,用与正文措辞不同的问句检索一遍,确认语义召回正常; - 全程不读取、不打印任何凭据值,写完落一份回执 JSON 供追溯。
备份哲学:payload 是资产,向量是衍生品
配套的导出脚本默认只导 payload、不导向量。这是整个系统里我最满意的一个观念修正:
向量是衍生品——模型一换就要全量重算;正文和元数据才是资产。
导出产物因此极小、可读、可 diff,一个集合一个 JSON。常规备份就是定期导出加异机归档;恢复演练也简单到不像话:一份导出 JSON 喂给入库脚本,全库重建完成。备份的价值在演练过的恢复路径上,不在备份文件本身——这句话我在上个月的博客迁移里学过一遍,这次在知识库上又用了一遍。
两个反直觉的决定
一、不开元数据过滤。mcp-server-qdrant 新版本支持把 metadata 字段暴露成过滤参数(FILTERABLE_FIELDS),我配置了但选择不启用,保持纯语义检索:当前量级小,语义检索足够;多暴露一个参数,AI 就多一种误用方式。留了两个重议的触发条件:单集合条目过万,或真的出现「语义 + 条件」的组合查询需求。顺带一个隐蔽的坑记在这里:这个配置只对新建集合自动生效,存量集合要手动走 REST 建索引——配了不等于生效,动手前想清楚。
**二、库里不存「文档」,只存「结论」。**三类内容直接排除:仍在演进的活文档(最多存一条指针);代码仓库里的内容(仓库自己是事实源,存了必过期);会话流水(「本次改了哪几行」是 git log 的事)。过滤掉这些,库里剩下的全是「结论级」条目——存结论不存过程:「选 X 不选 Y,因为 A 和 B」可以入库,「我们先试了 X 又试了 Y」不行。
现在的形态
- 两个集合、每条带四件套元数据,查询毫秒级返回,常驻内存增量 0.7GB,全部跑在那台 NAS 的角落里;
- 日常节奏:干活 → 收尾时助手列出候选结论、附上三问结果建议沉淀 → 我确认 → 蒸馏入库;下次同类问题开场,检索自动命中;
- 每月一次例行导出、异机归档;换模型的掉头路已经完整走过一轮,基线问题集和对比报告归档在案。
写在最后
回头看,这套系统最反直觉的地方在于:技术栈是最不构成门槛的部分。Qdrant + fastembed + MCP 的组合,一个晚上就能跑通,教程遍地都是;真正决定它好用不好用的,是那些毫无技术含量的东西——三问过滤、查重前置、宁缺毋滥。技术栈是公共知识,纪律是私有资产。
另一个感受是「外置记忆」对使用姿势的改变:以前怕忘,什么都往笔记里塞,塞完再也不看;现在敢忘——因为知道结论检得到。存进库里的是结论,省下来的脑子留给判断。
下一步,我打算把入库纪律反过来用在「清库」上:过期的、被推翻的、重复的条目定期回访,让知识库和代码一样有 review 流程。到时又是一篇。