【大模型、工程实践】LLM Wiki 技术原理剖析:三层架构与四阶段检索管线
本文是「LLM Wiki 实战」系列第 4 篇(完结篇)。
前三篇分别讲了入门全流程、火电报告实战、四大亮点。本篇把镜头拉到底层,讲透整个系统是怎么运作的:三层架构的职责边界、四阶段检索管线的数据流、关联度算法的计算、级联删除与可靠性工程。适合想理解「为什么这样设计」的读者,也是你二次开发或借鉴思路的参考。
一、三层架构:不可变 / LLM 生成 / 规则
整个系统建立在一个清晰的三层模型上,每一层的所有权和可变性都不同:
| 层 | 角色 | 可变性 | 谁来写 |
|---|---|---|---|
| Raw sources | 原始资料(真相之源) | 不可变 | 人类(投放/删除) |
| Wiki | LLM 生成的结构化知识 | 持续更新 | LLM(人类只读+策展) |
| Schema | 结构规则与配置 | 协同演进 | 人类 + LLM 共同维护 |
对应到实际目录:
my-wiki/
├── raw/sources/ # Raw 层:你导入的 PDF/DOCX/MD,LLM 只读不改
├── wiki/ # Wiki 层:entities/ concepts/ sources/ ... 全部 LLM 生成
├── purpose.md # Schema 层的一部分:方向意图
├── schema.md # Schema 层:结构规则、页面类型、命名规范
└── .llm-wiki/ # 应用状态:聊天、审核项、配置(非知识)
为什么强调不可变? Raw 层是 source of truth。LLM 永远不修改原始资料,只从中抽取信息写进 Wiki。这意味着:任何 Wiki 内容都可以通过 frontmatter 的 sources: [] 追溯回原始资料;原始资料删了,相关 Wiki 内容可以精确级联清理(见第五节)。这种「读源 / 写派生」的隔离,是知识库可信度的根基。
为什么 LLM 独占 Wiki 层? 知识库维护的痛点不是读和想,而是「记账」——更新交叉引用、保持摘要最新、标注矛盾、维护几十个页面的一致性。人类会因为维护成本随规模暴涨而放弃。把 Wiki 层的所有权交给 LLM,维护成本就被压到接近零。人类的职责是策展(选资料、提好问题、定 purpose/schema),LLM 负责其余。
二、三大操作:Ingest / Query / Lint
三层的存在,是为了支撑三个核心操作:
Ingest(摄入) —— 你往 Raw 层投放新资料,LLM 读它、抽取信息、整合进 Wiki(两步摄入,见系列第 3 篇)。一份资料可能触动 10~15 个 wiki 页面。
Query(查询) —— 你向 Wiki 提问,LLM 检索相关页面、读取、综合出带引用的回答。关键洞察:好的回答会被回填进 Wiki(存到 wiki/queries/),让探索成果和原始资料一样累积。
Lint(检查) —— 定期健康检查:矛盾、过时结论、孤儿页、缺失交叉引用、可联网补全的空白。这是知识库的「体检」。
注意三者都不直接碰 Raw 层的写(Raw 不可变),都通过 Wiki 层产出。这是一个单向数据流:Raw → Wiki → (Query/Lint 反馈进 Wiki)。
三、index.md 与 log.md:为可扩展性设计
随着 Wiki 增长,有两个特殊文件帮 LLM(和你)导航。它们职责不同,刻意分开:
index.md —— 内容导向(逻辑视图)
Wiki 的目录,每个页面一行带 [[wikilink]] 和一句话描述,按类型分组。LLM 每次摄入都更新它;查询时先读 index 定位相关页,再钻进去读。在中等规模(~100 份资料、几百页)下,这套「index + 图谱扩展」足够好用,避免了上 embedding RAG 基建的必要性。
log.md —— 时序导向(历史视图)
append-only 的操作流水账。设计上有个为脚本化考虑的细节:每条用统一前缀,使其可用 unix 工具解析:
grep "^## \[" log.md | tail -5 # 最近 5 条操作
为什么要分两个文件?因为「按内容查」和「按时间查」是两种正交需求,塞一个文件里会互相干扰。index 让 LLM 快速定位「有哪些内容」,log 让 LLM 知道「最近做了什么」——查询时读 index,摄入时写 log,职责清晰。
四、四阶段检索管线(核心)
Query 操作背后是一条精心设计的多阶段管线。这是整个系统技术含量最高的部分。
用户提问
│
▼
┌─────────────────────────────────────────┐
│ 阶段 1:分词搜索 │
│ 英文:分词 + 停用词过滤 │
│ 中文:CJK 二元组分词 │
│ 标题命中 +10 分 │
│ 同时搜 wiki/ 和 raw/sources/ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 阶段 1.5:向量语义搜索(可选) │
│ OpenAI 兼容 /v1/embeddings 端点 │
│ LanceDB(Rust 嵌入式)ANN 检索 │
│ 余弦相似度,发现无关键词重叠的语义相关页│
│ 结果合并:增强已有匹配 + 补充新发现 │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 阶段 2:图谱扩展 │
│ 搜索结果 = 种子节点 │
│ 四信号关联度模型发现相关页 │
│ 2 跳遍历,带衰减 │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 阶段 3:预算控制 │
│ 可配置上下文窗口 4K → 1M tokens │
│ 比例分配:60% Wiki / 20% 历史 / │
│ 5% 索引 / 15% 系统提示 │
│ 页面按 搜索分 + 图谱关联度 综合排序 │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 阶段 4:上下文组装 │
│ 编号页面附【完整内容】(非摘要) │
│ 系统提示含 purpose / 语言规则 / │
│ 引用格式 / index │
│ LLM 被指示按 [1] [2] 编号引用 │
└─────────────────────────────────────────┘
│
▼
带编号引用的回答 + 引用面板
逐阶段拆:
阶段 1:分词搜索
这是兜底的基础检索,中英文处理方式不同:
- 英文:标准分词 + 停用词过滤(the/a/is 这类)
- 中文:CJK 二元组(bigram)分词 —— 把连续汉字按相邻两字切分。比如「深度调峰」→
["深度", "度调", "调峰"]。这是无需词典的粗暴但有效的方案,对小规模知识库够用,避免了引入中文分词库的复杂度。 - 标题命中加 10 分 —— 标题匹配比正文匹配更重要,加权。
- 双域搜索 —— 同时搜
wiki/(LLM 综合)和raw/sources/(原文),兼顾加工后的知识和原始细节。
阶段 1.5:向量语义搜索(可选)
分词搜索的硬伤是关键词不重叠就搜不到——比如搜「频繁启停」,匹配不到只写了「变负荷循环」的页面。向量搜索解决这个:
- 通过任意 OpenAI 兼容的
/v1/embeddings端点生成 embedding(可接本地或国产模型) - 存在 LanceDB(Rust 嵌入式向量库,
src-tauri/src/commands/vectorstore.rs)里做快速 ANN 检索 - 用余弦相似度发现语义相关页
- 结果与阶段 1 合并:增强已有匹配 + 补充新发现
关键工程决策:向量搜索默认关闭,在设置里独立配置端点、Key、模型。关闭时管线自动 fallback 到「分词 + 图谱扩展」,系统仍完整可用。这是「渐进增强」的典型做法——不强依赖向量基建。
官方基准:开启向量搜索后,整体召回率从 58.2% 提升到 71.4%。
阶段 2:图谱扩展
把搜索结果当种子节点,用四信号关联度模型(上一篇讲过)发现相关页面,做 2 跳遍历带衰减:
- 第 1 跳:种子的直接邻居
- 第 2 跳:邻居的邻居
- 衰减:越远的页面贡献越小
这一步把「关键词没搜到、但和搜到的页面强相关」的页面捞回来——比如搜到「水冷壁」,图谱扩展会带出「水动力稳定性」「质量流速」等关联页,即使提问里没提这些词。
阶段 3:预算控制
不同 LLM 上下文窗口差异巨大(4K 到 1M tokens),不能无脑把所有相关页塞进去。这一步做比例预算分配:
总预算(可配 4K~1M)
├── 60% → Wiki 页面内容
├── 20% → 聊天历史
├── 5% → 索引(index.md)
└── 15% → 系统提示(purpose + 语言规则 + 引用格式)
页面按「搜索分 + 图谱关联度」综合排序,在 60% 预算内尽量多塞高相关页。更大的窗口按比例获得更多 Wiki 内容——窗口越大,能参考的知识越多,但比例结构不变。
阶段 4:上下文组装
最后把检索到的内容拼成 prompt:
- 编号页面附完整内容(不是摘要)——给 LLM 充足的原文,避免它二次「缩水」
- 系统提示塞入:
purpose.md(方向)、语言规则(中文/英文)、引用格式约束、index.md(全局目录) - 明确指示 LLM 按编号引用
[1] [2]—— 这样回答里的每个引用都能映射到具体页面,可追溯、可核对
这四步合起来,就是「问一句 → 拿到带可信引用的综合回答」的全过程。
五、关联度计算与级联删除
四信号关联度的计算
回看上一篇的四信号模型,给个具体算例。假设页面 A(概念:深度调峰)和页面 B(实体:水冷壁):
| 信号 | 命中情况 | 计算 | 得分 |
|---|---|---|---|
| 来源重叠 | 共享 1 份报告 | 命中 × 4.0 | 4.0 |
| 直接链接 | 有 [[wikilink]] |
命中 × 3.0 | 3.0 |
| Adamic-Adar | 共享 2 个邻居(度数 3 和 5) | (1/ln3 + 1/ln5) × 1.5 ≈ 2.30 | 2.30 |
| 类型亲和 | 不同类型(概念 vs 实体) | 0 × 1.0 | 0 |
| 合计 | 9.30 |
Adamic-Adar 标准公式:对两个节点的每个共同邻居,贡献
1 / log(degree(邻居)),邻居度数越高贡献越小(因为高度数邻居的「连接」信息量低)。这个分数既用在图谱边的视觉权重,也用在阶段 2 的图谱扩展排序。
级联删除:删一份资料会发生什么
Raw 层删一份 PDF,Wiki 层不能留下指向它的孤儿内容,但又不能误删被多份资料共享的实体。LLM Wiki 用三重匹配找到所有相关 Wiki 页面:
- frontmatter 的
sources: []字段 - 资料摘要页的名称
- frontmatter 的章节引用
然后区分处理:
- 资料摘要页(只为这份资料而生)→ 整页删除
- 共享实体/概念页(被多份资料引用)→ 只从
sources[]移除被删的那份,保留页面 - index.md → 清除被删页面的条目
- 其余页面的
[[wikilink]]→ 清理指向已删页面的失效链接
这套机制保证了「删资料」是干净可逆的,不会留下断链,也不会因删一份资料而误伤共享知识。
六、可靠性与跨平台工程
最后是让这一切在真实环境稳定跑起来的工程细节:
摄入可靠性
- SHA256 增量缓存:未变更文件跳过
- 持久化串行队列:落盘、崩溃恢复、失败重试 3 次
- 15 分钟超时:长任务不误判失败
- 保证资料摘要生成:LLM 遗漏时的兜底
跨平台兼容(Rust + Tauri v2)
- 路径规范化
normalizePath():统一反斜杠→正斜杠,在 22+ 个文件中使用,解决 Windows 路径问题 - Unicode 安全字符串切片:按字符而非字节切片,防止中文文件名导致崩溃(字节切片会切断多字节字符)
- 平台差异:macOS 关闭=隐藏后台,Cmd+Q 才真退出;Windows/Linux 关闭弹确认框
- CI/CD:GitHub Actions 自动构建 macOS(ARM+Intel)/ Windows(.msi)/ Linux(.deb/.AppImage)
数据一致性
dataVersion信号:Wiki 内容变更时递增,图谱和 UI 据此自动刷新,避免显示陈旧数据
七、系列小结
四篇走完,我们从「能跑起来」到「能在真实数据上用」到「懂亮点」再到「懂原理」:
- 入门全流程 —— 安装、配置 LLM、导入、问答、图谱、Lint 的完整闭环
- 实战案例 —— 用火电调峰报告演示跨文档问答、图谱聚类、知识空白补全
- 亮点深挖 —— 四信号关联度、MCP 接入 Agent、深度研究、两步摄入
- 技术原理 —— 三层架构、四阶段检索管线、级联删除、跨平台工程(本文)
贯穿始终的核心思想只有一句:让 LLM 承担知识库的维护成本,让人类专注策展和提问。 这是 LLM Wiki 区别于 RAG 的根本——知识被编译一次并持续维护,而不是每次查询都从零推导。
如果打算二次开发或借鉴思路,最值得拿走的三样东西:① 三层不可变/生成/规则的隔离;② 分词+向量+图谱扩展的渐进式检索管线;③ 两步摄入的关注点分离。
系列完结。本系列基于 LLM Wiki v0.4.21(Tauri v2 + React 19),方法论源自 Andrej Karpathy 的 llm-wiki 方法论。
源码与 Releases:github.com/nashsu/llm_wiki · 许可证 GPL-3.0
更多推荐



所有评论(0)