☰
LLM-Wiki:把知识库编译成超文本,让大模型自主翻书检索
2026/10/8 10:59:35 网站建设 项目流程

从年初开始,我把团队的知识库从一堆 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检索的Wiki

4.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(encoding="utf-8") title = extract_title(text) aliases = re.findall(r"aliases: \[(.+?)\]", 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", encoding="utf-8") as f: json.dump(pages, f, ensure_ascii=False, indent=2)

脚本跑完后,我会用文本编辑器检查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。第一类是资料以图片、音频、扫描件为主,没有文本页面可以链接,强行做只会得到一个空壳;第二类是业务数据必须精确到数字,比如订单金额、库存数量,这类应该走结构化数据库而不是 Wiki,LLM 的文本回答再漂亮也不能拿来记账;第三类是知识库本身很小只有几篇文档,那直接让 LLM 读全文就行,没必要建图。

还有一类更容易踩坑:内容还在快速变动、每天都有大量新增页面,但没人维护链接和摘要。这种情况下编译出来的 Wiki 很快就会长成一片杂草,LLM 翻着翻着就迷路。我的经验是,LLM-Wiki 适合知识已经相对稳定、愿意投入人力维护的组织,而不是一个“把文档丢进去就完事”的自动化系统。

最后聊点个人体会。我一开始把编译理解成“格式转换”,后来发现真正的编译动作是把人的阅读习惯翻译成机器可遍历的结构——打开总览、顺着链接走、停在不相关的页面时折返、最后把多个来源的信息拼成结论。第一次从日志里看到 LLM 连续调用了search_pages、get_page、get_linked_pages时,我突然有种“它在真的翻手册”的感觉。如果你也想试,建议别从几千页的大库开始,先挑一个三十页左右的小知识库跑通链路,重点观察它有没有沿着链接找到你没提前喂给它的上下文页面。能走出来,这套方案就成了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询