本文是「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 页面:

  1. frontmatter 的 sources: [] 字段
  2. 资料摘要页的名称
  3. 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 据此自动刷新,避免显示陈旧数据

七、系列小结

四篇走完,我们从「能跑起来」到「能在真实数据上用」到「懂亮点」再到「懂原理」:

  1. 入门全流程 —— 安装、配置 LLM、导入、问答、图谱、Lint 的完整闭环
  2. 实战案例 —— 用火电调峰报告演示跨文档问答、图谱聚类、知识空白补全
  3. 亮点深挖 —— 四信号关联度、MCP 接入 Agent、深度研究、两步摄入
  4. 技术原理 —— 三层架构、四阶段检索管线、级联删除、跨平台工程(本文)

贯穿始终的核心思想只有一句:让 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

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐