开头先交代一个背景。我做了几年的知识库建设,从最早的文件夹归档,到后来的Wiki系统,再到最近的RAG知识库,踩过的坑一个没落下。但直到我把“LLM-Wiki”这个概念真正落地成一套工作流,我才意识到,过去所有的知识库方案都卡在同一个问题上:知识库是给人读的,不是给机器用的。为什么这么说?因为传统知识库的核心假设是“人通过目录导航来获取知识”,而LLM-Wiki把这件事颠倒了过来——先把知识库“编译”成一张互相链接的Wiki网络,再把检索路径的选择权交给LLM。这篇文章是“LLM-Wiki”系列的第一篇,我不打算一上来就堆代码,而是先把整套思路的来龙去脉讲透,包括它解决什么问题、编译阶段和检索阶段各自做什么、和RAG的区别、以及我落地第一版时的实际体验。适合正在纠结“知识库怎么搭才能喂给大模型”的开发者,也适合已经被RAG的“垃圾进垃圾出”折磨到怀疑人生的朋友。
1. 知识库的现状痛点与LLM-Wiki的机会点
先聊聊我观察到的普遍困境。你的团队肯定也有一个这样的知识库:要么是几百篇Markdown文档躺在Git仓库里,要么是Wiki站点里塞满了词条,要么是各种网盘、在线文档、Confluence页面混在一起。建的时候雄心壮志,用的时候发现三大问题。
第一个问题是“建得起、存不下”。文档越来越多,命名越来越随意,目录层级越来越深。最后整个知识库变成了一座信息垃圾场。我见过一个团队的技术Wiki有四千多个页面,其中三分之一的页面标题叫“未命名文档”或者“新建文档(12)”。这不是管理问题,这是知识库缺少“编译”环节的必然结果。所谓编译,就是把零散的、重复的、断链的信息,整理成有明确边界的知识节点。
第二个问题是“存得下、找不着”。搜索功能永远在关键时刻掉链子。关键词搜不到,因为作者和读者用的是不同词汇;目录导航找不到,因为一个知识点往往交叉分布在多个板块。经典的例子是:你明明记得知识库里有一篇关于“JWT过期处理”的文章,但你搜“JWT”出来几十个结果,搜“Token过期”啥也没有,你用“登录态失效”再去翻目录,翻了五层也没找到。这时候你会开始怀疑,这个知识库到底是为谁建的?答案很残酷:它是为“建库的人”建的,不是为“用库的人”建的。
第三个问题最要命,叫“找得着、读不完”。假设你费尽力气终于找到了那篇文档,发现它一万多字,你需要从中提取三段关键信息。你硬着头皮读完,提取,归档,然后继续处理下一个问题。一次两次可以,次数多了你就放弃了,转而直接去问同事、搜网页、翻聊天记录。知识库的活跃度就这样一天天下降,直到变成一座无人问津的数字化废墟。
所以你看,传统知识库的问题根源是:它把“存储结构”当成了“检索结构”,又把“检索结构”当成了“理解结构”。存储结构是树状的,检索结构是倒排索引的,理解结构是人脑里关联网络的。三种结构错位,知识库自然用不起来。
LLM-Wiki的切入点就在这里:它用一套静态工程手段把知识库“编译”成Wiki网络(解决结构和连接问题),再用LLM的动态推理能力来取代人肉导航(解决检索和理解问题)。前者是确定性流程,后者是概率性推理,两者结合,才有可能真正盘活一个知识库。
2. 编译阶段:把散装知识变成互链Wiki网络
LLM-Wiki的第一步不是让LLM直接回答用户问题,而是先对知识库做一次“编译”。这个“编译”不是写代码那种编译,而是类似“把一堆源代码文件组织成可运行的软件”的过程。我拆成四个子步骤来理解。
2.1 知识切块:先定“页面”的边界
知识库的原始单位通常是一篇文档、一页Wiki或者一篇笔记。这些单位的粒度太粗,而且边界往往模糊。你要做的第一件事,是把它们切分成“知识页面”。每个页面的标准是:单一主题、可独立引用、有明确标题。
实际操作上,我建议用“目录结构 + 语义完整性”双重约束来切块。你可以让LLM帮你切,但更稳妥的是先手动定义切块规则。比如我的规则是:每个页面必须能回答一个问题,标题就写成问题形式或主题短语。这个原则听起来简单,做起来却需要大量取舍。一个页面如果信息太多,就应该拆;一个页面如果信息太少,就应该合并到相邻页面。这就像程序员重构代码时拆函数一样,边界切得好不好,直接决定后续Wiki链接的质量。
2.2 Wiki链接生成:让页面之间长出“路”
这是LLM-Wiki编译阶段的核心动作:为每个知识页面找到它在知识网络中的位置,然后生成与其他页面的链接。链接类型我大致分三类:
- 相似链接(Similarity Links):内容主题相近的页面互相链接。比如“JWT过期处理”和“Token刷新机制”就是一对相似链接。
- 实体链接(Entity Links):共享同一个名词或概念的页面互相链接。比如多个页面都提到了“Redis”,它们之间就通过实体建立连接。
- 层级链接(Hierarchy Links):父子关系、上下位关系。比如“认证体系”是父页面,“JWT登录流程”是子页面。
手动给几千个页面打链接是不现实的,所以这一步必须用脚本来做。我的实现思路是:先对所有页面做Embedding向量化,然后计算相似度矩阵,选出Top-K个相似页面,再用LLM对候选链接做一次质量过滤。关键点在于:不要像传统RAG那样把相似内容当成“候选片段”,而是把相似内容当成“邻居节点”。邻居节点之间需要LLM给你一句话的链接理由,这就变成了知识网络里的导航路标。
2.3 页面元数据:给LLM一张可用的“地图”
光有链接还不够。你在编译阶段还需要为每个页面生成结构化元数据,供后续LLM检索时使用。我落地时的元数据字段包括:title(标题)、aliases(别名)、summary(页面摘要)、tags(标签)、linked_pages(关联页面列表)、category(所属知识域)。这些信息会被存在页面的Frontmatter里,或者存在单独的知识图谱数据库中。
为什么强调“别名”?这是我在实战中体会最深的一点。你搜“JWT”可能搜不到“令牌过期”,但如果页面的aliases字段里有“令牌”“Token”“登录态”,LLM在导航时就能通过别名匹配快速锁定目标。别名不是给字符串匹配用的,是给语义匹配用的。你要在编译阶段把“同一个意思的不同说法”提前铺好。
这种做法的好处是:LLM在做检索决策时,不需要依赖笨拙的关键词匹配,它可以像人一样“用不同说法去找同一页”。我把这一步称为“把知识库的地图提前画好,而不是让模型每次现画”。
2.4 编译产物:存什么,用什么格式
编译阶段的产物建议是两样东西:一批带元数据和链接的Markdown页面文件,以及一张关系表/图谱数据。页面文件是给人看的,关系表是给机器检索用的。关系表可以用图数据库存(比如Neo4j),也可以简单用JSON文件存。小而美的方案是直接用pages.json+links.json两个文件搞定;规模大了再上图数据库不迟。
这里要提醒一点:编译阶段不要让LLM去改写页面原文内容。LLM只负责切块边界建议、生成摘要、提取别名、判断链接关系。原文内容应该保持原样,最多做格式归一化。原因很简单:LLM重写内容会引入幻觉,而链接和摘要的幻觉是可以事后抽检修正的,内容重写的风险则大得多。知识库的原始材料是你的资产,不要用大模型去污染它。
3. 检索阶段:把检索权交给LLM,意味着检索策略从“查”变成“逛”
传统RAG和搜索引擎的核心范式是“查询—召回—排序”。系统先根据用户的query去匹配知识片段,再把最相关的一堆片段塞给LLM。这就像你站在一个巨大的图书馆里,不让往里走,只让图书管理员帮你搬一摞书出来——至于那一摞书里有没有你要的,全凭管理员的判断。
LLM-Wiki的检索范式完全不同。它的做法是:给LLM一张知识地图(也就是编译阶段产出的Wiki网络),告诉它“目的地”大致在哪个区域,然后让它自己在网络里导航、跳转、阅读、决定下一步去哪。这不是一次性的召回,而是一个多步的探索过程。
3.1 为什么“多步导航”比“单次召回”更适合复杂问题
我举一个非常典型的实战例子。假设你的知识库里有一篇关于“私有化部署”的文章,内容里依次提到了“服务器要求”“Nginx配置”“HTTPS证书”“离线安装包构建”四个子主题。传统RAG在回答“私有化部署的HTTPS证书怎么配”时,可能召回的是整篇“私有化部署”文档,然后让LLM从中找答案。找得到还好,找不到就瞎编。
LLM-Wiki呢?它会让LLM先访问“私有化部署”页面,看到页面上有四个子页面的链接,然后LLM根据用户意图,主动点击“HTTPS证书”这个链接,再阅读那里面的内容来回答。整个过程中,LLM的行为非常像人:先看目录,再翻到具体章节。区别在于,这一切对用户是透明的,用户不需要自己找链接。
当然不是所有问题都需要多步导航。你要是问“Redis默认端口是多少”,一步就到了。但知识库里的问题往往没有那么简单,尤其是涉及跨知识域的排查类、方案类问题。这时候多步导航的价值就会体现出来:每一次点击都是一次信息确认,链路上的页面共同组成答案的上下文,而不是靠一个超大片段硬撑。
3.2 用户、LLM、知识网络三者的新型协作关系
在LLM-Wiki的检索阶段,用户不再直接面对“检索框”和“文档列表”,而是面对一个能听懂人话的导航员。我给这个流程画过一张非常简洁的协作图:
- 用户提出一个问题(自然语言)。
- 主控LLM先看一遍知识图谱,判断入口页面有哪些。
- LLM依次访问入口页面,阅读摘要和链接描述,决定下一步跳转到哪。
- LLM重复第3步,直到它认为已经收集到足够信息,或者到达了“无法再往下走”的叶子页面。
- LLM汇总所有访问过的页面内容,生成最终回答,并附上“参考路径”供用户追溯。
在第3步,有一个细节值得展开:LLM如何才能知道该不该跳转?这就完全依赖于编译阶段生成的链接描述质量了。我在落地时会让LLM在生成链接时同时生成一句“链接说明”,比如“参见:Token刷新机制——适用于JWT过期后如何用Refresh Token续期”,这一步非常有效。它的作用相当于Wiki里的“hover预览”,让导航中的LLM在点击链接之前就能判断这条路是否值得走。
3.3 和GraphRAG、Knowledge Graph Agent的区别
很多朋友看到这里会问:这跟GraphRAG有什么区别?跟LLM+Neo4j的Agent方案又有什么区别?
GraphRAG的做法是:在查询时把问题拆解成多个子问题,然后在图上做多跳检索,再把检索结果送回LLM聚合。它本质上是预建图谱 + 查询时图搜索。LLM-Wiki的编译阶段虽然也建立了知识网络,但它的检索方式是交互式导航:LLM每走一步都会检查当前页面内容、决定下一步,整个路径是动态生成的。相比之下,GraphRAG的多跳是搜索算法决定的,LLM只是在最后做聚合。
至于LLM+Neo4j的Agent方案,那是完全让LLM去写Cypher查询——这种方法对提示词和模型能力要求极高,一旦LLM写错查询,结果就是灾难性的。LLM-Wiki把这一步变成了“从候选链接中选择下一步”,不需要LLM具备严格的查询语言能力,它只需要“点链接”的能力。你可以把这种设计理解为:我们把复杂决策(写查询)简化成了简单决策(点链接),把错误率降到了可控范围。
4. 现有工具能直接撑起LLM-Wiki吗
我落地之前,也想过一个问题:有没有现成的开源工具直接支持这套LLM-Wiki范式?答案很现实:目前没有完整支持LLM-Wiki的开箱即用方案,但有几个工具可以作为地基。我把它们拉开来对比一下。
| 工具/方案 | 定位 | 与LLM-Wiki的匹配度 | 主要欠缺 |
|---|---|---|---|
| RAGFlow | 文档理解+RAG流水线 | 中等 | 仍是传统“切块召回”范式,知识网络构建能力弱 |
| Dify | LLM应用编排平台 | 中低 | 知识库检索仍然时基于向量/关键词召回,没有编译阶段 |
| Obsidian + 插件 | 本地双链笔记 | 中高 | 双链结构天然匹配Wiki网络,但缺少LLM导航机制 |
| Wiki.js + API | 传统Wiki系统 | 中等 | 有现成的页面/链接基础,但检索侧还是传统搜索 |
| LangChain + 自建图谱 | 开发框架 | 高 | 需要自己写编译流程和导航逻辑,起步成本高 |
先说我为什么觉得现成RAG工具匹配度不高。市面上几乎所有RAG工具的核心是“切块 + Embedding + TopK召回”。它的静态索引是平铺的,没有“页面”和“链接”概念,召回结果之间彼此孤立。你让LLM回答时拿到的是三五个互不相关的小片段,而LLM需要自己脑补片段之间的关系。一旦知识库里存在“跨文档的知识依赖”,传统RAG就很容易答得七零八落。
Obsidian是我最近用得最顺手的“编译端工具”。它的双向链接、Frontmatter、图谱视图,几乎天然就是LLM-Wiki的静态载体。但Obsidian缺少一个“检索代理层”——它只能让你手动浏览或全文搜索,不能让LLM在里面自主导航。所以我在落地时,是把Obsidian当成编译产物编辑器来用的,真正的检索逻辑是我自己写的一套Python/TypeScript服务。
这里我想强调一个观点:LLM-Wiki不一定需要从零开发。聪明的方式是组合现有工具:用Obsidian管理Wiki页面和链接,用脚本导出图谱数据,再用一个LLM Agent服务处理用户请求。整套方案的工程量其实可控,关键在于你是否理解“编译”和“导航”这两个核心动作。
5. 我的第一代LLM-Wiki落地实践
讲完理念,分享一下我实际搭建的第一代LLM-Wiki。这个项目大概用了一个不错的周末的时间就把闭环跑通了,因为思路清晰之后,工程实现反而是相当简单的。
5.1 目录结构与数据流
我的仓库结构大致是这样:
llm-wiki/ ├── content/ # 编译后的Wiki页面(Markdown格式) │ ├── _index.md # 入口页,包含全库分类导航 │ ├── auth/ │ │ ├── jwt-overview.md │ │ └── token-refresh.md │ └── deploy/ │ ├── nginx-config.md │ └── https-cert.md ├── meta/ │ ├── pages.json # 所有页面元数据(标题/别名/摘要/标签) │ ├── links.json # 页面间链接关系(含链接描述) │ └── emb.json # 页面的向量表示(用于入口页面检索) ├── compile/ │ ├── chunker.py # 切块脚本 │ ├── linker.py # 链接生成脚本 │ └── compiler.py # 编译调度主入口 ├── retrieve/ │ └── navigator.py # LLM导航检索服务 └── app.py # 对外API服务(HTTP接口)数据流是单向的:原始文档 → 编译脚本 →content/+meta/→ 检索服务 → 用户问题。编译是离线批处理,导航是线上推理。这里要特别注意:不要把编译逻辑和检索逻辑搅在一个服务里,否则你每次更新知识库都要重新设计检索策略,越做越乱。
5.2 编译脚本的核心逻辑
编译阶段我用到了Embedding模型 + LLM两个组件。Embedding模型负责计算页面相似度,LLM负责生成摘要、提取别名、筛选链接。伪代码大致如下:
# compile/linker.py - 简化版链接生成逻辑 def generate_links(pages, embedding_model, llm_client): vectors = embed_all_pages(pages, embedding_model) for page in pages: # 1. 向量召回相似候选页 candidates = find_topk_similar(page, vectors, k=15) # 2. LLM过滤并生成链接描述 prompt = build_link_prompt(page, candidates) links = llm_client.filter_links(prompt) # 3. 写入 links.json save_links_for_page(page.id, links)这里的核心计算量在向量矩阵,但页面量一般几千个以内,完全可以用本地Embedding模型跑,不需要GPU。
编译时我要特别关注的是:链接不要贪多。一个页面保留5~10个高质量链接就够用了。链接太多,LLM导航时反而容易迷路;链接太少,知识网络太稀疏。另外,我还会在编译时给链接加一个relation字段,标记是“相关”“目标在这个页面再往下的子页面”还是“背景知识”,这样导航时LLM能区分路径优先级。
5.3 导航检索服务的实现思路
检索服务是整个LLM-Wiki的“大脑”。我实现时用的是LangGraph搭配普通OpenAI/兼容API。流程很简单,但每一步的逻辑都需要仔细打磨:
- 入口选择:用用户的query对
meta/emb.json里所有页面做向量相似度TopK,返回前3个入口候选页。 - 下一页选择:LLM阅读当前页面内容和所有出链(含链接描述),决定是否跳转、跳转到哪个页面、或者已经足够开始回答。
- 循环控制:设置最大访问页数限制(比如6页),防止LLM在大型知识库中无限跳转。
- 回答生成:把已访问页面的摘要+关键段落拼进上下文,让LLM生成回答。
值得专门说明的是第2步,这是LLM-Wiki和RAG最大的分水岭。我用了一个结构化的函数调用,让LLM输出navigate或answer动作。如果输出navigate,就附带next_page_id;如果输出answer,就附带final_answer。这种设计比让LLM自由输出文本可靠得多:
# retrieve/navigator.py - 导航决策核心 def agent_loop(query, current_page, visited, max_steps=6): if len(visited) >= max_steps: return finalize(visited, query) # 构造当前页面的上下文 page_content = read_page(current_page) outbound_links = get_outbound_links(current_page) # 让LLM做导航决策 decision = llm.decide( query=query, current_page=page_content, candidates=outbound_links, # 每个候选包含链接描述 visited_pages=visited ) if decision.action == "answer": return decision.answer elif decision.action == "navigate": return agent_loop(query, decision.next_page_id, visited + [decision.next_page_id])我一开始用LangGraph写,后来发现其实用简单的递归加while循环也能搞定,只要能保证状态清晰、上下文可追踪就行。知识库导航的核心不是框架复杂度,而是决策质量。决策质量又取决于两件事:当前页面信息的完整度、出链描述是否准确。这又回到了编译阶段的工作。
5.4 一个完整案例的实测效果
我用一个内部知识库做了对比测试。这个知识库大概有800个页面,包含运维手册、开发规范、历史决策记录。我挑了几个传统RAG容易翻车的问题来测:
- 问题:“我们的生产环境Nginx超时时间当前设置的是多少,当时为什么不改成120秒?”
- 传统RAG:召回了“Nginx配置”文档的片段,但答案里完全没有“当时为什么不改”的背景信息,因为那条信息在另一篇“运维变更记录”里。
- LLM-Wiki:第一步进入“Nginx配置”页面,发现出链里有“运维变更记录:2024年6月——超时时间调整讨论”,点击进入,找到当时的讨论摘要,给出了完整答案。回答末尾还能溯源到两个页面。
这只是其中一个例子。我测了大概20个问题,LLM-Wiki在“跨页面信息组合类问题”上的准确率显著优于传统RAG。在“事实型单点查询类问题”上两者接近,LLM-Wiki略慢,因为毕竟多了一步导航过程。这个差异是符合预期的,也说明LLM-Wiki不是单点查询的银弹,而是复杂知识问答场景的进阶方案。
6. 我踩过的坑与下一步规划
既然写了第一篇,不分享几个真实的坑总觉得少了点什么。这几点都是我实际部署之后才砸到头上的,给打算自己搭的朋友提前打个预防针。
6.1 链接质量是天花板,链接数量不是
我最早编译时贪多,每页保留15条链接,想着知识网络越稠密越好。结果跑下来发现两次问题:一是导航决策明显变慢,因为LLM在候选列表里选路时,选项太多导致它开始“犯迷糊”;二是频繁出现无效跳转,LLM根据标题猜测了一个路线,走进去才发现相关度并不高,浪费步数。
后来我把链接数降到每页5~8条,并要求编译时只保留“有明确信息增益”的链接,无关痛痒的弱相关链接直接剔除。效果立刻好转。所以经验是:链接质量比链接数量重要得多,宁可稀疏不要臃肿。你可以在编译脚本里设定一个置信度阈值,低于阈值的链接宁可不要,等后续知识库持续更新时再补。
6.2 不要贪心一步到位,先用“半自动编译”起步
我最初的计划是完全自动编译整个知识库,后来发现开源工具对原始材料的切分质量参差不齐。很多文档切出来的页面是“四不像”——既不像一个独立主题,内容又横跨多个子主题。自动切块的输出需要人去审核调整。
所以我的建议是:第一版不要盲目追求全自动,而是用“半自动编译”。先让LLM给出切块建议和链接建议,然后由知识库管理员(对知识最熟悉的那个人)做一次快速确认。确认后的内容再入库。这个成本换来的是干净的图谱结构,对后续所有导航动作都是一本万利的事。等跑顺了,再逐步加大自动化比例也不迟。
6.3 启用安全的“终止条件”,防止导航失控
LLM在多步导航时,偶尔会出现“绕圈”的情况:访问了页面A,跳到B,又从B跳回A,然后在A和B之间反复横跳。我在检索服务里加了两个终止条件才解决这个问题:一是设置最大访问步数(6步),二是维护已访问列表,禁止重复访问(除非显式指定“回顾”)。如果LLM在新页面上找不到任何有价值的信息,我会要求它立即放弃并回退到上一个有效页面。没有这些护栏,多步检索反而会比传统RAG更容易失控。
另外还有一个细节:当LLM无法在限定步数内找到答案时,我要求它必须回答“当前在Wiki中没有找到明确答案”,而不是强行从已有素材里缝合一个答案。这个行为约束写在系统提示词里,能有效防止“编造感”。
6.4 下一步的规划
第一版已经跑通了基础闭环,后面我想做三件事。第一,把编译流程做成增量式的:知识库更新时只重编译受影响的页面和链接,而不是全量重跑。第二,在导航检索中引入“多Agent并行探索”:不同Agent走不同路线,最后汇总。第三,也是我最期待的,把用户的反馈(点赞、点踩、追问)作为后续编译调整的信号,让知识库网络越用越准。
不过这些都是后话。第一篇文章嘛,能把“把知识库编译成Wiki,再把检索权交给LLM”这个概念讲清楚,并且让看到这篇文章的你愿意动手试一下,我觉得就值了。如果你们落地了第一版,欢迎来和我交流你们在链接编译和导航决策上的具体做法,我拿到实弹经验后会在系列的第二篇里继续展开。