ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

LLM-Wiki:把知识库编译成超文本,让大模型自主翻书检索

LLM-Wiki:把知识库编译成超文本,让大模型自主翻书检索 从年初开始我把团队的知识库从一堆 Markdown 文件重新整理成了 Wiki 形态接着把检索这层决策权完全交给了 LLM。效果比我想象中好不少——用户问一个跨版本问题LLM 能像人一样先打开总览页再顺着链接翻到相关页面把分散在各处的信息拼成完整答案。这就是标题里说的 LLM-Wiki把知识库编译成 Wiki再把检索权交给 LLM。这篇文章是这个系列的总论第一篇适合正在维护知识库、搭过 RAG 又觉得效果不稳定、想给 LLM 一个更可靠“翻书”路线的人。先说明我理解的边界这里说的“知识库”不是传统意义上的关系型数据库而是产品文档、技术方案、内部规范、复盘记录这类以自然语言为主的资料集合。这类知识库的痛点从来不是“存不存得下”而是“找不找得到”“答不对”。接下来我会把知识库的问题、编译成 Wiki 的思路、检索权移交的设计以及我在落地时踩过的坑一次性讲清楚。1. 知识库的三个老问题存得下、找不回、答不对1.1 文件夹树和全文搜索的极限绝大多数知识库刚开始时都是文件夹树加全文搜索。文件夹树是人类心智模型的投影适合“我知道这个东西在哪个目录下”但业务问题往往是跨目录的。比如“新功能上线后旧版数据怎么迁移”这个问题可能同时涉及产品设计文档、后端接口说明、运维部署手册三个文件分布在三个目录文件夹树毫无办法。全文搜索解决了一部分跨目录问题但它本质是词法匹配。你搜“迁移”搜不到只写了“数据搬运”的文档你搜“配额”搜不到通篇用“limit”描述的英文页面。更麻烦的是用户提问时通常不会用和文档完全一致的关键词。于是我们发现搜索框看起来是个入口实际上逼迫每个用户去猜“文档作者可能用了什么词”。我在很多团队里见过同样的情况知识库越堆越大搜索命中率却越来越低最后大家宁愿去问同事也不搜知识库。不是知识没有沉淀是检索成本高于问人成本。1.2 向量召回把检索权焊死在了切块上后来 RAG 火起来很多人开始搭知识库流水线上传文档、切块、做 embedding、向量召回、把 top-k 个片段丢给 LLM 生成答案。像 Dify 这类工具的知识库流水线确实把这件事的门槛降得很低我身边不少非技术同事都能跑通一个 demo。但多跑几个真实问题就会发现向量召回只是把“关键词猜谜”换成了“语义猜谜”并没有解决知识库的结构问题。切块是最大的元凶。一篇文章被切成固定长度的 chunk 后上下文边界被切碎了。用户问“为什么发布后回滚失败”系统召回的是某个 chunk但这个 chunk 可能只是部署文档中间的一段代码没有标题、没有前后逻辑、没有关联页面。LLM 拿到这个片段只能靠自身知识补全而补全的内容可能根本不在你的知识库里——这就是幻觉的常见来源之一。我把这个问题理解为“检索权焊死”检索的粒度、排序、召回数量全是固定的LLM 没有选择权只能被动接受系统给它的几个碎片。它不是在答你的知识库是在答一堆被切碎的无主文本。1.3 RAG知识库、KG知识库、结构化知识库各管一段说到这得提一下市面上几种知识库形态的区别因为很多人容易混为一谈。RAG 知识库把文档切块后向量化擅长语义召回适合“按照意思找片段”。短板是不理解文档之间的关系经常把不同章节、不同版本的碎片混在一起。KG 知识库把实体和关系抽出来构建知识图谱擅长回答“谁和谁什么关系”这类问题。构建成本高而且非结构化文档里的上下文信息会被丢掉。结构化知识库本质是数据库表适合精确查询和统计比如订单、库存、权限记录。但业务文档、规范说明这类内容很难塞进表里。这三者覆盖了语义检索、关系分析、精确查询三种需求但漏掉了一种非常常见的知识组织方式文档之间通过链接互相引用。产品手册里写着“部署方式见发布流程”“参数说明见配置中心”这种关系正是 Wiki 最擅长表达的。LLM-Wiki 的定位就是补上这段空缺用超文本链接组织知识让 LLM 在页面之间自主导航。2. LLM-Wiki的思路把知识库编译成超文本2.1 “编译”在这里指什么我在标题里用了“编译”这个词很多朋友第一反应是“把知识库变成可执行文件”不是。这里的编译是沿用“源代码到目标代码”的隐喻把一堆平铺的、互相孤立的文档转换成结构明确、可导航、带元数据的超文本知识网络。展开说源代码是自然语言的原始文档目标代码是 Wiki 形态的页面集合和索引数据。页面里有明确的链接页面之间有反向链接每个页面都有元数据比如标签、别名、最近更新时间。编译过程要做的是识别文档中的内容单元、建立它们之间的语义关系、生成可供程序读取的索引。这个类比有一点非常贴切编译会产生编译产物而产物和源码是可以分离的。你的原始文档可以继续放在原来的仓库里Wiki 只是它的一个可检索视图。重新编译即可同步更新不需要你手动去维护一套副本。2.2 Wiki形态的产出物页面、链接、反向链接、元数据Wiki 的核心不是那套语法而是三个结构要素。页面内容的基本单位。每个页面聚焦一个主题有自己的标题和正文通常 500 到 2000 字之间。链接页面之间的有向关系。A 页面里写了“部署方式见发布流程”这就是一条从 A 指向“发布流程”的链接。反向链接有多少页面引用了当前页面。反向链接是导航关键LLM 可以顺着反向链接找到“包含当前主题的其他上下文”。元数据则是给检索用的补充信息。别名很重要因为同一件事可能有多种叫法标签是粗粒度的分类更新时间用于判断知识新鲜度。把这些要素配合起来Wiki 就不再是“网页上的文档”而是一张可以被程序遍历的图。2.3 编译后的知识库长什么样拿我自己 Obsidian 里的一个项目知识库举例编译之后的目录结构大概是这样的wiki-repo/ ├── pages/ │ ├── order-system-overview.md │ ├── payment-fallback.md │ ├── deployment-checklist.md │ ├── rollback-runbook.md │ └── incident-log/ │ ├── payment-timeout-2025xx.md │ └── upgrade-data-migration.md ├── index.json ├── backlinks.json └── assets/ └── images/每个页面的 markdown 内部会维护统一的结构--- title: 支付降级方案 aliases: [支付备用通道, payment fallback] tags: [支付, 容灾] updated: 2025-06-01 --- # 支付降级方案 当主支付通道不可用时流量自动切到备用通道。 具体实现见[[支付网关设计]]常见故障处理见[[支付故障应急手册]]。页面之间用[[双链]]表达引用index.json记录所有页面的元数据和链接关系backlinks.json单独存反向链接。这样一份 Wiki 不用任何数据库用小工具就能生成索引。后面接入 LLM 时检索操作只需要读取这两个 JSON而不需要反复扫描全文。3. 检索权交给LLM从“搜索框”到“翻书人”3.1 检索权的三层解耦说到“把检索权交给 LLM”很多人以为是“让 LLM 自己编一个搜索词然后走一遍搜索框”那是表面理解。真正要解耦的是三层决策。第一层是召回权从整个知识库里找到哪些页面可能相关。传统方式是 embedding 相似度一次性给 top-k。LLM 模式下召回可以由 LLM 决定先用一个宽泛的搜索找到候选页再判断哪些页值得打开。第二层是路由权打开页面之后下一步往哪走。人翻手册的时候会从目录页跳到章节页再顺着“相关链接”“参见”跳到另一个页面。LLM 也可以这么做它每打开一个页面都能看到页面里的链接然后决定是继续深入还是返回重试。第三层是生成权用哪些内容组织答案。传统 RAG 把片段拼在一起直接让 LLM 润色LLM-Wiki 让 LLM 在遍历多个页面之后自己决定哪些信息该采用、哪些是背景、哪些相互矛盾需要指出。三层解耦的意义在于检索不再是一次性的“猜”而是一个可迭代的探索过程。这正是搜索框和翻书人的区别。3.2 LLM作为路由器的工程形态要实现这种“翻书”能力最直接的工程形态是 Function Calling。我给知识库设计了四个检索工具[ { name: search_pages, description: 根据查询词在Wiki索引中搜索可能相关的页面返回页面标题和简介, parameters: { type: object, properties: { query: { type: string } }, required: [query] } }, { name: get_page, description: 读取给定page_id的Wiki页面完整内容, parameters: { type: object, properties: { page_id: { type: string } }, required: [page_id] } }, { name: get_backlinks, description: 获取引用该页面的反向链接列表, parameters: { type: object, properties: { page_id: { type: string } }, required: [page_id] } }, { name: get_linked_pages, description: 获取当前页面中包含的双链目标页面ID列表, parameters: { type: object, properties: { page_id: { type: string } }, required: [page_id] } } ]系统提示词里我会给 LLM 一段相当明确的引导先搜索候选页再打开最相关的一页读完看链接如果页面提到了“参见”或“详见”顺着链接继续翻最多翻六步。把搜索、打开、跳转三个动作拆成独立工具后LLM 的行为变得非常可观测——日志里能看到它每一步打开了哪一页、为什么跳过某一页。3.3 与向量检索配合使用的混合策略把检索权完全交给 LLM 不等于抛弃向量检索。向量检索仍然是最快的“粗筛器”适合在几万甚至几十万条记录里先圈定候选范围。我的做法是把 Wiki 页面作为向量的最小单位进行 embedding而不是把每个 chunk 都向量化。当用户提问后先用向量召回 5 到 10 个候选页面再把候选页的 ID 作为初始上下文传给 LLM。LLM 拿到的不再是孤立的片段而是一个明确的页面入口它可以打开候选页里的任意链接做扩展这就是混合策略向量负责“定位”Wiki 链接负责“展开”LLM 负责“决定”。这种策略相比纯向量 RAG 的最大优势是答案永远能溯源到页面级乃至链接级。用户问“为什么降级没生效”LLM 可以回答“根据《支付降级方案》第 X 段《支付网关设计》中写了降级触发条件而《故障应急手册》记录了这次事件的配置异常”。你能清楚地看出答案来自知识库的哪个节点而不是模型自己编的。4. 落地步骤从零编译一个可被LLM检索的Wiki4.1 内容清洗与页面粒度决策任何编译的第一步都是保证输入质量。团队知识库里往往混着多版本文档、废弃方案、草稿、空白页。不清理直接编译只会把垃圾关系也编译进去。我一般做三件事一是去重同一主题有多份写法的只留权威版本二是标记过期文档不要删除而是加一个status: deprecated元数据让 LLM 能识别三是统一文件头每个页面必须有标题、别名、标签、更新时间。页面粒度是我认为最容易被忽视的环节。粒度太粗一个页面包含十个主题LLM 打开后上下文太大关键信息淹没在长篇大论里粒度太细一个概念拆成十几页LLM 翻到头也凑不齐答案。我现在的经验是一个页面只讲一个可独立回答的问题。比如“支付降级方案”是一个页面“支付网关设计”是另一个页面“支付故障应急手册”是第三个页面三者用链接串起来。4.2 半自动建立链接关系的脚本思路纯手工给几百个文档加双链会把自己累死纯自动又容易产生错误链接。我的做法是脚本提候选人审重点。脚本做的事情很简单扫描所有 markdown 文件解析标题、别名、标签和已有的[[双链]]然后通过名称匹配补一条候选链接。下面这个脚本是简化版适合小规模知识库图个思路import os, re, json from pathlib import Path WIKI_DIR Path(wiki-repo/pages) def slugify(name): return re.sub(r[^a-z0-9\u4e00-\u9fff], -, name.lower()) def extract_title(text): for line in text.splitlines(): if line.startswith(# ): return line.lstrip(# ).strip() return Path(text_path).stem pages [] for md_path in sorted(WIKI_DIR.glob(**/*.md)): text md_path.read_text(encodingutf-8) title extract_title(text) aliases re.findall(raliases: \[(.?)\], text) alias_list [a.strip().strip(\) for a in aliases[0].split(,)] if aliases else [] links re.findall(r\[\[([^\]|#]), text) outgoing [slugify(link) for link in links] pages.append({ id: slugify(title), title: title, file: str(md_path), aliases: alias_list, outgoing: outgoing, }) for page in pages: page[incoming] [p[id] for p in pages if page[id] in p[outgoing]] with open(wiki-repo/index.json, w, encodingutf-8) as f: json.dump(pages, f, ensure_asciiFalse, indent2)脚本跑完后我会用文本编辑器检查index.json里每个页面的outgoing列表重点修正两类问题同名不同义导致的错误匹配以及重要关系缺失。自动生成 80% 的候选人工确认 20% 的关键链接这个比例比较稳妥。4.3 生成索引和元数据索引是整个 LLM-Wiki 的检索基础我建议至少包含这些字段字段说明检索用途id页面的稳定标识由标题生成工具参数title页面标题展示和匹配aliases别名列表提高召回率tags标签列表粗粒度过滤summary两到三句话的页面摘要搜索结果返回给 LLM 预览outgoing当前页面的出链 ID 列表顺着链接跳转incoming反向链接 ID 列表向上找上下文updated最后更新时间判断知识新鲜度statusactive/deprecated让 LLM 避免用过时知识摘要千万别偷懒。LLM 在做search_pages时第一步看到的不是全文而是摘要摘要写得好模型判断“要不要打开这个页面”的准确率会高很多。我一般要求摘要里包含“这个页面回答什么问题、涉及哪个系统、适合什么时候看”。4.4 把Wiki暴露成LLM可调用的工具索引生成后检索接口并不需要多复杂。我用一个轻量 HTTP 服务包住四个函数search_pages读索引做关键词匹配和摘要比对get_page读取 markdown 正文并转成纯文本get_backlinks和get_linked_pages直接查索引里的关联字段。四个接口加起来不到两百行。接入 LLM 时把这四个接口的 OpenAPI 描述塞进 Function Calling 即可。系统提示词我会这样写你是一名知识库助手。你的知识来源是Wiki索引和页面内容。 回答流程 1. 先用 search_pages 找到候选页面 2. 判断哪些候选页最可能包含答案用 get_page 打开 3. 如果页面中有相关链接或“参见”“详见”字样用 get_linked_pages 或 get_backlinks 继续翻页 4. 跨页面获取信息后组合答案并标注每个结论来自哪个页面。 约束 - 最多打开6个页面 - 所有结论必须来自页面内容不要凭常识补充 - 如果页面相互矛盾列出两个页面的说法和更新时间让用户自行判断。这套提示词既给了自由度又限制了它胡跑。实际运行中LLM 一般会先搜 1 次打开 2 到 4 个页面然后作答。成本可控效果稳定。4.5 评估怎么知道这次编译是成功的接完 LLM 之后一定要做评估否则你只是又多了一个“看起来能聊”的机器人。我常用的方法是准备一组测试问题每个问题记录三个维度能否定位到正确的页面答案是否完全来自知识库还是掺杂了模型自身知识用户能不能根据回答里的引用找到原始文档这组测试问题不需要多二十个就够。先把预期命中的页面 ID 提前标好跑一轮看命中率再让一个人对照原始文档核对答案的溯源链。命中率低于 70% 时不要急着调提示词回去看页面粒度是否合理、摘要是否清晰、链接是否准确。这个循环做两三轮后知识库的质量会明显上升。5. 实测中的坑编译不是一次性的活儿5.1 粒度没定好检索再强也白搭我最早编译一个规范库时把整份“部署规范”作为一页塞了进去结果 LLM 打开页面后上下文瞬间超过窗口能有效处理的长度回答时抓不住重点。后来我把“部署规范”拆成“环境准备”“发布流程”“回滚预案”“常见问题”四个页面再建一个“部署规范概览”作为导航页效果立刻好起来。反面例子也有。我试过把“支付”相关的内容拆得非常细连“支付金额格式”都单独成页结果 LLM 为了回答一个简单问题要翻五六页中间只要有一页链接不准整条推理链就断了。页面的标准不是“小”而是“独立成题”。5.2 链接质量比链接数量重要链接建得越多不代表 Wiki 越好。我曾经让脚本自动给所有出现“支付”这个词的页面相互加链接结果 LLM 从“订单状态页”出发跳到了“支付金额格式说明”一页比一页远最后答非所问。原因是链接是语义关系不是词汇共现。后来我加了规则只有同一主题域内、真正存在“详见/参见/依赖/前置条件”关系的页面才能建立双向链接脚本只提候选核心链接必须人工确认。可以接受某些页面没有出链但绝不能有一堆误导性的错链。检索时把跳转步数上限设为 6 也很有用能让 LLM 在恶意绕路时停下来重新搜索。5.3 知识会过期Wiki要能重新编译知识库最大的隐性敌人是过期。产品改了字段、流程换了负责人、系统下线了旧接口如果 Wiki 不更新LLM 会非常自信地把过时信息当成正确答案说出来这比搜不到更危险。我的做法是持续编译而不是一锤子编译。页面文件更新后脚本自动重新生成index.json并检测三类异常没有updated字段的页面、超过 180 天未更新的页面、没有任何链接引用的孤立页面。这些异常会进入人工巡检清单。对已经确定废弃的内容我会把状态改成deprecated不是删除因为历史问题可能还需要被检索追溯。5.4 权限问题不能等接LLM再想团队知识库一定有权限边界有些内部原理只有某些岗位能看有些故障复盘不是所有人都该读。传统知识库靠登录态和目录权限就能挡住但把检索权交给 LLM 之后如果检索工具直接返回所有页面就相当于给模型安了一个越权通道。我在所有检索工具里都加了一个user_context参数调用时从请求里取出当前用户的角色和权限组查询索引时过滤掉无权访问的页面。这样既保证了模型拿不到它不该拿的内容也保证了答案里不会出现敏感信息。权限过滤必须在检索层做不能指望提示词让模型“自觉不看”。6. 什么时候该用LLM-Wiki什么时候该劝退6.1 四种知识库形态的选型对照很多朋友会问那到底选 RAG 还是 KG 还是结构化库还是 Wiki我的判断维度是表达非结构化语义、表达关系、构建成本、精确导航、更新成本。把四种形态放在一起看更直观形态语义召回关系表达构建成本精确溯源更新成本典型场景RAG 知识库强弱低弱低通用语义问答KG 知识库中强高中高实体关系分析结构化知识库弱中中强中精确查询统计LLM-Wiki较强强中强中文档检索、团队知识沉淀这个表格不是我拍脑袋定的是我在实际项目中反复对比后的结论。RAG 是最快能跑起来的方案但如果你的文档天然有引用关系、答案需要跨页拼接RAG 的弱点就会放大KG 能表达关系但构建太重适合实体密度高的领域结构化库适合事实型查询但装不下叙事型文档。LLM-Wiki 恰好适合大多数“以自然语言为主、页面之间有引用”的知识场景。6.2 我眼中LLM-Wiki的甜点场景我从自己实践过的项目中总结了三个比较合适的场景。第一个是技术团队内部 Wiki。架构设计、发布流程、故障预案、规范约定这些内容互相引用极其频繁。工程师问“新服务上线要走哪些流程”LLM 会从“接入总览页”出发顺链接翻到“配置中心”“监控告警”“发布审批”最后给出完整路线。第二个是产品手册和帮助中心。FAQ 之间经常会写“参见”“相关问题”用 Wiki 表达后用户问一个复杂操作LLM 能把多个 FAQ 页面串联成一个步骤清晰的答案并且每个步骤都能给出原文出处。第三个是合规性审查和溯源要求高的场景。比如审计时需要回答“这个安全策略的变更历史是什么”LLM-Wiki 的页面级溯源能力比碎片化 RAG 可靠得多它能明确告诉你这句话来自哪个页面的哪一段不会东拼西凑。6.3 硬上的结果很惨的情况也有几种场景我不建议上 LLM-Wiki。第一类是资料以图片、音频、扫描件为主没有文本页面可以链接强行做只会得到一个空壳第二类是业务数据必须精确到数字比如订单金额、库存数量这类应该走结构化数据库而不是 WikiLLM 的文本回答再漂亮也不能拿来记账第三类是知识库本身很小只有几篇文档那直接让 LLM 读全文就行没必要建图。还有一类更容易踩坑内容还在快速变动、每天都有大量新增页面但没人维护链接和摘要。这种情况下编译出来的 Wiki 很快就会长成一片杂草LLM 翻着翻着就迷路。我的经验是LLM-Wiki 适合知识已经相对稳定、愿意投入人力维护的组织而不是一个“把文档丢进去就完事”的自动化系统。最后聊点个人体会。我一开始把编译理解成“格式转换”后来发现真正的编译动作是把人的阅读习惯翻译成机器可遍历的结构——打开总览、顺着链接走、停在不相关的页面时折返、最后把多个来源的信息拼成结论。第一次从日志里看到 LLM 连续调用了search_pages、get_page、get_linked_pages时我突然有种“它在真的翻手册”的感觉。如果你也想试建议别从几千页的大库开始先挑一个三十页左右的小知识库跑通链路重点观察它有没有沿着链接找到你没提前喂给它的上下文页面。能走出来这套方案就成了。
返回列表