ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LLM-Wiki:把知识库编译成Wiki,让LLM自主浏览检索

LLM-Wiki:把知识库编译成Wiki,让LLM自主浏览检索 最近有个现象让我感触特别深一提到知识库几乎所有人默认就是做RAG——把文档切成块、喂给向量数据库、算相似度、召回topk然后交给大模型拼答案。我也这么干过一段时间但每次遇到需要跨章节推理、概念串联、或者用户问得稍微绕一点的问题召回结果就明显不对劲。后来我开始认真琢磨“LLM-Wiki”这个思路发现它可能是被严重低估的方案与其让LLM在一片均匀切碎的文本碎片里捞针不如先把知识库“编译”成Wiki——有目录、有双链、有索引、有页面结构然后再把检索权整个交给LLM让它像人一样去翻找、跳转、交叉引用。这篇文章是LLM-Wiki总论的第一篇我会把背后的逻辑、与RAG/KG/结构化知识库的区别、以及一套可以照着落地的编译与检索方案完整讲透。1. 先搞清楚LLM-Wiki到底是什么意思1.1 它不是一个新网站而是一套工作范式我第一次听到“LLM-Wiki”这个说法是在和几个做知识中台的同事聊技术选型时。有人开玩笑说与其折腾向量嵌入和重排序不如把公司内部文档做成一个巨大的Wiki然后让GPT自己进去逛。我当时觉得这是段子仔细一想这其实是一个非常严肃的架构思路。LLM-Wiki本质上不是某个具体的产品或者网站它是一套“知识组织 智能检索”的方法论。传统知识库靠人自己在搜索框里输入关键词找答案RAG靠向量相似度自动召回而LLM-Wiki不同它要求先把知识内容整理成Wiki那样的超文本结构——也就是由一个个独立页面组成页面之间有双向链接、有分类索引、有目录导航然后让大语言模型通过工具调用的方式亲自去浏览这个Wiki网络自己决定下一步打开哪个页面、跟随哪条链接最后综合所有读到的内容来回答问题。你可以把它理解为RAG是把一本厚书的每一页都撕下来叠在一起让LLM先从里面摸出几张“最相似”的纸而LLM-Wiki则是把这本厚书变成一本带目录、带页码、带交叉引用的正经书然后派LLM像个图书管理员一样去查目录、翻章节、看注释边看边做笔记。这个区别说起来简单但实际影响很大。RAG适合“快速定位一段话”可一旦问题需要跨章节、跨概念推理RAG那种固定块大小的召回就很容易翻车。LLM-Wiki的核心优势恰恰在于它让LLM有了“阅读路线”而不是一次性的关键词碰撞。1.2 “编译”到底在编译什么为什么要用这个词标题里我用了“编译”这个词很多人会觉得奇怪知识库又不是程序怎么编译其实这里的编译指的是一条完整的知识工程流水线——把原始、散乱、非结构化的文档转化为一个结构完整、可被程序遍历的Wiki站点。我拆解一下这个“编译”过程包含的关键动作页面化把原始文档按照语义边界拆成独立的、可被单独引用的页面而不是按固定字节截断。结构化给每个页面生成统一的Markdown Frontmatter包含标题、摘要、标签、分类、创建时间、来源等元信息。链接化分析页面之间的语义关系自动补上[[相关页面]]这样的双链让知识网络真正“连”起来。导航化生成总目录、主题索引页、标签页确保从任何一个入口都能顺着链接走到任何相关页面。一致性校验让LLM检查是否存在断链、孤立页面、内容重复或相互矛盾的条目再回写修正。为什么管这一步叫“编译”因为它的产出物——编译后的Wiki——是可以被机器稳定遍历、被LLM可靠检索的“中间表示”。原始文档就像源码Wiki就是编译后的目标代码。运行时LLM拿着这份“目标代码”去执行检索任务效率和准确度都会高很多。我自己在实践中最深的体会是这个编译动作不能只做一次。知识库是活的文档会更新关联关系会变化所以“编译”要有增量回归的机制。就像代码改了要重新编译一样知识库内容变了Wiki的索引和链接也得重新生成。这一步做扎实了后续的LLM检索才会轻松。2. 为什么检索权必须交给LLM2.1 传统向量检索的隐痛它更像抓阄而不是找答案既然要讨论“把检索权交给LLM”那就得先弄清楚传统检索到底差在哪。我并不是说RAG一无是处但在很多真实场景里它的短板实在太明显了。首先是块切分破坏了语义。最常见的做法是按字符数或token数硬切比如切512个token一块。结果就是一句话的上半句在一号块下半句在二号块。用户问“为什么Windows下编译ESP32特别慢”向量召回可能返回的是“安装ESP32开发环境”这一块因为它在字面上和“编译”“ESP32”都重叠但完全不是你想要的答案。其次是多跳问题束手无策。一次好的回答往往需要多个知识点的串联。比如用户问“PostgreSQL源码在Ubuntu上编译时怎么解决flex版本太旧的问题”你至少需要理解“编译环境的依赖关系”和“flex版本与语法特性的关系”两个点。普通RAG只做一次embedding检索没法在知识片段之间来回跳转往往只能召回一半内容。第三是不可解释性。用户看到RAG给出的答案不知道是来自哪一段、经过怎样的推理。你没法告诉用户“我是先看了安装文档再跳转到编译错误说明那页才得出的结论”因为整个检索过程对用户是个黑盒。最后是召回结果对query措辞过于敏感。同一个意思换种问法向量结果可能就不一样了。我测过很多次把问题里的“构建”改成“编译”top5结果能换掉一半这对严肃的知识场景来说是不可接受的。传统向量检索本质上是一种“相似度抓阄”它不知道问题需要什么结构只是凭向量距离找文本碎片。这套机制做技术demo很爽做正经的知识问答还是太勉强。2.2 LLM当检索员的好处翻目录、跟链接、做交叉引用把检索权交给LLM就是把上述所有问题放到一个“带大脑的检索员”面前来解决。大模型虽然有时会一本正经地胡说八道但如果我们给它一个清晰可浏览的Wiki再给它几个检索工具它的行为就会变得非常接近一个真实的人类研究者。想象一下LLM的检索过程。它拿到用户的问题后会先调用wiki_search搜索关键词看看有哪些相关页面然后打开其中最像总览的那一页读完摘要后发现里面提到某个子页面就顺着[[双链]]点进去在子页面里又看到另一个关联概念再点进去最后把一路上收集到的信息组合成回答。每一步都是自主决策而不是一次embedding计算。这么做的好处至少有四层上下文是“按需拉取”的。LLM不需要一次吞下几万个token的候选块而是每次只读一页读完再决定下一步。这样即使知识库很大实际使用的上下文窗口也能保持在一个可控范围内。推理路径清晰可见。LLM每打开一个页面我们都可以记录日志。用户问“为什么这样回答”你能直接把浏览路径贴出来“它先看了目录页然后打开第3.2节又通过链接跳到了配置文件说明。”这种可解释性是RAG难以做到的。多跳推理有了天然载体。Wiki的链接结构本身就在告诉LLM哪些概念有联系。跳转一次就是一次推理步骤多跳问题天然适合这种“逐步寻路”的方式。减少幻觉风险。虽然不能完全消除但只要LLM严格按照“打开的页面内容”作答且我们限制它不能编造链接它编造内容的余地就小很多。当然LLM做检索也有代价每次问答要多次调用模型token消耗更高响应时间更长。但这是“用算力换准确性”。在知识问答这种容错率很低的场景里这笔账非常划算。3. LLM-Wiki和其他知识库形态的对比3.1 RAG知识库适合快查但不适合深挖RAG知识库是目前最主流也是最容易上手的方案。把文档上传切片embeddings入库然后query时拿用户问题和文档块做相似度比较。我当年也是被这套流程吸引的因为它实现门槛低——有大量现成的框架甚至拖拽式流水线就能跑通。可一旦文档量变大、问题变复杂RAG就有点吃力了。我见过不少几千页文档的RAG系统用户问“某个旧版本接口怎么升级到新版本”系统召回的全是零散的接口定义而不是“升级指南”这整篇文章。原因就在于RAG的检索单元是“块”而不是“篇”。没有篇章结构跨章节的问题就缺一条贯通的主线。我并不是说RAG没用。它适合快速定位明确的事实比如“后台管理系统的登录超时时间是多久”。但如果你想做一个能让人放心交付的知识问答系统单靠RAG是不够的。3.2 KG知识库擅长实体推理构建成本却是硬伤知识图谱KG知识库是另一个常见方向。它把知识表示成“实体—关系—实体”的三元组比如PostgreSQL依赖flexflex版本2.5.31。这样做的好处是关系推理很强你可以问“哪些组件依赖于flex”并沿着图里的边找到答案。但KG在真实业务里的落地代价非常高。首先是构建成本你需要从文本中抽取实体和关系这一步本身就需要大量人工清洗和LLM抽取。其次是维护成本知识更新时你得同步修改图中的节点和边否则很容易出现链接到过期节点的情况。第三是覆盖度问题很多知识是纯文本描述性的强行抽成三元组反而损失了细节。我见过不少KG项目花了大半年建图最后应用场景还是局限在“查上下游依赖”这种极其结构化的查询上。KG适合已知的固定关系模式但对于开放域的长文本知识它的表现远不如Wiki这种“半结构化超文本”灵活。3.3 结构知识库严格但死板适合程序化查询而不适合对话结构知识库一般指强Schema约束的数据库比如字段固定的业务表、配置管理系统、主数据平台。它的优势是精确、可靠查询结果格式稳定。适合做报表、做权限校验、做单据流转。但把它直接接到LLM问答上会很别扭。LLM必须把自然语言问题翻译成数据库查询或者通过API查询。一旦问题超出了Schema覆盖范围模型就答不上来。比如一个存了“采购单状态”的结构化知识库你问“为什么这批货延迟了三天才入库”它就无能为力因为“延迟原因”这种非结构化信息根本没被建模。所以结构知识库适合作为LLM-Wiki体系里的一个补充层而不是全部。LLM-Wiki负责承载非结构化、强语义的知识结构知识库负责提供准确的数字和状态两者组合才完整。3.4 一张表看明白四种知识库形态维度RAG知识库KG知识库结构知识库LLM-Wiki组织单位文本块实体与边结构化字段Wiki页面与链接检索方式向量相似度图遍历结构化查询LLM自主浏览多跳推理能力弱强很弱强可解释性差中强强构建成本低很高高中维护成本中很高中中适用场景快查事实关系推理事务处理深层知识问答这张表其实已经说明问题了LLM-Wiki虽然不是每项都最强但它在“多跳推理、可解释性、构建成本”这几个关键维度上取得了很好的平衡。尤其当你的知识库是长期演进的文档集合时Wiki结构反而比图谱、比RAG块都更贴近人类真实的阅读方式。4. 实操把一堆文档“编译”成Wiki的完整流水线4.1 先把手头文档标准化按语义拆成页面我平时处理的原料大多是老旧的Markdown、Word转的文本或者网页导出的HTML。第一步永远是清洗去掉页眉页脚、广告、重复的导航文字把图片转成链接并保存到附件目录把表格转成Markdown表格。接下来是最关键的“页面化”工作。我的经验是不要用固定长度切页而是按章节标题和语义边界切。比如一个“安装部署”手册正常应该拆成“环境要求”、“下载源码”、“编译配置”、“启动服务”、“常见错误”这样几个页面。拆分的判断基准是每个页面能否独立回答一个完整小问题。如果一个页面拆出来之后用户还得看另一个页面才能理解那说明拆得太细如果页面里包含多个主题说明拆得太粗。这一步可以借助LLM来做。我会写一个脚本先提取文档的标题层级再用LLM判断每个标题下的内容是否包含多个子主题必要时生成新的小标题并重排内容。如果原始文档本身结构混乱我会先让LLM生成“大纲草案”再按大纲重新组织原文。这样做完你手上会得到一批体积适中的Markdown页面文件每个页面都有清晰的标题和正文。注意页面数量不宜过少否则退化成普通长文档也不宜过多几百页的知识库通常拆成一千到三千个页面是比较健康的范围。4.2 用LLM生成双链、标签和索引让知识真正“连”起来页面建好之后下一步是给页面之间建立关系。这步是整个编译流程的灵魂。我采用的做法是并行调用LLM处理每个页面让模型阅读当前页面内容然后输出两个结构一是该页面引用了哪些其他页面用Wiki双链的[[页面名]]格式二是该页面建议打上哪些标签以及属于哪个分类。我通常会给模型一个这样的提示模板你是一个知识库小编。请阅读下面的页面内容然后完成三件事 1. 找出页面中提到的其他概念或文档这些概念在本Wiki中很可能有独立页面请用[[页面名]]格式列出最多10个。 2. 给页面打3-5个标签。 3. 推荐这个页面应该归入哪个分类从给定分类表中选或者新建议。 只输出JSON不要多余解释。 页面标题{title} 页面内容{content}输出JSON的好处是方便程序解析再写回Markdown的Frontmatter区。处理完所有页面后我会再做一次反向扫描统计每个[[页面名]]是否真的存在。不存在的有两种处理方式要么从链接池里删掉要么去原文里找相近标题生成新页面。一定要把断链解决在编译阶段否则运行时的LLM会被404折磨疯。索引页也需要自动生成。至少要有两个一个是总目录页按分类列出所有页面一个是标签索引页按标签聚合页面。这两个页面是LLM漫游的起点所以它们的质量直接决定了检索成功率。4.3 生成静态Wiki站点本地预览并导出检索索引完成页面的双链和索引后我会把它们组织成一个标准的目录结构wiki/ ├── pages/ # 所有知识页面 ├── index.md # 总目录 ├── tags.md # 标签索引 ├── assets/ # 图片与附件 └── wiki_index.json # 给程序用的检索索引如果你喜欢图形化浏览可以直接用Obsidian打开这个目录双链会渲染得很漂亮。但如果要交给LLM检索最好生成一个wiki_index.json里面记录每个页面的标题、路径、摘要、标签、链接关系。这样运行时工具可以快速检索页面不用每次都用正则去扫Markdown。我还会用静态站点生成器VitePress、Hugo之类把整个目录打包成一个可以HTTP访问的站点。这样做的目的不是为了给人看而是让LLM的工具可以通过fetch请求直接读取页面内容。比如wiki_read_page工具可以用HTTP GET访问/pages/编译原理实验.html拿到干净HTML后转成纯文本。这个过程比在本地读文件更通用也方便后续对接其他平台。预览验证这一步必不可少。我会打开几个代表性页面检查双链是否正常跳转检查首页目录是否列出了所有分类检查wiki_index.json里的字段是否完备。这一步相当于“编译通过”只有这里没问题后面才能放心让LLM接管检索。5. 把Wiki的检索权真正交给LLM5.1 设计一套最小可用的Wiki浏览器工具要让LLM把Wiki“逛起来”我们需要给它提供浏览器一样的工具。我建议第一版只做四个工具不要贪多。wiki_search(keywords)在页面标题、摘要、标签中做关键词搜索返回TopN页面列表。这个工具替代了“搜索引擎入口”。wiki_read_page(title)按页面标题读取正文返回正文纯文本和使用说明。这是LLM阅读单页内容的方式。wiki_list_links(title)列出某个页面上所有的双链目标。这相当于“点击页面上的超链接”。wiki_get_backlinks(title)列出哪些页面链接到了当前页面。这个工具容易被忽略但在Wiki里回链经常能帮你找到“上下文来源”价值极高。我用这四个工具跑过不少真实问答效果已经比单次RAG好很多。如果你的知识库很大还可以加一个wiki_list_category(category)按分类浏览页面。5.2 用Function Calling搭一个检索循环现在看一个基于OpenAI Function Calling风格的实现框架。核心是一个循环LLM判断需要哪个工具我们执行工具并把结果返回给LLM直到LLM认为信息已经足够给出最终答案。import json def wiki_search(keywords: str) - list: # 从 wiki_index.json 按关键词过滤页面标题和摘要 # 返回 [{title, summary, path}, ...] pass def wiki_read_page(title: str) - str: # 读取 pages/{title}.md 的正文转成纯文本 pass def wiki_list_links(title: str) - list: # 正则匹配当前页面中的 [[...]] 双链返回去重的页面标题 pass def wiki_get_backlinks(title: str) - list: # 扫描 wiki_index.json 中所有页面找出包含 [[title]] 的页面 pass tools [ { type: function, function: { name: wiki_search, description: 在Wiki中搜索关键词返回最相关的页面列表, parameters: { type: object, properties: { keywords: {type: string, description: 搜索关键词} }, required: [keywords] } } }, # 其余三个工具类似省略 ] def run_agent(question: str, messagesNone, max_steps10): if messages is None: messages [{role: user, content: question}] for step in range(max_steps): response call_llm_with_tools(messages, tools) tool_calls response.get(tool_calls) if not tool_calls: return response[content] # 最终答案 messages.append(response) for call in tool_calls: fn_name call[function][name] args json.loads(call[function][arguments]) if fn_name wiki_search: result wiki_search(args[keywords]) elif fn_name wiki_read_page: result wiki_read_page(args[title]) elif fn_name wiki_list_links: result wiki_list_links(args[title]) elif fn_name wiki_get_backlinks: result wiki_get_backlinks(args[title]) else: result 未知工具 messages.append({ role: tool, tool_call_id: call[id], content: json.dumps(result, ensure_asciiFalse) }) return 步骤超限请基于已有信息回答这个框架本质上就是ReAct模式的变体。有几个细节非常关键一是每一步都要把“已经读过的页面标题”记录下来避免LLM反复打开同一页浪费token二是工具返回内容不要超过页面原文的合理长度如果页面太大就截断成前3000字并提醒“继续阅读可调用wiki_read_page”三是如果某页不存在返回一个明确的空结果并提示LLM尝试搜索相近关键词。5.3 提示词让LLM成为懂Wiki的“研究员”工具齐了最后一块拼图是系统提示词。我踩过很多次坑最早直接给LLM所有工具它反而乱翻一气。后来我总结了三个原则告诉它先定位再深入先搜索或看目录找到最相关的入口页面再顺链读内容不要一上来就随机点链接。要求它引用来源最终回答里必须标注每句话来自哪个Wiki页面否则视为未完成任务。禁止编造链接和页面只使用工具返回的真实结果如果找不到就说找不到不要脑补。我的系统提示词大致长这样你是一个擅长浏览Wiki的研究员。你面前的Wiki知识库由多个相互链接的页面组成。 在回答用户问题前请 1. 调用wiki_search搜索与问题相关的关键词 2. 打开你认为最有价值的总览页面阅读内容 3. 通过页面上的双链跳转到细节页面必要时查看回链页面 4. 把多个页面中的信息交叉比对形成完整回答。 回答要求 - 结论优先随后给出支撑细节 - 每个关键结论后用[来源页面: 页面名]标注来源 - 如果某个信息无法从页面中找到明确说“知识库中没有找到相关内容” - 不要编造任何Wiki页面标题或链接。这样设置之后LLM的搜索行为会明显收敛。它通常会先搜两个词打开两三个总览页面再顺着两三条链接读完就开始组织答案。整个过程在十步以内基本都能完成。5.4 与Dify等流水线平台的集成思路如果你不打算自己写代码也可以用Dify这类可视化的Agent平台来搭。思路是把上面的工具封装成HTTP API比如/tools/wiki_search、/tools/wiki_read_page在Dify的Agent节点里把这些API声明为自定义工具并在系统提示词里描述清楚每个工具怎么用。这样你就把Wiki检索权交给了Dify编排出来的LLM Agent外层还可以继续挂知识库、工作流和审核节点。不过我个人还是建议手写一遍核心循环哪怕只是一个小Demo。因为只有亲手实现工具调用和消息拼接你才会真正理解为什么“把检索权交给LLM”不是一句口号而是一个需要精心设计工具边界和提示词的工程问题。6. 常见问题与排查心得6.1 链接断裂、孤立页面怎么处理最常遇到的问题就是[[页面名]]指向了一个不存在的页面。我排查时先看编译阶段的断链检查日志再决定是“删除链接”还是“根据反链生成新页面”。这里有一个原则宁可少一条链接也不要给LLM一条死路。断链会让LLM在调用wiki_read_page时拿到空结果进而开始瞎猜这是幻觉的温床。孤立页面同样需要警惕。如果某个页面没有其他页面链接到它它几乎永远不会被LLM发现等同于不存在。我每周会跑一次扫描把所有“入链为0”的页面列表打出来让LLM帮忙判断是应该把这个页面加入某个索引还是写一个页面引用它。6.2 LLM在Wiki里迷路了怎么办有时候LLM会像一只无头苍蝇一样连续打开七八个页面却始终没有回到主题上。这时我会做两个调整。一是限制单次问答的最大步骤数比如10步超时就要求它基于已读内容作答二是在工具结果中主动“提醒”它当前页面的相关链接有哪些引导它选择更接近答案的路径。本质上这是给LLM一个更清洗的“路标”。如果迷路情况频繁出现问题往往出在Wiki结构上——页面分类不清晰、总览页没有写好。我会回到编译阶段让LLM重新生成几个“导览页”把零散页面按主题聚合成群相当于在Wiki里多建了几座“导航塔”。6.3 不一定要二选一LLM-Wiki可以和RAG结合我最终在项目里落地的方案其实是LLM-Wiki和RAG的混合体。LLM-Wiki负责主线探索和交叉引用RAG负责兜底的细碎信息召回。具体做法是如果LLM在Wiki检索后仍然无法确认答案就再调用一个向量检索工具rag_search(query)把命中的块和Wiki页面内容一并作为输入。这个组合比单一RAG稳定得多也比纯Wiki结构应对冷门内容时更灵活。6.4 成本到底怎么算很多人担心LLM-Wiki的调用成本过高。我算过一笔账一个500页的知识库假设每次问答平均6次工具调用每次处理约1500 token一次问答大约消耗9000 token。按一个调用成本适中的模型价格一次深问答的成本大概是几厘到几分钱。如果每日问答量只有几百次总成本完全可以接受。相比它换来的准确性和可解释性这点开销很值。编译阶段的批量处理成本是一次性的。每页用LLM做一次双链和摘要大约消耗2000-4000 token500页就是一两百万token也不是什么夸张的数字。而且编译过程可以复用结果后续知识库不更新就不需要重复花钱。最后说一点个人心得我在实际调这套系统时最大的收获不是“准确率提高了多少”而是“我终于知道这个回答是从哪个页面推理出来的了”。这种可追溯性让知识库从“黑盒相似度机器”变成了“可以被审阅和修正的知识网络”。如果你也正在被RAG的召回结果折磨不妨把LLM-Wiki完整试一遍特别是先花一个周末把你的文档编译成Wiki再让LLM去逛。这个方向值得长期投入。
返回列表