读完文章转眼就忘,笔记散落各处找不到——这是大多数程序员的痛点。本文记录我如何用本地大模型+向量数据库搭建个人知识库,三个月后检索效率提升明显,信息焦虑也缓解了不少。
我受够了“松鼠症”式的学习
过去五年,我收藏了 3000 多篇文章,记了 2000 多条笔记,散落在各种云笔记、浏览器书签、甚至微信“文件传输助手”里。每次想找一个半年前看过的技术方案,翻箱倒柜十几分钟,最后往往还是重新 Google。
更让我焦虑的是,明明花了很多时间学习,但被问到“你了解 XXX 吗”的时候,脑子里只有模糊的印象,说不清原理,更别提落地经验。这种感觉就像往一个漏水的桶里灌水——灌得越多,挫败感越强。
今年初,我决定用 AI 工具给自己搭一个真正的“第二大脑”。三个月的打磨迭代,现在这个系统已经成了我日常开发中最依赖的工具。这篇文章是完整的搭建记录,包含工具选型、配置步骤、踩坑复盘,以及心态上的变化。
整体架构:所有数据留在本地,AI 只做理解和检索
我的核心诉求有两个:数据隐私(笔记里可能有公司项目的代码片段)和检索精准(不能比 Ctrl+F 更差)。
最终选型:
| 组件 | 选型 | 理由 |
|---|---|---|
| 笔记仓库 | Obsidian(本地 Markdown) | 纯文本,Git 版本控制,无平台锁定 |
| 嵌入模型 | BGE-M3(通过 Ollama 本地运行) | 中文+代码混合场景表现好,免费 |
| 向量数据库 | Chroma | Python 一键启动,零配置 |
| 检索与对话 | 本地脚本 + Claude Sonnet 4 API | 只传检索到的摘要,原文不出本地 |
| 前端 | 命令行 REPL | 程序员不需要花里胡哨的界面 |
为什么不直接用 ChatGPT 或 Claude 的 Projects 功能?因为它们会存储我的数据到云端。而本地嵌入+云端推理的组合,既能保护隐私,又能用上最强的模型——检索出来的只是文本片段摘要,不包含完整原文。
搭建步骤:分四步,每一步都有坑
第一步:笔记清洗与分块
Obsidian 仓库里 2000 多个 .md 文件,混杂着技术笔记、读书摘抄、个人碎碎念、甚至购物清单。直接全部灌进向量库,检索质量会一塌糊涂。
我的处理脚本(Python 核心逻辑):
import os
import re
from pathlib import Path
def clean_and_chunk(md_content: str, file_path: str) -> list[dict]:
"""清洗 Markdown 并按标题分块"""
# 去除图片链接
content = re.sub(r'!\[.*?\]\(.*?\)', '', md_content)
# 去除 HTML 标签
content = re.sub(r'<[^>]+>', '', content)
# 去除 front matter
content = re.sub(r'^---.*?---', '', content, flags=re.DOTALL)
chunks = []
# 按 ## 标题拆分
sections = re.split(r'\n(?=## )', content)
for i, section in enumerate(sections):
# 跳过太短的片段
if len(section) < 100:
continue
# 生成唯一 ID
chunk_id = f"{file_path}#chunk-{i}"
chunks.append({
"id": chunk_id,
"content": section.strip(),
"source": file_path
})
return chunks
踩的坑:一开始用固定 500 字符切分,把大量代码块拦腰截断,导致嵌入质量极差。后来改为按标题切分,代码块保持完整,检索准度立刻上了一个台阶。
第二步:生成嵌入并存入 Chroma
BGE-M3 模型通过 Ollama 本地运行,一张 RTX 4060 就够用。
import chromadb
import ollama
# 初始化 Chroma
client = chromadb.PersistentClient(path="./my-knowledge-base")
collection = client.get_or_create_collection("tech-notes")
def embed_and_store(chunks: list[dict]):
"""批量生成嵌入并存入 Chroma"""
for chunk in chunks:
# 调用 Ollama 本地生成嵌入
response = ollama.embeddings(
model="bge-m3",
prompt=chunk["content"]
)
embedding = response["embedding"]
# 存入 Chroma
collection.add(
ids=[chunk["id"]],
embeddings=[embedding],
metadatas=[{"source": chunk["source"], "content": chunk["content"]}],
documents=[chunk["content"]]
)
2000 多个文本块,嵌入生成大约跑了 15 分钟。之后就可以用来检索了。
第三步:构建检索+重排序流水线
直接用向量相似度召回 Top 10,再交给 Claude 重排序,挑出最相关的 3 条。
def search(query: str, top_k: int = 3) -> list[dict]:
"""检索+重排序"""
# 1. 向量检索
query_emb = ollama.embeddings(model="bge-m3", prompt=query)["embedding"]
results = collection.query(
query_embeddings=[query_emb],
n_results=10
)
# 2. 用 Claude 重排序
candidates = results["metadatas"][0]
rerank_prompt = f"""从以下 10 个片段中,选出与问题最相关且能回答的 3 个。
只返回选中的片段 ID,逗号分隔,不要解释。
问题:{query}
片段列表:
"""
for i, c in enumerate(candidates):
rerank_prompt += f"\n[{i}] 来源: {c['source']}\n内容: {c['content'][:300]}...\n"
# 调用 Claude(省略具体 API 代码,参考前文)
selected_ids = call_claude(rerank_prompt)
# 3. 返回最终结果
final_results = []
for idx in selected_ids:
meta = candidates[idx]
final_results.append({
"source": meta["source"],
"content": meta["content"]
})
return final_results
为什么加重排序? 纯向量检索对“语义相似但答非所问”的内容误召回率很高。加上 Claude 重排序后,命中率提升了大约 35%,而且每次重排序消耗不到 0.01 美元。
第四步:搭一个命令行对话界面
我用 Python 的 argparse + rich 库写了个简单的终端 REPL,支持:
search <关键词>:检索笔记ask <问题>:基于检索结果生成回答recent:查看最近添加的笔记stats:查看知识库统计
效果:
> ask Go context 超时控制的最佳实践
📖 基于你的笔记,找到 3 条相关内容:
1. [Go/并发/context包避坑.md]
context.WithTimeout 的坑:如果父 context 已经超时,
子 context 会立即收到取消信号。建议在 RPC 调用时
单独设置 timeout,不要复用上层 context 的 deadline。
2. [踩坑记录/2025-06-微服务超时排查.md]
那次支付回调丢失的原因:上游设置了 3s 超时,但我们
的签名验证需要 2.8s,网络抖动直接超时。后来把验证
改为异步,先回 200 再处理。
3. [Go/并发/信号处理.md]
优雅关闭时的超时处理:用 select + time.After 设置
最大等待时间,配合 sync.WaitGroup 等待协程退出。
💡 建议:根据你 2025 年 6 月踩过的坑,关键点是"不要复用
上层 context 的 deadline,为每个外部调用单独设置 timeout"。
这一刻,我觉得所有搭建成本都值了。 那些曾经花费数小时排查后写下的笔记,不再躺在文件夹里吃灰,而是在我再次面临同样问题时主动跳出来。
三个月后的真实变化
数据层面
| 指标 | 搭建前 | 搭建后 |
|---|---|---|
| 找到目标笔记的时间 | 平均 8 分钟 | 平均 15 秒 |
| 笔记复用率 | <5%(几乎不复习) | >40%(被动触达) |
| 写技术文章时引用旧笔记 | 靠记忆翻找 | 直接检索引用 |
| 碎片信息丢失率 | 高(忘了就真忘了) | 低(只要记过就能找到) |
心理层面
比数据更重要的是心理状态的变化。
以前看到一篇好文章,我会焦虑:“又要花时间看完,看完也记不住,不看完又觉得亏了。” 现在我的心态变成了:“看过、标注过、索引过,就已经是我的了。需要的时候能找回来就行。”
知识库的意义不是让你记住一切,而是让你放心地忘记。 当你知道任何曾经学过的内容都能在 15 秒内找回时,信息焦虑会自然消退,学习也从“囤积”回归到了“理解”。
几个你可能需要的提醒
1. 不用追求全自动
我一开始试图写个文件监控脚本,自动增量索引。结果 Obsidian 的自动保存和频繁切换文件,产生了大量脏数据。手动触发索引,每次只索引加了 #published 标签的笔记,效果反而更好。
2. 代码块保留原样,不要摘要化
嵌入模型对代码的理解已经很强了,不要在索引前把代码“翻译”成自然语言——那样反而丢失细节。BGE-M3 对代码段的语义理解足够支撑检索。
3. 先跑通 MVP,再考虑优化
先用命令行玩起来,不要一上来就搭 Web UI。程序员用命令行的效率远高于点鼠标。等确定这套流程真的适合你,再考虑把它做成浏览器插件或 VS Code 扩展。
4. 这不能替代“理解”,只能辅助“检索”
AI 知识库帮你找到碎片,但真正的理解仍然需要你自己花时间去消化。它是外挂,不是替代品。把检索当成“快速回忆起自己曾经学过的内容”,而不是“AI 帮你学习新东西”。
可以抄的作业
如果你也想搭一套,最小启动成本是:
- 装好 Ollama,拉取
bge-m3模型 pip install chromadb ollama rich- 写一个不到 200 行的 Python 脚本(核心逻辑都在这篇文章里了)
- 选一个周末的下午,把最有价值的笔记跑一遍索引
- 在终端里打出第一句
ask命令
然后,你会发现自己再也回不到靠文件夹和文件名搜索笔记的日子。
你们有没有搭建过自己的知识库?用的什么方案?或者有哪些笔记管理的独门技巧?欢迎评论区讨论。