资讯动态

用LLM打造个人知识库:从RAG到本地维基的实践

发布时间:2026/9/14 7:21:14 来源:尧图企业网站定制
老话讲好记性不如烂笔头。可我这人是反过来的烂笔头记了一堆真到用的时候脑子照样一片空白。五年下来我的笔记散落在 Markdown 文件、网页剪藏、PDF 批注、还有各种临时写在手机便签里的只言片语中。每次想找点什么都要在十几个文件夹里翻来翻去最后经常放弃重新搜索一遍互联网。这种挫败感攒到今年年初我决定换个思路既然记笔记的核心障碍是整理和检索与其手动给每篇笔记打标签、建目录、做双链不如让大语言模型LLM接盘。这就是 llm_wiki 这个项目的由来。一句话说清楚它是个什么东西llm_wiki 是一个“LLM 替你管理知识库”的个人本地维基系统。它把你丢进来的文档自动清洗、切片、生成摘要、打上标签、建立语义关联然后给你提供一个能聊天的检索入口——你可以像问一个熟悉你全部笔记的人那样问它去年那篇讲某个算法收敛性的文章里提到的边界条件到底是什么它不仅能告诉你答案还会引用原文片段、标注出处。项目本身支持本地优先部署、模型无关可以对接 OpenAI 兼容接口也可以跑本地 Ollama 模型。这中间最折腾的不是调 API 或者写检索代码而是内容结构化那一层——怎么让模型稳定地输出能用的知识元数据以及怎么让检索结果在找得到和答得准之间取得平衡。这篇文章把我从规划到跑通、再到日常使用了三个月的完整经验写出来包括每个环节为什么这么设计、哪些地方容易翻车、以及可以直接抄走的配置方案。如果你也攒了一堆笔记但根本不想整理或者你已经在用 RAG 做问答但总被垃圾召回困扰这篇文章应该能帮你省掉不少弯路。1. 为什么是 LLM Wiki 而不是再试试新笔记软件先聊点真实的痛点。市面上的笔记软件我几乎轮了个遍印象笔记、Notion、Obsidian、思源笔记都用过半年以上。它们解决的是记录和双向链接的问题但对我来说最耗时的其实是三个环节整理归类、内容关联、以及想不起来关键词时的模糊检索。这三个环节恰恰是规则和人工最不擅长、而 LLM 最擅长的事。最初的灵感其实来自一个很简单的反推如果只是把文档存进去然后按关键词搜索那我直接用 grep 都行为什么还需要一套系统我需要的是——丢一篇很长的技术报告进去模型能告诉我这篇报告的主旨、涉及哪些技术概念、和我笔记里哪几篇旧内容存在关系。这显然超出了传统标签体系的表达能力但又在 LLM 的能力范围内。wiki 这种组织形式在这里帮了大忙。它不像传统笔记强调文件夹层级而是强调词条和页面之间的链接。llm_wiki 沿用了这个思路每篇文档都被视为一个 page但链接关系不再由我手动维护而是由模型在导入时自动生成。指向关系来自摘要中的关键实体、语义相似度计算、以及用户后续提问时产生的关联反馈。也就是说wiki 结构是 LLM 基于内容自发生长出来的不是我预先规划好的。这套设计还有个额外的红利懒人友好。以前我写笔记要想着这里该打个标签那里该建个链接现在完全不用。我只需要把资料丢进去系统自己处理。从一个使用者的角度看llm_wiki 给我的感觉更像一个图书管理员而不是另一个笔记软件。2. llm_wiki 的整体方案本地优先、模型无关、双通道召回2.1 架构总览四条主链路整个系统可以拆成四条主链路理解这四条链路就能明白所有后续细节导入链路接收 Markdown、纯文本、PDF、网页 HTML 等多种输入做清洗、格式化、分割成块。结构化链路每个分块过一遍 LLM生成摘要、关键词、实体列表、以及该块与已有页面的潜在关联。存储链路原始分块和结构化元数据写入 SQLite 作为主存储同时把分块向量化后写入本地向量数据库。检索与问答链路用户提问后先做关键词检索BM25再做向量检索Embedding 相似度融合结果后拼接提示词交给 LLM 回答并附带可追溯的引用来源。这四条链路不是串行而是两条异步、两条同步导入和结构化是异步的通常丢进去一批文档后由后台队列处理检索和问答是同步的用户提问必须在秒级内返回。异步处理要用消息队列吗没有我用的是 SQLite 做任务表 一个简单的 Worker 轮询本地单用户场景完全够用。别一上来就上 Kafka没有那个必要。2.2 为什么我坚持本地优先本地优先带来的最大好处是隐私可控。笔记里有太多不适合上云的内容比如个人健康记录、工作中的内部资料、还有各种未成形的想法。这些内容如果通过 API 发给第三方模型心里总是不踏实。而 llm_wiki 的默认配置是Embedding 模型本地跑LLM 可选本地或远程。哪怕全选远程 API也支持在发送前做脱敏处理。这个即使上云也可脱敏的设计很重要它让我在后续想接入更强大模型的时侯多一个选择。另一个原因是离线可用。我有过在地铁上突然想查一个旧笔记却因为没网而只能干瞪眼的经历。本地部署之后只要笔记本上跑着一个小模型哪怕只有 7B 参数基本的摘录、检索、问答全都能做。这种可靠性是云端笔记软件给不了的。2.3 模型无关的抽象层设计模型无关是 llm_wiki 最早确定的设计原则。今天可能觉得 OpenAI 的 API 很好用明天可能出了个更好的开源模型如果代码里写死了调用方式换模型就会很痛苦。所以我定义了一个统一的CompletionProvider接口chat(messages, options) - responseembed(texts) - vectors然后分别对接了 OpenAI 兼容的 HTTP API、Ollama 本地模型、以及一个用于快速测试的 Mock 模型。Mock 模型是我自己写的专门返回固定结构的内容用于跑通流程时不做真正的模型调用。接入新模型只需要实现这个接口完全不用动核心逻辑。这个抽象层同时也管住了成本。我可以在全局配置哪些操作走小模型哪些操作走大模型比如摘要和实体提取用本地 7B 模型而最终回答用户问题用更强的云端模型。通过这种分级调度日常费用被压到了很低——大部分时候接近零成本。2.4 双通道召回不要让向量模型单打独斗接下来是整个系统里我认为最容易被人忽略、但恰恰最影响体验的一环召回。很多入门教程会让你把所有文档切片后扔进向量数据库然后提问时直接做相似度搜索。这种方案在文档量少、主题单一的时候好像不错但我拿真实笔记测下来的结果很一般——向量检索对明确含义、术语一致的提问表现好对关键词很具体但不带语义的提问反而不如传统关键词搜索。比如我想找之前记的SQLite WAL 模式参数如果只做向量检索模型可能把我带偏到别的数据库事务相关的内容上。但结合 BM25 全文检索SQLite 和 WAL 这两个词一出精确命中的排序自然就上来了。所以 llm_wiki 采用了双通道召回BM25 结果和向量结果各取 Top N然后用 RRFReciprocal Rank Fusion算法合并排序。这套方案在实际效果上比单纯向量检索的召回准确率高出一大截而且实现成本并不高大概一百来行代码。两个通道的召回结果合并后还要根据文档的重要程度比如被引用次数、访问频率做一次轻量重排再截取 Top K 作为最终上下文。整个链路我都记录日志了方便出问题时回溯到底当时召回的是什么内容。3. 核心模块落地从文件进来到答案出去3.1 导入与清洗垃圾进垃圾出llm_wiki 的导入模块是我最先写的因为后面所有环节都依赖干净文本。这里说的清洗并不是简单地把 HTML 标签去掉而是要处理一堆现实世界的脏数据网页剪藏里的广告、导航栏、页脚PDF 里因为双栏排版导致的文本顺序错乱Markdown 里的 Mermaid 代码块LLM 可读但发送成本高一些临时笔记里包含的乱码和 emoji 变体。清洗策略并没有用复杂的机器学习模型而是规则先行LLM 兜底。预先用训练好的提取模板处理掉 80% 的常见垃圾剩下的、比如 PDF 排版错乱导致语义不通的段落才丢给 LLM 做一次重写为流畅的 Markdown。这种做法的好处是很显然的——快且便宜。清洗后的文本进入切分环节。切分是我踩坑最多的地方后面我会专门花一节来说。这里先记住一个原则不要按固定 token 数硬切最好按段落的语义边界切同时让相邻块之间保留重叠部分。llm_wiki 默认配置是每块 600 token重叠 80 token。这样既能保证每块内容相对完整又不会让上下文信息在切分处断裂。3.2 结构化让模型产出稳定的元数据每一快被清洗好的文档分块在入库之前都需要生成一组元数据。这部分我设计了一个固定的 Prompt 模板要求模型以 JSON 格式返回{ summary: 两到三句话的概要保留关键数字和结论, keywords: [词1, 词2, ...], entities: [人名/机构名/技术名词/产品名], relations: [ {target_title: 已有的页面标题, relation_type: supports/conflicts/extends} ] }这里有个非常关键的细节relations 里的 target_title 必须是从已有页面标题集合中检索出来的不能由模型凭空造句。最开始我尝试让模型自由填写关联结果它总能编出一些和现有内容长得像但其实不存在的页面名导致知识网络里全是断链。后来我改了一个思路在 Prompt 中加入当前已有页面标题列表比如取前 100 个最相关的标题要求模型只从里面挑剩下的关联宁可不要。这一改断链率大幅降低。元数据写回 SQLite 之后原始分块会做一次向量化和元数据一起存入向量库。向量化的时机也需要注意比起每写一条就嵌入一次我更推荐批处理每攒够 32 个分块或者隔 30 秒嵌一次。这样能减少模型调用次数对本地嵌入服务也更友好。3.3 语义检索与重排召回不是终点排好序才算前面说了双通道召回这里再展开讲讲合并后的重排逻辑。RRF 合并公式非常简单score(doc) Σ 1 / (k rank_i(doc))其中k是一个常量通常取 60。rank_i是文档在第 i 个检索通道中的排名。这个公式的思路是某篇文档如果在多个通道里的排名都比较靠前那它的综合分就高。和加权求和不同RRF 不要求两个通道的分数在同一量纲下所以实现起来非常省心。重排阶段我加了一个可选的 LLM Reranker 模块把召回到的 Top 15 文档分块和用户提问一起交给模型让它按相关度从高到低排序并返回前 5 个作为最终上下文。这个重排器很贵所以默认是关闭的。只有在用户提问比较长、而且双通道召回结果里文档太多的时候才自动开启。实际体验下来它对最终答案质量的提升非常明显但也会增加 3~5 秒的耗时。可以把开关放在前端界面上让用户自己权衡。3.4 问答告诉模型你不知道也没关系问答链路是用户直接感知的部分。llm_wiki 的问答模块除了拼上下文外还额外做了三件事限定范围在系统提示词里明确说你是一个个人知识库助手只能基于提供的内容回答。如果内容中找不到答案请回答未知不要臆造。引用溯源要求模型在回答中标注引用编号[1]、[2]和最终展示的参考来源一一对应。追问建议在回答末尾附加三个与当前问题相关的后续提问建议这些建议是 LLM 根据召回结果生成的帮助用户继续深挖。这样做下来用户得到的就不是一段干巴巴的答案而是答案 证据 延展路径。我自己用了这么久默认最常用的功能反而是那个后续追问建议——很多我自己没想到的关联都是它提示出来的。4. 最容易翻车的三个环节踩坑记录与排查链路4.1 向量切分固定长度切出来的全是废话四个字总结我最初的切分策略惨不忍睹。我一开始图省事直接把文档按 512 个 token 等长切块。测试的时候问张三是哪个团队的结果 LLM 回答了一堆不相关的内容。排差了一圈最后定位在切分上——张三的名字出现在第一块末尾而他是 A 团队负责人出现在第二块开头中间正好被切开。向量化之后两块的内容都很稀疏都没有完整表达张三是 A 团队负责人这个信息。解决思路也不复杂切分时优先选段落边界如果一个段落太长再在里面找二级标题、句子边界下刀。我用的是按 Markdown 标题和空行分段的逻辑结合一个递归切分函数如果当前文本长度在[min_tokens, max_tokens]内直接作为一个块如果太长先尝试按##或###标题切再不行就按双换行切最后才按句子结束符句号、问号、感叹号切。相邻块保留 1~2 句话的重叠。这套规则之后召回的命中率提升得很明显。这个小细节值得每个做 RAG 的人认真对待。4.2 结构化 Prompt既要稳定输出又不要上下文爆仓第一次让 LLM 生成元数据时我给的 Prompt 是个自由发挥题请分析以下内容并返回摘要、关键词、实体、关联。模型确实按格式写了但输出特别不稳定有时候关键词有 20 个有时候只有 1 个关系列表里还出现了根本不在已有页面标题列表里的标题而且摘要风格来回横跳。后来我把 Prompt 改成了任务说明 输出 Schema 示例 注意事项四段式并且在 Schema 里严格限定了枚举值和数量范围关键词3~8 个名词短语不要包含标点 实体尽量用人名/机构名/技术名词最多 5 个 relations: 只从给定标题列表中选择如果无法确定就输出空数组 summary: 最多 60 字同时我在解析 JSON 的时候做了容错即使模型输出里带点杂七杂八的文本也能提取出合法 JSON。这个extract_json函数我实测很有效——它先用正则找到第一对{和最后一个}然后直接json.loads如果失败就把字符串交给一个修错 Prompt 再跑一次。顺便一提所有元数据生成之后我都存了原始输出字段方便以后模型升级后重新生成时做对照。4.3 引用溯源模型记错了出处比不引用更坑引用是 llm_wiki 用户感知最强的功能之一但实现它让我发现了 LLM 的另一个毛病它会在引用编号上撒谎。明明给它了 5 条参考内容它却回答 [6] [7] 这样的编号有时它引用的内容确实存在于某一块里但对答案的支撑关系很弱。这个问题的根源在于引用和生成文本是在同一个过程里完成的模型无法严格保证自己为某句话分配的编号对应的原始块确实支持这句话。我的解决办法是引入事后验证从模型回答中提取所有引用编号把这些编号对应的原文块调出来计算原始块与包含引用的句子的余弦相似度如果相似度低于阈值比如 0.35删除该引用编号并在界面上打上弱相关标记。实测下来这一步能过滤掉大量幻觉引用。虽然后处理会多耗 100ms 上下因为要重新走一遍嵌入但这个成本在可信度面前不值一提。5. 从部署到日常使用可以直接抄的配置方案5.1 环境依赖清单llm_wiki 本身是 Python 写的依赖并不多核心就这几个fastapi提供 API 服务sqlite-vec或chromadb向量存储我个人偏好sqlite-vec因为它和 SQLite 主存储可以共用一套备份机制httpx调用模型 APIbeautifulsoup4和pypdf处理网页和 PDFpython-frontmatter解析 Markdown 元数据部署时我用 Docker Compose 起两个容器一个是应用本身另一个是 Ollama 服务跑本地 Embedding 和 LLM。如果只想用云端 API比如 OpenAI那只需要一个应用容器就够了配置里填好 API Key 和 Base URL 即可。5.2 核心配置项配置文件采用 YAML 格式下面是精简版llm: provider: ollama # 可选: openai / ollama / mock chat_model: qwen2.5:14b embedding_model: bge-m3 base_url: http://localhost:11434 storage: sqlite_path: ./data/llm_wiki.db vector_table: vec_chunks chunk_size: 600 chunk_overlap: 80 retrieval: bm25_weight: 1.0 vector_weight: 1.0 rrf_k: 60 top_k: 15 final_top_k: 5 enable_llm_rerank: false pipeline: batch_size: 32 worker_interval_seconds: 30这几个参数里chunk_size和top_k对最终效果影响最大。我自己的经验是内容偏向长文本和深度技术文档时chunk_size调到 800 更好偏向碎片化笔记时400 更合适。没有一劳永逸的参数最好做一个后台盲测对比每天随机抽几个问题在两组参数下分别运行看哪组召回准确率高然后定期调整。5.3 典型使用流程日常使用我基本是这样操作的把网页正文复制到剪贴板用浏览器插件或快捷键调起一个本地脚本往 llm_wiki 的/api/import接口 POST 一段文本系统自动清洗、切分、生成元数据并入库过 30 秒左右新内容可以在最近更新里看到需要找内容时我在网页端的输入框直接问问题系统返回带引用的回答同时右侧栏显示相关页面列表。这套流程真正做到了导入即忘。以前记笔记是需要刻意去做的事现在反而是看到了好内容就往里丢丢完就再也不看了需要时通过问话来取。6. 模型选择与成本控制低配电脑也能玩6.1 不同模型的定位我把模型分成三档用途推荐模型举例参数量级备注本地摘要/实体提取Qwen2.5、Llama 3.17B~14B要求中文和英文都要稳本地 EmbeddingBGE-M3、GTE-Qwen300M~1B关键指标是检索命中率云端强推理问答GPT-4o、Claude Sonnet-回答复杂综合问题时效果好本地模型选型时我重点看的是长上下文下的稳定性和JSON 输出能力。很多 7B 模型单独写摘要没问题但让它输出一段严格 JSON 时常会少括号或者多字段。如果发现这类情况可以在 Prompt 里加 few-shot 示例但更省事的方法是直接换一个经过指令微调的模型比如 Qwen2.5 Instruct。6.2 成本与延迟实测我用的是一台 CPU-only 的老笔记本8 核 16 线程用 Ollama 跑 7B 模型做摘要速度大概是 10 token/s处理一个 600 token 的分块大概需要 60 秒左右。批量导入一篇 5000 字的文档大约要等 8~10 分钟。这个速度能接受但如果你经常导入大量文档还是建议搞一块 8GB 显存的显卡速度能提升 20 倍以上。云端 API 的价格其实也不贵。我在完全可用云端模型跑摘要的情况下1000 篇短笔记大概消耗 150 万 token按当前主流 API 价格算约 6 块钱人民币。最贵的是开启 LLM Reranker 之后因为它每次都对 15 块内容做一次排序一次问答可能额外烧掉 3000 token。所以我把 Reranker 的默认开关设成了 false只在语义复杂、答案分散的场景下才手动开。6.3 一个省钱的混合调度技巧我在配置里加了一个规则question_len 100才启用云端模型否则用本地模型。这么做的好处是日常简单问答几乎零成本只有拿它当正经知识库研究复杂问题时才花几分钱。对于本地跑不动的更大模型我还会用 Streaming 输出让首 token 尽快出现体感上快很多。7. 后续还能玩的方向知识图谱、自动维护、多人协同llm_wiki 跑通到现在已经三个月日常使用的核心功能都稳定了我开始琢磨几个能把它推向更深处方向第一个是知识图谱可视化。现在模型已经生成了实体和关系但它们目前只存在 SQLite 里没有一张可视化网图。我打算在网页端增加一个力导向图把文档作为节点、实体关系作为边点击任意节点能看到关联文档。这能让用户在浏览时发现意外的知识连接。对这个系统来说本质上是把已有的关系数据再加工一下。第二个是自动维护与旧闻感知。很多维基系统都有陈旧页面问题——当某个技术名词的定义在新文档里发生了变化旧文档里的相关段落却还留在原来的语义里。llm_wiki 可以做一个内容差异检测模块当新文档的实体与旧文档重叠度较高时抽出新旧摘要中的关键结论提醒用户在旧文档中做一次校对更新。第三个方向是多人协同。目前系统是单用户模式所有文档共享一个向量空间。如果后续让一个团队使用就需要加入权限控制、命名空间隔离、以及共享知识库之间的互相引用。这部分工作量大但思路是清楚的把当前page表加上owner和team_id字段向量检索时多一个scope过滤条件就行。另外还想提一个使用层面的想法不要只把它当知识库也可以把它当项目复盘库。我每完成一个小项目就把项目相关的零散笔记、聊天记录、数据结果全丢进去过几天再问一问这个项目有哪些风险被我忽略了。LLM 给出的回答有时会从意想不到的角度串起线索——这个用法反而成了我目前最喜欢的功能。8. 写在最后的一个小技巧最后贡献一条我踩过不少坑才总结出来的技巧无论你的模型和检索参数调得多好都不要省略原文存档这层。llm_wiki 在存储向量块时永远会同时保存这个块在原始文档中的起始位置、结束位置和原始 Markdown 源码。导出引用时系统给出的不是模型生成的一句话而是原文里真实存在的那一段。这一点在认真做知识管理的人眼里比任何花哨的 AI 功能都重要——因为可验证性才是知识库长期可信的基石。如果你也想搭一个类似的系统我的建议是别急着模仿我整套设计先从把文档导入后让模型自动打标签和做摘要这个最小功能开始跑通一条最简单链路之后再逐步加上向量检索、双通道召回、引用验证这些进阶模块。每一步增加的复杂度都应该以你实际使用中的痛点为依据否则很容易掉进为造轮子而造轮子的坑里。llm_wiki 目前的代码我还放在自己 Git 仓库里维护等再稳定一些我会整理成开源项目。短期内我更想专注把知识图谱可视化和陈旧页面检测这两个功能做扎实。这几个方向上的探索后续我会继续更新在这个系列里。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价