
1. 从“存知识”到“编译知识”LLM Wiki 到底在解决什么问题第一次看到“LLM Wiki”这个说法很多人会下意识把它理解成“又一个知识库工具”。但如果你真的搭过几套 RAG 系统就会明白它想解决的痛点完全不在“存”这个层面。传统知识库的核心逻辑是把文档切块、向量化、存进向量数据库用户提问时召回最相似的几个片段塞给大模型生成答案。这套流程在 Demo 阶段看起来很美好一旦进入真实场景问题就暴露了——召回片段之间没有结构关系模型拿到的是几段孤立的文字遇到需要跨文档推理、需要理解概念层级、需要追溯知识来源的问题时回答质量断崖式下跌。LLM Wiki 的思路是把“知识库”升级成“知识编译器”。这个比喻很关键编译器不是简单地存储源代码而是把源代码解析成抽象语法树、做语义分析、生成中间表示、最终产出可执行目标代码。对应到知识管理上LLM Wiki 不再把文档当成一堆待检索的文本块而是让大模型主动参与知识的抽取、结构化、关联和持续维护最终形成一套可读、可查、可推理、可追溯的知识网络。换句话说传统 RAG 是“检索增强生成”LLM Wiki 更接近“编译增强生成”——先编译再检索最后生成。这套思路适合谁如果你只是想让模型回答几个 FAQ传统 RAG 足够了。但如果你面对的是持续演化的领域知识、需要多跳推理的复杂问题、或者要求答案能追溯到具体概念和关系的场景那 LLM Wiki 值得认真研究。它尤其适合技术团队做内部知识中枢、研究机构做领域知识沉淀、以及个人做长期可维护的第二大脑。下面我会从整体设计、核心细节、实操落地、问题排查四个层面把这套东西拆开讲清楚。2. 整体设计与思路拆解为什么不能只做向量检索2.1 传统 RAG 的三个结构性瓶颈先说清楚传统 RAG 到底卡在哪里不然没法理解 LLM Wiki 为什么要这么设计。第一个瓶颈是语义碎片化。文档被切成 500 到 1000 字的块之后每个块都是孤立的。一个概念的定义可能在 A 文档它的应用场景在 B 文档限制条件在 C 文档。用户问“这个概念在什么情况下不适用”向量检索可能只召回 B 和 C漏掉 A 里的关键前提模型就开始编。第二个瓶颈是关系缺失。向量相似度衡量的是文本表面的语义接近程度它不知道“A 是 B 的子类”“C 依赖 D”“E 和 F 互斥”这些结构化关系。遇到需要沿关系链推理的问题纯向量检索基本无能为力。第三个瓶颈是知识不更新。文档改了你得重新切块、重新向量化、重新入库。如果知识之间有交叉引用改一处可能影响多处但向量库不会自动帮你维护一致性。2.2 LLM Wiki 的核心设计哲学LLM Wiki 的设计哲学可以用一句话概括让大模型做知识的编译器和维护者而不是只做最后的答题者。具体来说它把整个流程拆成四个阶段。第一阶段是知识抽取用 LLM 从原始文档中识别出实体、概念、定义、关系、约束条件输出结构化的知识单元。第二阶段是知识编译把这些知识单元组织成有层级的 Wiki 结构每个条目有明确的标题、定义、属性、关联条目和来源引用。第三阶段是知识检索用户提问时先定位到相关 Wiki 条目再沿条目间的链接做有限跳转收集足够上下文。第四阶段是知识维护当新文档进来或旧文档更新时LLM 负责判断哪些条目需要新增、修改或废弃并保持引用一致性。这个设计和传统 RAG 最大的区别在于知识在入库时就已经被结构化了检索时拿到的是有组织的知识单元而不是散落的文本块。这就像图书馆传统 RAG 是把书撕成页随便堆着LLM Wiki 是编好目录、做好索引、标好交叉引用。2.3 方案选型为什么是 Wiki 而不是纯图数据库有人会问既然要结构化为什么不直接上知识图谱图数据库确实能表达关系但纯图结构对普通用户不友好而且构建和维护成本高。LLM Wiki 选择了一条中间路线用 Wiki 的页面结构承载知识用页面内的结构化字段和页面间的链接表达关系。这样做的好处是Wiki 天然适合人读也适合机器解析。每个页面就是一个知识单元页面标题是概念名正文是定义和说明元数据字段记录属性链接指向相关概念。LLM 可以很容易地把一个页面编译成一段上下文也可以沿着链接做多跳检索。相比纯图数据库Wiki 的编辑和维护门槛低得多而且版本控制、diff、回滚这些能力可以直接复用现成工具。另一个关键选型是编译时机。LLM Wiki 选择在入库时编译而不是查询时编译。查询时编译虽然灵活但每次提问都要跑一遍 LLM 做抽取和结构化延迟高、成本高、结果还不稳定。入库时编译是一次性投入之后检索就是查表加有限跳转速度和稳定性都好得多。当然入库编译对抽取质量要求更高后面会讲怎么保证。3. 核心细节解析与实操要点知识单元怎么设计3.1 知识单元的最小结构LLM Wiki 的基本单位是知识条目一个条目对应一个概念、实体或方法。每个条目至少包含以下字段字段名类型说明是否必填titlestring条目名称唯一标识是aliaseslist别名、缩写、同义词否definitiontext一句话定义是bodymarkdown详细说明是attributesdict结构化属性键值对否relationslist指向其他条目的关系否sourceslist来源文档和位置是confidencefloat置信度 0-1否last_updateddate最后更新时间是这个结构看起来简单但每个字段的设计都有讲究。title必须唯一因为它是检索的锚点。aliases解决同一概念多种叫法的问题比如“大语言模型”和“LLM”应该指向同一个条目。definition要求一句话说清楚逼着 LLM 做提炼避免把整段原文抄进来。relations是关系表达的核心常见关系类型包括is_a、part_of、depends_on、contradicts、used_for等。3.2 关系类型的设计原则关系类型不能随便定否则会变成一锅粥。我的经验是关系类型应该满足三个条件方向明确、语义单一、可传递性清晰。方向明确是指 A 到 B 的关系和 B 到 A 的关系必须区分。比如is_a是单向的“苹果是一种水果”不能反过来说“水果是一种苹果”。语义单一是指一个关系类型只表达一种含义不要用related_to这种万能关系它等于没说。可传递性清晰是指要明确哪些关系可以传递比如is_a可以传递苹果是水果水果是食物所以苹果是食物但depends_on不一定能传递。实操中我建议先定义 8 到 12 种核心关系覆盖大部分场景然后根据领域需要扩展。下面是一组经过验证的关系类型is_a子类关系可传递part_of部分整体关系可传递depends_on依赖关系不可传递causes因果关系不可传递contradicts矛盾关系对称used_for用途关系不可传递derived_from派生关系可传递similar_to相似关系对称但不可传递3.3 抽取提示词的关键设计知识抽取的质量直接决定整个系统的上限。提示词设计有几个关键点。第一要求 LLM 输出结构化 JSON而不是自由文本。JSON 便于程序解析和校验也逼着 LLM 按字段组织信息。第二给出明确的抽取规则和示例。比如“只抽取文档中明确陈述的知识不要推断”“如果同一概念多次出现合并为一个条目”“关系必须指向文档中已出现的概念”。第三要求标注来源位置。每个知识条目必须附带原文出处方便追溯和验证。第四设置置信度字段。让 LLM 对自己抽取的内容打分低置信度的条目后续人工复核。一个实际可用的抽取提示词骨架是这样的你是一个知识抽取器。请从以下文档中抽取知识条目输出 JSON 数组。 每个条目包含 - title: 概念名称唯一 - aliases: 别名列表 - definition: 一句话定义不超过 50 字 - body: 详细说明保留原文关键信息 - attributes: 结构化属性 - relations: 关系列表每项包含 type 和 target - sources: 来源位置格式为 [文档名, 段落号] - confidence: 0-1 之间的置信度 抽取规则 1. 只抽取文档明确陈述的内容不要推断 2. 同一概念只出现一次合并所有相关信息 3. 关系类型从以下列表选择is_a, part_of, depends_on, causes, contradicts, used_for, derived_from, similar_to 4. 如果文档中没有足够信息填充某字段留空而不是编造 文档内容 {document}3.4 编译阶段的去重与合并抽取出来的条目会有重复和冲突编译阶段要处理这些问题。去重的核心是判断两个条目是否指向同一概念。简单做法是用标题和别名字符串匹配但不够可靠。更稳的做法是用嵌入向量算相似度超过阈值就认为是同一概念然后合并。合并时要注意几点。definition取更准确的那个或者让 LLM 重新生成一个综合版本。body做并集但要去掉重复内容。relations做并集冲突的关系要标记出来人工确认。sources全部保留因为多个来源能提高可信度。confidence取加权平均来源越多、置信度越高。注意去重阈值不要设得太低否则会把相关但不同的概念合并。我的经验是余弦相似度 0.92 以上才考虑合并0.85 到 0.92 之间标记为“疑似重复”人工复核。4. 实操过程与核心环节实现从零搭一套 LLM Wiki4.1 环境准备与工具选型先列一下我实际用到的工具栈都是开源或免费可用的。环节工具用途备注文档解析MarkItDown / Unstructured把 PDF、Word、HTML 转成 MarkdownMarkItDown 轻量Unstructured 格式支持全知识抽取任意支持 JSON 输出的 LLM从文档抽取知识条目本地可用 Ollama 跑小模型云端可用主流 API存储SQLite 文件系统存条目元数据和 Markdown 正文简单可靠方便版本控制向量检索FAISS / Chroma条目级语义检索条目数量少时 FAISS 足够Wiki 渲染MkDocs / Obsidian把条目渲染成可浏览的 WikiObsidian 适合个人MkDocs 适合团队编排Python 脚本串联整个流水线不需要复杂框架脚本足够选型逻辑很简单能用文件系统就不用数据库能用脚本就不用框架。LLM Wiki 的核心复杂度在知识建模和抽取质量上不在工程架构上。过早引入重型框架只会增加调试成本。4.2 文档解析与预处理第一步是把各种格式的文档统一转成 Markdown。这一步看起来简单但坑不少。PDF 里的表格、公式、多栏排版经常解析错乱扫描件还需要 OCR。我的做法是优先用 MarkItDown 处理结构化文档遇到复杂 PDF 用 Unstructured 的 hi_res 模式扫描件走 OCR 流程。预处理阶段要做几件事。去掉页眉页脚和页码这些内容会污染抽取结果。合并被错误分段的段落PDF 解析经常把一句话拆成多行。保留标题层级因为标题是判断概念层级的重要线索。给每个段落编号方便后续标注来源位置。import re def preprocess_markdown(text): # 去掉页码行 text re.sub(r^\s*\d\s*$, , text, flagsre.MULTILINE) # 合并被拆断的句子 text re.sub(r([^\n。.!?])\n([^\n#\-\d]), r\1\2, text) # 给段落编号 paragraphs text.split(\n\n) numbered [] for i, p in enumerate(paragraphs): if p.strip(): numbered.append(f[P{i}] {p.strip()}) return \n\n.join(numbered)4.3 知识抽取的批量处理与限流文档多的时候抽取要批量跑。这里有几个实操要点。按文档分批每批不超过 LLM 的上下文窗口留出提示词和输出的空间。并发控制本地模型并发 2 到 4 路云端 API 看限流策略一般 5 到 10 路。失败重试JSON 解析失败或字段缺失时重试重试两次还失败就记录到待处理队列。import json from concurrent.futures import ThreadPoolExecutor def extract_batch(doc_chunk, llm_client, max_retries2): for attempt in range(max_retries 1): try: response llm_client.generate( promptEXTRACT_PROMPT.format(documentdoc_chunk), temperature0.1, response_format{type: json_object} ) data json.loads(response) return validate_entries(data) except (json.JSONDecodeError, KeyError) as e: if attempt max_retries: log_failure(doc_chunk, str(e)) return [] return [] def process_documents(docs, llm_client, workers4): all_entries [] with ThreadPoolExecutor(max_workersworkers) as executor: futures [executor.submit(extract_batch, d, llm_client) for d in docs] for f in futures: all_entries.extend(f.result()) return all_entries温度设 0.1 是为了让输出稳定抽取任务不需要创造性。response_format强制 JSON 输出能大幅降低解析失败率。4.4 条目合并与 Wiki 生成抽取完成后把所有条目汇总做去重合并。合并后的条目写入 SQLite同时每个条目生成一个 Markdown 文件文件名用条目 ID。Markdown 文件里用[[条目名]]的语法做内部链接这样 Obsidian 或 MkDocs 能自动渲染成可点击的 Wiki。def merge_entries(entries, similarity_threshold0.92): merged [] for entry in entries: matched find_similar(entry, merged, similarity_threshold) if matched: merged[merged.index(matched)] merge_two(matched, entry) else: merged.append(entry) return merged def write_wiki(entries, output_dir): for entry in entries: filename f{entry[id]}.md content render_markdown(entry) with open(f{output_dir}/{filename}, w) as f: f.write(content)render_markdown负责把条目结构转成 Markdown包括 frontmatter 元数据、定义、正文、关系链接和来源引用。frontmatter 用 YAML 格式方便后续程序读取。4.5 检索流程的实现检索分两步。第一步是条目定位用用户问题的嵌入向量在条目向量库里找最相似的 N 个条目。第二步是上下文扩展从定位到的条目出发沿关系链接跳转 1 到 2 跳收集相关条目。最后把收集到的条目按相关度排序拼成上下文送给 LLM 生成答案。def retrieve(query, entry_index, entry_store, top_k5, max_hops2): query_vec embed(query) candidates entry_index.search(query_vec, top_k) collected set() frontier [c[id] for c in candidates] for hop in range(max_hops): next_frontier [] for eid in frontier: if eid in collected: continue collected.add(eid) entry entry_store.get(eid) for rel in entry.get(relations, []): next_frontier.append(rel[target]) frontier next_frontier return [entry_store.get(eid) for eid in collected]跳数不要太多2 跳通常足够。跳数多了会引入不相关条目反而干扰生成。5. 常见问题与排查技巧实录5.1 抽取质量不稳定的排查思路抽取质量不稳定是最常见的问题表现是同一批文档跑两次抽出来的条目数量和内容差异很大。排查顺序是这样的。先看提示词是否足够明确。如果提示词里没有给出字段定义和抽取规则LLM 每次理解都不一样。解决方法是把规则写死给出正例和反例。再看温度是否太高抽取任务温度应该设 0 到 0.2。然后看文档预处理是否干净页眉页脚、乱码、错误分段都会干扰抽取。最后看模型能力是否够小模型在复杂文档上确实力不从心可以考虑换更大的模型或者把文档拆得更细。5.2 条目重复和冲突的处理条目重复的根源是同一概念在不同文档里叫法不同或者 LLM 对同一概念给出了不同的标题。处理方法是建立别名表把已知的同义词映射到标准名。别名表可以手工维护也可以让 LLM 从已有条目里自动发现。条目冲突是指两个条目对同一概念给出了矛盾的定义或属性。这种情况不要自动合并应该标记出来人工裁决。我的做法是在 Wiki 里加一个conflicts字段列出冲突的条目 ID然后在渲染时高亮显示。5.3 检索命中率低的优化检索命中率低通常有三个原因。条目粒度不对条目太大或太小都会影响检索。太大则一个条目包含多个概念检索时定位不准太小则信息不完整需要跳很多跳。我的经验是每个条目对应一个明确的概念正文控制在 200 到 800 字。嵌入模型不适合领域通用嵌入模型在专业领域上表现会下降可以考虑用领域数据微调或者用 LLM 做重排序。关系链接缺失如果条目之间没有建立关系多跳检索就退化成单跳上下文不够丰富。5.4 常见问题速查表问题现象可能原因排查方法解决方案抽取条目数量异常少提示词太严格或文档预处理过度检查提示词和预处理输出放宽提示词减少预处理抽取条目大量重复别名未归一化统计标题相似度分布建立别名表提高去重阈值JSON 解析频繁失败模型输出不稳定查看原始输出降低温度强制 JSON 格式增加重试检索结果不相关条目粒度过大或嵌入模型不匹配人工检查召回条目拆分条目换嵌入模型加重排序多跳检索引入噪声跳数过多或关系类型太泛检查跳转路径减少跳数收紧关系类型Wiki 渲染链接失效条目名变更未同步检查链接目标是否存在用 ID 做链接渲染时解析为名称5.5 几个踩过的坑第一个坑是过早追求全自动化。一开始我想让整个流程全自动跑结果抽取质量差、合并错误多最后还是要人工介入。后来改成半自动抽取和合并自动跑但低置信度条目和冲突条目进人工复核队列。这样整体效率反而更高。第二个坑是关系类型定太多。一开始我定了 30 多种关系结果 LLM 经常选错而且关系太细导致链接稀疏。后来砍到 10 种核心关系链接密度上来了检索效果也好了。第三个坑是忽略来源追溯。早期版本没存来源位置后来发现答案有问题时根本查不到是哪个文档哪一段出的错。加上来源标注后排查效率提升明显。第四个坑是用通用嵌入模型做领域检索。在技术文档上还行换到医疗、法律领域就明显不够用。后来改成先用通用模型粗筛再用领域 LLM 做重排序命中率提升了不少。6. 知识编译的进阶玩法与个人体会6.1 增量编译与版本管理知识库不是一次建完就完事文档会更新新知识会进来。增量编译的核心是判断哪些条目受影响。新文档进来时先抽取条目然后和已有条目做匹配。匹配上的走合并流程匹配不上的走新增流程。文档更新时先找到该文档贡献的所有条目重新抽取后对比差异有变化就更新。版本管理直接用 Git 就行。每个条目一个 Markdown 文件每次编译提交一次diff 清晰可见。回滚也方便git revert就能恢复到任意版本。这比在数据库里做版本管理简单得多。6.2 用 LLM 做知识质量巡检知识库跑一段时间后会有各种质量问题过时信息、矛盾条目、孤立条目、断链。可以定期跑一个巡检脚本让 LLM 检查这些问题。比如让 LLM 读一批条目找出定义模糊、关系可疑、来源缺失的条目输出待修复列表。这个巡检不需要很频繁一个月跑一次就够。6.3 个人使用体会我用这套方法维护自己的技术知识库已经有一段时间了最大的感受是前期投入值得。刚开始搭建时确实比直接上 RAG 麻烦要设计条目结构、调抽取提示词、处理合并冲突。但一旦跑通后续维护成本低很多。新文档进来跑一遍编译流程知识就自动归位了。检索时拿到的是有组织的知识而不是散落的文本块回答质量明显更稳。另一个体会是不要追求完美。知识库永远有改进空间条目可以更细、关系可以更准、来源可以更全。但如果一直纠结这些就永远建不起来。我的策略是先跑通最小闭环然后在使用中持续迭代。哪些条目经常被检索到但质量差就优先修哪些。哪些关系经常被跳转但指向错误就优先改哪些。用起来的知识库才是好知识库。最后分享一个小技巧给条目加一个“使用频率”字段每次被检索到就加一。定期看高频条目优先保证它们的质量。低频条目可以放一放等有需要时再完善。这样能把有限的精力花在最有价值的地方。