
大概从去年年底开始我明显感觉到自己的笔记库已经失控了。收集的资料、看过的论文、随手记的 idea散落在十几个文件夹里文件名还经常是“新建文档(5).md”。最气人的是有时候明明记得自己写过某个技术踩坑记录真到用的时候翻了半小时也找不到。那时候我就动了念头能不能给自己做一个自带“对话能力”的 wiki不是那种只能翻目录的静态知识库而是能让它像人一样你问它问题它把相关文档找出来、顺带解释给你听。这就是我这个 llm_wiki 项目的起点。我花了大半个月的时间把散落的笔记整理成了结构化的 wiki 知识库然后接入了大语言模型LLM搭了一套检索增强生成RAG的问答系统。现在无论是复盘项目、查回忆还是让助手帮我从笔记里提炼观点都变得非常顺滑。这篇博文就把我踩过的坑、选型的思考、以及可以直接抄作业的配置细节整理出来希望能帮到同样在折腾个人知识库的人。要说明的是这并不是一个“开箱即用”的 SaaS 教程更多是一个自建方案从零到一的完整记录。你不需要很厉害的技术背景但要愿意折腾一点点代码和配置。如果你恰好对 Obsidian、Dify、本地模型、向量检索这些词感兴趣那这篇文章应该对你有用。1. 项目定位与整体设计思路1.1 llm_wiki 到底是什么解决什么问题先说清楚这项目不是什么。llm_wiki 不是一个“把文档扔进去就能自动回答一切”的黑盒也不是一个简单的“问答机器人”。它本质上是一个带语义理解能力的个人知识库系统——底层仍然是 wiki 的页面、链接、标签体系但在上层接入了一个“能读文档的大模型层”让你可以通过自然语言提问直接命中笔记库里的相关内容并且获得有上下文、有出处的回答。我在实际使用中最痛的一个场景是某天想查“当时我调研过几种 embedding 模型的效果比较”如果按传统方式我得记住大概在哪个月份的笔记、当时用了什么关键词才能翻到。但现在我只需要在 llm_wiki 里问“我对比过哪些 embedding 模型结论是什么”系统会从几十篇相关笔记中召回最相关的片段再把答案组织给我。这一步的体验提升是颠覆性的——知识库不再是被动的存储而是主动的助理。当然LLM 接进来之后问题也多了。最大的风险是“幻觉”和“检索不准”模型可能信誓旦旦地编一个根本不存在的结论。所以这个项目的核心并不是“接入”而是设计一套机制来约束模型、增强检索质量。这也是为什么我把项目命名为 wiki 而不是 assistant——因为知识库本身的结构、内容质量才是整个系统稳定性的地基。1.2 为什么选择 wiki 形态而不是直接搞问答在刚开始构思时我也犹豫过是不是直接把所有文档扔给向量库搞个“chat with your documents”就完事了后来发现不行。纯问答式的文件堆叠有几个硬伤文件之间没有关联提问时召回的是碎片上下文零散答案质量不稳定没有内容结构更新和复盘时无从下手跨文档的逻辑关系比如“这个方案是在那篇分析的基础上做的”完全丢失团队协作时纯问答系统很难体现“谁负责哪块知识”。wiki 的页面、双链和目录结构恰好补足了这些。每一篇笔记不只是独立的知识点它通过链接与标签形成一个网络。当 LLM 检索时不仅可以命中单个片段还能顺着链接找到关联页面这样回答就有了更完整的上下文。所以在设计时我确定了一条原则先有 wiki后有 LLMLLM 是增强层而不是替代层。也就是说内容本身必须保持清晰的结构和可读性LLM 只是让我们访问这些内容时更方便。这个顺序一旦倒过来整个系统就会变成建立在垃圾数据上的空中楼阁。1.3 技术选型本地模型还是 API知识库框架怎么挑选型是这种项目最耗时的一环。我对比了多条路线方案优点缺点适合场景纯 APIOpenAI/国产大模型向量库效果稳定无需关注显卡有成本数据出网敏感些个人/团队追求开箱即用本地模型Ollama/vLLM向量库数据可控无单次调用成本需要较好硬件效果调优成本高隐私敏感或长期大量使用现成知识库应用Dify/Quivr/RAGFlow界面友好配置方便定制性不足依赖项目维护快速验证场景自写全套 pipeline完全可控能深度定制开发维护成本高有工程能力深度集成我最终选择的是“私有化部署 混合路线”知识库管理和展示层用 Obsidian 自建索引LLM 底层优先用本地模型跑日常问答因为数据都在本地更安心需要更强推理能力时再调用云端 API 做互补。框架层用 Dify 做应用编排减少重复造轮子。这里给个经验新手不要一上来就造轮子先用 Dify、FastGPT 这类现成工具把 RAG 流程跑通理解“知识库—召回—重排—生成”每一环的作用然后再决定要不要自己写代码。我一开始就是手写类似 LangChain 的脚本结果光处理各种文本格式和编码问题就花了一周实际产出却很可怜。2. 核心知识底座wiki 的搭建与内容组织2.1 用 Obsidian 搭 wiki从文件夹思维到链接思维以前我记笔记的方式很原始按年度建文件夹里面再按主题建子文件夹。这种方式的弊端在知识量变大之后特别明显——一个知识点可能横跨多个主题但只能被放进其中一个文件夹另外的引用入口就断了。Obsidian 让我最大的转变是“链接大于文件夹”。现在我建任何笔记时不会问“放哪个文件夹”而是问“这个页面应该从哪些已有页面链接过来”。比如我写一篇关于“RAG 中的 chunking 策略”的笔记它会同时被“RAG 系统设计”、“文本预处理”、“embedding 模型选型”三个页面链接到。这样一来LLM 在召回“embedding”相关问题时也能顺藤摸瓜找到 chunking 笔记即使正文里没有大量重复关键词。我的 wiki 目录结构简化下来大概是这样的wiki/ ├── moc/ │ ├── LLM 大语言模型.md │ ├── RAG 检索增强生成.md │ ├── 个人知识管理.md ├── notes/ │ ├── llm/ │ │ ├── transformer 结构笔记.md │ │ ├── 主流开源模型对比.md │ ├── rag/ │ │ ├── chunking 策略实践.md │ │ ├── embedding 模型选型记录.md ├── assets/ ├── templates/ │ ├── 技术笔记模板.md │ ├── 项目复盘模板.md在这个结构里moc是“Maps of Content”相当于知识地图notes是按主题划分的内容区templates是固定格式的笔记模板。文件夹只负责粗粒度归类真正的内容关联靠的是双链[[]]。2.2 wiki 内容的结构化模板、标签和状态标记如果说双链是 wiki 的骨架那内容的元信息就是血肉。LLM 本身理解自然语言但如果你想让它返回更准确、更结构化的信息笔记本身必须包含足够的元数据。我给每篇笔记定义了两个 Front Matter 字段块--- title: chunking 策略实践 tags: [RAG, 文本处理, 经验总结] status: done date: 2025-01-12 related: [RAG 系统设计, embedding 模型选型记录] ---这几个字段看着简单实际在检索时非常有用。tags可以让 LLM 在回答时优先聚焦某些领域status表示内容是否可靠——done代表经过验证draft代表还在思考中。我在 prompt 里明确告诉模型只有status: done的笔记才可作为事实依据draft的内容只能作为背景参考。这一条规则大大减少了模型因为检索到半成品笔记而给出误导性结论的概率。另外每篇技术笔记我都会按照固定模板来写背景、结论、过程细节、参考资料、踩坑记录。模板的好处是强迫我把模糊的想法写清楚也让后续向量化时文本块的内容更完整。读者可以照这个模板直接复制# 标题 一句话说明这篇笔记解决什么问题 ## 背景 ## 结论 ## 过程/关键细节 ## 踩坑记录 ## 参考资料/链接2.3 从静态 wiki 到语义检索文本切块与向量化wiki 建好之后下一步是让 LLM 能“读懂”这些笔记。这里的关键技术点是 RAG把笔记内容切分成小块用 embedding 模型把每个小块转成向量提问时把你的问题也转成向量然后在向量库里做相似度搜索找出最相关的小块最后把这些小块作为上下文送给 LLM 生成回答。听起来不复杂但里面有一个非常影响效果的点切块策略。我一开始用固定字符数切块比如按 500 个字符一刀切结果很多块从半句话开始、到另一个半句话结束检索出来根本没法看。后来我改成“按段落切块小段落合并代码块单独成块”的策略效果提升显著。下面是用 Python 参考实现做一个简单切块的示例实际生产我还会加更多规则import re def split_markdown_by_paragraph(text, max_chars800, min_chars100): parts [] lines text.splitlines() current [] current_len 0 for line in lines: # 代码块独立处理 if line.strip().startswith(): if current: parts.append(\n.join(current)) current, current_len [], 0 code_lines [line] while not line.strip().endswith(): # process next line ... pass # 简化处理实际需要读取到结束标记 parts.append(\n.join(code_lines)) continue current.append(line) current_len len(line) # 遇到空行或达到最大长度则切一刀 if line.strip() and current_len min_chars or current_len max_chars: parts.append(\n.join(current)) current, current_len [], 0 if current: parts.append(\n.join(current)) return [p.strip() for p in parts if p.strip()]这个脚本在真实场景中需要处理表格、引用的 blockquote、列表缩进等格式。我的建议是切块时优先保留语义完整性而不是追求固定长度。宁可有的块短一点也不要让一句话被腰斩。2.4 向量化的选择本地 embedding 模型还是 API切完块之后要转成向量。这一环节我做过对比测试结果让我很意外在中文知识库场景下本地小模型的效果并没有想象中那么差。我用的是bge-m3这个开源模型它在句子匹配和长文本表示上表现扎实与一些收费 API 相比差距不大而且完全本地运行没有数据出网问题。对比下来模型维度中文效果部署成本备注bge-m31024好低适合本地推荐优先尝试m3e-base768中等低轻量速度较快text-embedding-ada-0021536好按量计费使用简单但数据出网国产 API embedding1024左右好按量计费兼容 OpenAI 格式部分有免费额度我在测试时用了一个自己标注的“20 个典型问题集”涵盖模糊提问、术语提问、跨文档提问三类。最后 bge-m3 的召回命中率大概是 85%而云端 API 大约是 90%——这 5% 的差距在日常使用时几乎感知不到。所以如果你的运行环境允许跑一个小模型建议优先本地化。3. LLM 接入与增强检索的实操细节3.1 如何在 Dify 里配置 LLM 与知识库如果你不想从零写代码Dify 是一个非常顺手的工具。它能让你把“知识库管理、检索逻辑、模型调用、对话界面”串起来。我在 Dify 里建了一个名为 llm_wiki 的应用核心配置分三步第一步在“设置—模型供应商”里配置 LLM。通常可以填 OpenAI 兼容的接口地址。比如用本地跑 Ollama 时我在 Dify 里添加自定义模型供应商model_type: llm model_name: qwen2.5:14b api_base: http://localhost:11434/v1 api_key: ollama # 本地不需要真实 key随便填第二布在“知识库”中上传或同步笔记。Dify 支持从本地文件导入 markdown也会自动切块、向量化。这里我建议关闭自动分段改为自定义分段规则分段标识符选择\n\n段落空行最大分段长度设为 800这样更接近我们手工测试时的效果。第三步在应用编排中设置“上下文”变量。这样系统会先从知识库检索相关片段再拼接到 prompt 里。具体配置我后面展开。3.2 关键参数chunk_size、top_k 与检索策略很多人以为回答质量完全取决于模型其实检索环节的参数影响更大。我系统调了三个参数记录下效果变化第一个是chunk_size分块长度。在 Dify 中我分别试过 256、512、800、1200。结果 512 和 800 表现最好过小导致上下文不完整过大导致召回时噪音多。这个没有标准答案与你的笔记风格有关。如果你的笔记都是大段论述型适合 800如果是精炼要点型512 更佳。第二个是top_k召回片段数量。top_k 小比如 2回答可能信息不够top_k 大比如 10模型容易被无关内容干扰。我最终设置为 4。如果你发现回答总忘事儿可以加到 6再观察引用片段是否准确。第三个是score_threshold相似度阈值。这是很多人忽略的。如果阈值设得太低就算问题与笔记内容完全不相关系统也会硬找一些片段送进去逼着模型“编”。我设的是 0.4低于这个分数时直接回答“知识库中没有找到相关内容”这比胡说八道诚实得多。另外Dify 里还可以开启“重排序”功能。它相当于在向量召回后再用另一个模型对候选片段精排效果提升很显著但会增加耗时。我的建议是如果你已经设置了合理的 top_k且回答已经能满足需求可以先不开重排。3.3 从“召回”到“生成”prompt 设计的实战套路模型生成阶段的 prompt 是决定回答风格的最后一个关键点。我系统里使用的是自己迭代过多次的中文 prompt核心逻辑就三条明确角色、限定依据、给出兜底逻辑。下面这段是我在 Dify 里实际使用的 System Prompt你是一位熟悉我知识库的专家助手。请你基于提供的“上下文片段”回答问题。 要求 1. 回答中必须优先使用上下文片段中的事实不要引入片段之外的知识。 2. 当你使用某个片段中的内容时在句末标注来源笔记的标题形如【来源xxx】。 3. 如果片段与问题不相关或者根本没有片段请直接说“知识库中没有找到相关内容建议补充笔记或换个方式提问。” 4. 不要编造数据、结论或引用。宁可少说不要说错。这套 prompt 看起来简单实际效果非常好。特别是“引用来源”这条它强制模型降低幻觉概率。因为在生成时模型会试图把回答锚定在给定片段上而不是自由发挥。我还为普通问答和“总结模式”分别设了不同 prompt。普通问答按上面来总结模式则要求模型读多篇笔记后输出结构化摘要并用表格对比异同。这个功能在复盘项目时特别好用。3.4 进阶玩法用 LLM Agent 辅助 wiki 内容维护到这一步llm_wiki 已经从“被动回答”进化到“主动维护”了。我的最后一步是给这个系统加了 Agent 的能力不只是回答问题还能调用操作脚本对 wiki 内容做辅助性修改、补全链接、生成摘要等。比如我可以这样用把一篇新入库的 pdf 转成 markdown然后让 Agent 自动生成 Front Matter 和双链建议。我预置了一个函数调用create_note(title, content, tags, related_links)Agent 在读取完文档内容后会调用这个函数生成一个新笔记粘贴到目标目录同时自动插入相关链接。这看起来很简单实际省了我大量手工整理的时间。当然这里有一个重要的安全边界Agent 的写操作必须是建议式的不能直接覆盖已有内容。我实现的时候Agent 生成的新内容会先放在一个pending/目录经过我 review 之后再合并到主库。让 AI 直接修改知识库是很危险的事它可能会把你悉心维护的笔记改得面目全非。如果你也想做这个功能请一定加一层人工确认。4. 从个人 wiki 到团队 wiki 的落地经验4.1 部署方式本地、服务器与共享协作个人项目跑在自己电脑上没什么问题但如果是一个小团队要共用这个 llm_wiki部署方式就得认真考虑了。我经历过的方案有三种第一种全部本地部署每个人电脑上跑一套 Obsidian 本地模型知识库用 Git 同步。这个方案离线可用、隐私好缺点是模型响应受各自电脑性能影响大模型跑起来有门槛。第二种中心化服务器方案知识库放在云服务器或内网服务器上Dify 也部署在服务器团队成员通过 Web 界面访问。这个方案统一体验、便于管理是最推荐的团队模式。第三种混合模式知识库本体在服务器上但个人终端用 Obsidian 编辑内容通过 Git 或网盘同步回服务器。这个方案兼顾了个人写作体验和团队统一查询入口。我当时在团队落地时选的是第三种。因为团队里有人习惯用网页有人更喜欢本地编辑器混合模式让大家都有顺手的感觉。但要注意同步冲突是团队协作最大的敌人。所以我在项目规范里强制要求同一时间只 allow 一个人编辑一个页面并在每篇笔记前面加owner字段。4.2 内容更新的异步索引与增量处理知识库是活的内容会不断更新。如果每改一个字符都立刻全量重建向量索引既慢又浪费算力。所以必须做增量更新。我的做法是给每个笔记文件算一个 hash存储期存到元数据库里。扫描时只处理新增、删除、hash 变化这三个状态。新增和变化的文档重新切块、向量化删除的文档同步把对应向量删除没变的直接跳过。系统结构可以用这个简化的 Python 伪代码描述import hashlib import os import json def sync_index(wiki_dir, index_meta): for root, _, files in os.walk(wiki_dir): for fname in files: if not fname.endswith(.md): continue path os.path.join(root, fname) content_hash hash_file(path) previous_hash index_meta.get(path) if previous_hash content_hash: continue # 需要重新索引的文档 chunk_and_vectorize(path) index_meta[path] content_hash我用了一个基于 SQLite 的索引表来保存文件路径、hash、最近更新时间。每次同步过程只需要比较 hash 值速度极快。对几百篇笔记的规模增量更新在几秒钟内就能完成。4.3 团队协作中的角色和流程谁负责知识谁负责测试团队协作中最容易出问题的是“职责不清导致的知识库维护停滞”。我落地时设了三个角色知识入库人负责将新资料整理成 wiki 页面填入模板确保元信息完整系统维护人负责索引更新、模型配置、prompt 调优质量验证人定期用一套“验证问题集”抽查问答效果发现检索不准或回答错误时反馈给系统维护人。这个流程看起来多了一点管理成本但非常值得。尤其质量验证人我发现这个角色对系统效果的稳定起了决定性作用。每周抽出半小时问 5 个刚入库的内容相关的问题看回答质量就能有效避免模型因为某次调参变 “傻” 而无人察觉。5. 常见问题与排查技巧实录5.1 检索质量差为什么搜不到相关内容我刚开始上线时最常遇到的问题就是一个问题明明笔记里写过但系统就是答不上来。排查之后发现常常是以下三个原因第一个是切块破坏了语义。比如一句话被分到两个块里每块都不完整向量化时语义丢失。解决办法就是查看分块结果把切分规则改成按段落分。第二个是 Embedding 模型与查询语言不匹配。我的笔记是中文为主、夹杂英文术语如果只用英文场景训练过的 Embedding 模型中文检索效果自然很烂。所以中文知识库尽量选择中文效果好的模型比如 bge-m3、m3e。第三个是知识库索引不同步。你明明更新了笔记但索引还是旧的导致检索不到新内容。检查一下 hash 比较逻辑确认是否运行了增量同步。5.2 回答胡编乱造幻觉问题的几个兜底方案“幻觉”是生成式系统的顽疾。我不能保证完全消除但经过调参后我系统的幻觉率已经可以降到很低。我用了三层兜底第一层是检索质量兜底。前面讲过了通过score_threshold控制拒绝回答的边界没有把握时就不答。这很重要因为哪怕模型再强没有相关上下文时它也容易一本正经地胡说八道。第二层是 prompt 约束兜底。在 prompt 里明确要求“只使用给定片段中的事实”并要求标注来源。虽然模型不一定能 100% 遵守但这能在很大程度上把回答“钉”在知识库内容上。第三层是答案校验兜底。我开发了一个简单的校验脚本提取回答中的所有数字和专有名词再回知识库搜索这些实体如果找不到对应内容就标记为“存疑”。这一步自动化成本不高但效果很好能在问题出现前就预警。5.3 本地模型显存不足 / API 成本超支如果你用本地模型显存会是个绕不开的坎。我现在用的 14B 模型量化到 Q4 之后大约需要 9GB 显存只能跑在 16GB 的显卡上。如果你的显存只有 8GB建议用 7B/8B 甚至更小的模型或者用 CPU内存跑一些压缩更狠的版本但速度会明显变慢。但如果只是个人用我反而建议日常高频问题和小型知识库用本地小模型跑复杂的综合分析和大规模检索再调用 API。我设计了一个“成本熔断”机制本地模型回答的置信度低或者 prompt 词数超过一定阈值时才自动切换到云端 API。这样每个月的 API 开销能控制在很低的水平。5.4 常见问题速查表现象可能原因处理办法回答“找不到相关内容”但笔记里明明有切块粒度太大 / 索引未更新 / 阈值过高检查分块结果运行索引同步降低 score_threshold 到 0.3回答内容张冠李戴检索到的片段不相关提高 top_k或开启重排序模型回答总是很笼统没有细节上下文片段太少增大 chunk_size 或 top_k模型引用了不存在的“来源”幻觉现象prompt 中注明必须引用原文标题开启答案实体校验本地推理速度很慢模型过大 / GPU 未启用换小模型检查是否加载到了 GPU打开 Obsidian 一直卡双链和附件太多定期整理禁用不必要的插件6. 实操过程中的几个意外心得如果你已经准备动手最后分享几个我在实践中才真正体会到的点。第一个是内容结构永远比技术参数更重要。我花了很多时间调参数后来发现一个特别大的问题来源是笔记本身写得太乱。当我把笔记按统一模板重新整理即使在同一个参数配置下回答质量也提升了一大截。所以如果你觉得检索效果不好先别急着换模型先检查你的内容是否符合“一篇笔记只聚焦一个主题”的基本原则。第二个是小步快跑不要一开始就追求全自动。我最初还计划做自动抓取网页、自动生成摘要、自动关联全部内容结果发现越复杂的系统越容易失控。后来我砍到了一个最小可用版本静态 wiki 定期同步 问答检索。这个版本稳定跑了三个星期以后我才逐步加了 Agent 辅助维护。每一次改动都要可回滚这才是长期主义。第三个是定期给系统出题考试。我会从一个“问题集”中随机抽取题目测试系统的回答质量。这个习惯帮我提前发现了很多问题比如某次升级模型后回答开始“话痨”但在一次测试里才发现它会对一个很确定的技术细节给出错误解释。出题考试看似笨其实是最可靠的回归测试方式。做 llm_wiki 这个项目最大的收获反而不是“技术跑通了”而是让我重新审视了自己和知识的关系。过去我把知识库当成仓库只负责存不负责取现在它更像一个聪明的研究助手会主动帮你把零散的信息串起来。如果你也有一堆积灰的笔记试着用这个思路搭一个自己的 llm_wiki你会重新爱上整理笔记这件事。