llm_wiki 技术栈深度拆解——从 RAG 到知识图谱的进化之路

llm_wiki 技术栈深度拆解——从 RAG 到知识图谱的进化之路
君逝生0. 阅读导引
本文面向以下读者:
- 了解 RAG 基本概念,希望进一步理解「知识图谱增强检索」架构的开发者;
- 有前端或 Python 基础,想了解一个完整 AI 桌面应用的跨语言技术选型;
- 正在学习 ChromiaDB / LanceDB 等向量数据库,需要横向对比参考;
- 对 Tauri v2(Rust 桌面壳)替代 Electron 的技术路线感兴趣。
本文假定读者具备基本的编程概念(变量、函数、HTTP 请求),但不要求 Rust、图论或编辑器框架的前置知识。每个概念均从零定义。
1. 项目概述
llm_wiki 是一个采用 GPL-3.0 许可的开源桌面端 AI 知识管理系统,支持 macOS、Windows 与 Linux 三平台。其核心工作流可概括为三步:
摄入:用户导入多格式文档(PDF、DOCX、网页等)→ 系统借助 LLM 自动提取实体与论点 → 生成结构化 Wiki 页面并构建持久化知识图谱。
检索:用户提出自然语言查询 → 系统执行四级混合检索(关键词 + 向量语义 + 图谱扩展 + 上下文预算分配)→ 拼接 prompt 交由 LLM 生成回答。
维护:SHA256 增量缓存确保文档局部修改不触发全量重处理;图谱分析模块自动发现「惊奇连接」与「知识空白」。
工程层面的关键指标:安装包体积 3–10 MB(对比 Electron 典型值 120+ MB),内存占用 20–50 MB(对比 Electron 典型值 150+ MB)。这一差距的核心来源是 Tauri v2 以 Rust 二进制替代了 Node.js 运行时。
2. 系统架构
llm_wiki 采用严格的分层架构,各层之间的职责边界清晰、技术栈独立。先看一张完整的四层逻辑视图:
1 | ┌─────────────────────────────────────────────┐ |
以下 Mermaid 图展示了各层之间的数据流与调用关系:
1 | graph TB |
| 层 | 核心职责 | 关键选型 |
|---|---|---|
| 前端 UI 层 | 用户交互、编辑器、图谱渲染、状态流转 | React 19, Zustand, ProseMirror |
| 桌面壳层 | 系统资源访问、原生性能、进程隔离 | Tauri v2 (Rust) |
| LLM 管线层 | 文档理解、结构化生成、语义编码 | CoT Prompting, Embedding API |
| 存储与索引层 | 持久化、向量检索、增量同步 | LanceDB, SHA256 |
3. 前端技术栈
前端层承载了最复杂的用户交互逻辑,涉及四组相互解耦的技术模块。
3.1 核心框架:React 19 + TypeScript + Vite
| 技术要素 | 角色 | 推荐掌握程度 |
|---|---|---|
| React Hooks(useState / useEffect / useMemo / useCallback) | UI 状态管理的原子单元 | 熟练 |
| TypeScript 泛型与类型推导 | 编译期安全与接口约束 | 熟练 |
| React 19 Server Components 范式 | 理解组件模型的演进方向 | 了解 |
| Vite 构建管线 | 开发热更新与生产打包 | 会用 |
React 19 在桌面端场景下不涉及 SSR,因此与 Web 开发中的 React 19 使用方式有显著差异——无需考虑水合(hydration)与服务端组件串行化问题。
Zustand:不可变状态管理
Zustand 的设计哲学是「将状态视为可变对象的不可变快照」。与 Redux 的 action → reducer → selector 三层间接性不同,Zustand 的 API 更接近原生 JavaScript 对象操作:
1 | import { create } from 'zustand' |
一个 set() 调用即完成状态更新,无需 action type 常量、无需 switch-case reducer、无需 middleware 串联。llm_wiki 使用 Zustand 管理三类高频变化的状态:聊天会话、Wiki 页面集合、以及知识图谱数据的缓存。
3.2 组件体系:shadcn/ui + Tailwind CSS v4
shadcn/ui 的核心设计决策是将组件源码直接复制到项目 src/ 中,而非通过 node_modules 引入黑盒包。这与 Ant Design 或 MUI 存在根本性差异:
| 特征 | 传统 UI 库 (Ant Design / MUI) | shadcn/ui |
|---|---|---|
| 分发方式 | npm 包 | 源码复制 |
| 可修改性 | 通过 props / theme token | 直接修改组件源码 |
| 底层基元 | 自研 | Radix UI(无样式、可访问) |
| 体积 | 全量引入 | 按需复制 |
Tailwind CSS v4 负责原子化样式层。其核心思想是将 CSS 属性映射为单一职责的 class:
1 | <!-- 每个 class 对应一个 CSS 属性,所见即所得 --> |
3.3 富文本编辑器:Milkdown / ProseMirror
这是整个前端层中架构复杂度最高的模块。理解它需要先理解 ProseMirror 的设计理念:
1 | ProseMirror: Schema-driven Editor Framework |
ProseMirror 的核心优势在于:所有编辑操作都经过 Schema 校验,输出永远符合预定义的文档结构。这与基于 contenteditable 或直接操作 DOM 的传统编辑器有本质区别——不存在「非法 DOM 状态」的概念。
3.4 图谱可视化:sigma.js + graphology + ForceAtlas2
| 技术要素 | 职责 |
|---|---|
| graphology | 图数据结构库,提供节点/边的 CRUD、属性管理,内置社区检测算法 |
| sigma.js | 基于 WebGL 的大规模图渲染引擎,利用 GPU 加速 |
| ForceAtlas2 | 力导向布局算法,计算节点的二维坐标以最大化可读性 |
性能关键数字:sigma.js 在消费级 GPU 上可实时渲染 10⁴–10⁵ 量级的节点与边,比 D3-force(基于 SVG,受 DOM 节点数约束)高出一个数量级。这是技术选型的决定性因素——D3 的 SVG 渲染路径在 10³ 节点时即开始出现可感知的帧率下降。
4. 桌面壳层:Tauri v2
4.1 架构对比
1 | Tauri v2 = Rust 后端 (tauri-core) + 平台原生 WebView + IPC 桥接层 |
| 维度 | Electron | Tauri v2 |
|---|---|---|
| 运行时 | Node.js (V8 JIT) | Rust (AOT 编译为原生二进制) |
| 内存基线 | ~150 MB | ~20–50 MB |
| 包体积基线 | ~120 MB | ~3–10 MB |
| 系统 API | 通过 Node.js 原生模块 | 通过 Rust crate 直接 syscall |
| CPU 密集型任务 | 需 Worker 线程或子进程 | 天然异步,不阻塞 UI 线程 |
| 插件生态 | 成熟 | 快速成长中 |
4.2 四个核心抽象
| 概念 | 说明 |
|---|---|
| Command | Rust 侧定义的 #[tauri::command] 函数,前端通过 invoke('name', args) 调用 |
| Event | 双向事件系统——前端可向 Rust 推送,Rust 亦可广播至前端 |
| Plugin | 可复用的功能模块(文件系统、剪贴板、通知等) |
| Capability | v2 新增的声明式权限层,以 JSON 配置控制前端可调用的 Command 白名单 |
Command 调用示例
1 | // src-tauri/src/main.rs —— Rust 后端 |
1 | // src/App.tsx —— TypeScript 前端 |
前端调用的 Rust 函数运行在独立线程上,完全不经过 JavaScript 事件循环。这意味着即使 PDF 解析耗时 3 秒,UI 依然保持 60fps 响应。这也是 llm_wiki 将全部文档解析逻辑放在 Rust 侧的根本原因。
5. LLM 管线:两步思维链摄入
这是 llm_wiki 与传统 RAG 系统最本质的差异点,值得用完整的篇幅展开。
5.1 一步 RAG 的局限
标准 RAG 管道为线性流程:
1 | 文档 → 文本分块 → Embedding → 向量数据库 → 用户查询 → 相似度检索 → 拼接 Prompt → LLM 回答 |
此流程存在三个系统性缺陷:
- 缺乏全局语义:每个 chunk 独立编码,LLM 生成时无法感知文档的全貌结构;
- 无矛盾检测:新摄入内容若与已有知识冲突,RAG 不会主动标记;
- 链接缺失:不同文档之间的隐含关联在检索阶段才被计算,摄入阶段完全丢失。
5.2 两步 CoT 设计
llm_wiki 在摄入阶段引入 LLM 参与,采用 Chain-of-Thought 分步推理:
1 | graph LR |
第一步:分析
LLM 通读完整文档(非分块),提取以下结构化信息:
| 提取维度 | 示例输出 |
|---|---|
| 实体识别 | “LanceDB”、”Rust”、”向量检索”、”Louvain 算法” |
| 核心论点 | “LanceDB 选型的核心原因是嵌入式部署,避免额外服务进程” |
| 矛盾标记 | 与已有页面「向量数据库选型」中的「推荐 ChromiaDB」存在冲突 |
| 新页面建议 | “嵌入式向量数据库对比”、”Tauri 插件开发入门” |
第二步:生成
基于分析层的结构化输出,LLM 执行:
- 创建或更新 Markdown 格式的 Wiki 页面(含 YAML frontmatter);
- 在页面内容中插入
[[wikilinks]]内链; - 更新知识图谱的节点与边(依据四信号模型,详见 §6.1)。
两步设计的额外 token 成本约为 30–50%,但换回的收益是:生成内容的语义一致性显著提升、重复内容减少、矛盾自动标记、图谱结构自动维护。在生产环境中,这 30–50% 的 token 开销通常远小于人工审校的时间成本。
5.3 多模态扩展
对于包含内嵌图片的 PDF 或直接输入的图像文件,管线增加视觉处理分支:
1 | 输入文件 → 文本层提取 → 常规 CoT 管线 |
这使得语义检索能够匹配到图片中描述但未以文本形式存在的概念——例如 PDF 中的架构图、论文中的实验流程图。
5.4 SHA256 增量缓存
1 | 文件 → SHA256(input_bytes) → 查缓存 |
在频繁修改长篇文档的实际使用场景中,此机制可减少 90% 以上的重复 token 开销。实现原理极为简单——仅是一个哈希字典——但工程价值极高。
6. 知识图谱层
llm_wiki 与传统 RAG 之间最核心的架构差异在于此:它维护了一个持久化的、带权重的知识图谱,摄入时建图,检索时用图。
6.1 四信号关联度模型
知识图谱的核心抽象问题:给定两个 Wiki 页面(节点),它们之间的边应该分配多大的权重?
llm_wiki 采用加权多信号模型:
| 信号 | 权重系数 | 语义 | 示例 |
|---|---|---|---|
| 直接 Wiki 链接 | ×3.0 | 页面 A 显式引用 [[页面B]] |
“Python” 链接至 “解释型语言” |
| 来源重叠 | ×4.0 | 两个页面提取自同一源文档 | 一篇 PDF 中提取的 “梯度下降” 与 “反向传播” |
| Adamic-Adar 指数 | ×1.5 | 共享「稀有」邻居越多,越可能语义相关 | 两页面均链接至 “非凸优化”(一个冷门概念) |
| 类型亲和 | ×1.0 | 本体论意义上的类属关系 | “Python” 的类型标签为 “编程语言” |
来源重叠获得最高权重(×4.0)的原因:文档作者在撰写时已天然完成了相关概念的聚类——同一文档中出现的概念拥有高概率的内在关联。这一信号比人工插入的 Wiki 内链(×3.0)更可靠,因为内链的建立依赖于第二步生成的质量。
6.2 Louvain 社区检测
Louvain 算法是图论中应用最广泛的社区发现算法之一,属于贪心优化的层次聚类方法。在 llm_wiki 中,它的输入与输出如下:
1 | 输入: 带权有向图 G = (V, E, w) |
为什么选用 Louvain 而非 K-means?
K-means 要求预设聚类数 k,且其距离度量定义在欧氏空间中。图数据的节点之间不存在坐标嵌入,只有拓扑关系。Louvain 不需要预设社区数量,它通过贪心最大化模块度(modularity)自适应地确定最优划分。模块度本质上衡量的是「社区内部的实际边数」与「随机图中期望边数」的差值——差值越大,社区结构越显著。
当某社区的内聚度 < 0.15 时,系统将其标记为「松散社区」,提示用户该聚类可能是算法伪影而非真正的语义聚类。
6.3 自动图谱洞察
系统基于图结构自动发现两类模式:
两个来自不同社区、在图拓扑中相隔多跳的页面,因共享某个稀有邻居而突然建立强关联。Adamac-Adar 指数在此发挥关键作用——它天然倾向于放大稀有共享邻居的权重。
系统行为:向用户推送通知——「以下两个看似无关的页面可能存在你未曾注意的联系」。
系统识别三种结构脆弱性:
- 孤立页面(度为零):可能是值得展开但尚未关联的「孤儿知识」;
- 桥节点(连接两个社区的唯一路径):结构脆弱,移除后两个社区断联;
- 稀疏社区(平均聚类系数极低):该主题域可能研究不足。
系统行为:自动触发 Deep Research 流程——针对空白区域发起外部搜索并生成补充页面。
7. 检索管线:四级混合检索
llm_wiki 的检索架构从传统 RAG 的两级(关键词 + 向量)扩展为四级:
1 | graph TD |
四级分解
| 级别 | 方法 | 输入/依赖 | 输出 |
|---|---|---|---|
| L1 | 分词搜索 | 查询文本 → 关键词 token | 精确匹配的页面子集 |
| L2 | 语义搜索 | 查询向量 → LanceDB 余弦相似度 | 「用词不同但语义相近」的扩充结果 |
| L3 | 图谱扩展 | 持久化知识图谱 → 多跳遍历 | 间接相关但未直接命中的节点 |
| L4 | 预算分配 | 前三级的候选集合 | 按比例拼接的最终 Prompt |
第三级是本系统的核心差异。传统 RAG 在每次查询时从零检索整个向量空间;llm_wiki 利用了摄入阶段构建的持久化知识图谱——这相当于将跨文档的语义关系预先物化为图的拓扑结构,检索时直接沿边遍历,避免了实时计算全量向量相似度的开销。
类比:RAG 是每次去图书馆都重新浏览所有书架;llm_wiki 是去图书馆时发现上一次已经把相关书籍用绳子捆在一起了,这次直接整捆拿走。
LanceDB 与 ChromiaDB 对比
| 特征 | LanceDB | ChromiaDB |
|---|---|---|
| 核心语言 | Rust | Rust |
| 部署模式 | 嵌入式(文件级,零配置) | 嵌入式 / 客户端-服务器 |
| 存储格式 | Lance(列式,Apache Arrow 生态) | 自定义格式 |
| 查询接口 | SQL-like + 向量检索 | 专用 API |
| 进程模型 | 库级别嵌入,无独立进程 | C/S 模式需独立服务进程 |
| 适用场景 | 桌面应用、边缘设备、嵌入式系统 | 服务端应用、微服务架构 |
选型逻辑:Tauri v2 的 Rust 后端可以直接 cargo add lancedb 将向量数据库编译进二进制文件,用户完全感知不到数据库的存在。若选用 ChromiaDB 的 C/S 模式,则需要用户额外运行一个数据库服务进程——这在桌面应用场景下不可接受。
8. 文档解析层
全部文档解析工作均在 Rust 侧完成,利用原生线程避免阻塞 UI:
| 文档格式 | 解析库 | 语言 |
|---|---|---|
pdf-extract |
Rust | |
| DOCX | docx-rs |
Rust |
| XLSX / XLS / ODS | calamine |
Rust |
| PPTX | 自研 | Rust |
| 网页 | Readability.js + Turndown.js | JavaScript (Chrome Extension) |
文档解析属于 CPU 密集型任务。若放在 JavaScript 主线程,解析一份 200 页 PDF 将导致 UI 冻结数秒。放在 Rust 侧并运行于独立线程上,解析过程与 UI 渲染完全并行。
网页内容的处理路径不同:通过 Chrome 扩展(Manifest V3)在浏览器中调用 Readability.js 提取正文、Turndown.js 将 HTML 转换为 Markdown,再送入标准的 CoT 摄入管线。
9. 其他模块
除了上述四层核心架构外,llm_wiki 还包含多个辅助但关键的子系统。
9.1 国际化 (i18n)
使用 react-i18next 实现中/英文双语界面。所有面向用户的文案均通过 t('key') 函数调用,翻译文件以 JSON 格式按模块组织:
1 | src/locales/ |
react-i18next 的选择逻辑:自动检测系统语言设置,用户可在应用内手动覆盖。切换语言无需重启应用——i18next 的响应式机制会自动触发所有使用了 useTranslation() Hook 的组件重新渲染。
9.2 数学公式渲染
集成 KaTeX(而非 MathJax)处理 LaTeX 数学公式。选型原因:
| 维度 | KaTeX | MathJax |
|---|---|---|
| 渲染方式 | 同步,直接输出 HTML + CSS | 异步,使用 SVG 输出 |
| 首屏速度 | 毫秒级 | 秒级(需等待 JS 解析全部公式) |
| 包体积 | 较小 | 较大 |
| LaTeX 宏支持 | 有限 | 完整 |
在桌面端场景下,KaTeX 的首屏渲染速度优势是决定性因素——用户打开包含大量公式的 Wiki 页面时不应出现可见的渲染延迟。
9.3 CoT 可视化
llm_wiki 内置了一个自定义的推理过程可视化组件,用于展示 LLM 思维链的中间步骤。当系统执行两步摄入时,用户可以在 UI 中观察到:
- 第一步分析产出的实体列表、论点摘要、矛盾标记
- 第二步生成产出的 Wiki 页面草稿与图谱更新计划
这不是通用的 LLM 对话界面——它是专为「文档→结构化 Wiki」这一特定工作流设计的可视化层。
9.4 Chrome 扩展(网页剪藏)
基于 Manifest V3 的 Chrome 扩展,技术栈独立于桌面端:
| 技术 | 用途 |
|---|---|
| Readability.js | 从任意网页中提取正文内容,剥离导航栏、广告、侧边栏等噪音 |
| Turndown.js | 将提取出的 HTML 正文转换为 Markdown 格式 |
工作流:用户在 Chrome 中点击扩展图标 → Readability.js 提取正文 → Turndown.js 转换为 Markdown → 通过本地 HTTP 接口(localhost 端口)将 Markdown 推入桌面应用的 CoT 摄入管线。
Chrome 扩展是唯一使用 JavaScript 做文档解析的组件。这是因为 Readability.js 是 Mozilla 维护的成熟 JS 库(Firefox Reader View 的核心引擎),在 Rust 生态中暂无同等质量的替代品。
9.5 异步审核队列
对于 LLM 生成结果中置信度低于阈值的内容(如矛盾标记、新页面建议),系统不会直接写入知识库,而是将其放入异步审核队列,等待用户人工确认。
1 | LLM 生成 → 置信度评估 |
此设计的关键原则是:自动管线不阻塞于人工判断。审核队列是一个独立的优先级队列,用户的待审核项以通知红点形式展示,不干扰正常的摄入与检索流程。这种「人机协作」模式比全自动或全人工都更实用——机器做它能确定的事,不确定的事交给人。
10. 关键设计决策
以下五项决策定义了 llm_wiki 的架构基因。每一项都涉及明确的权衡取舍。
10.1 Tauri v2 而非 Electron
| 优势 | 代价 |
|---|---|
| 包体积缩小 10–40 倍 | Rust 开发者供给少于 Node.js |
| 内存占用缩小 3–7 倍 | 社区插件数量与成熟度不及 Electron |
| 文档解析天然异步,不阻塞 UI | 平台相关的 WebView 差异需逐个适配 |
核心判断:性能收益是用户直接感知的(启动速度、内存占用、响应流畅度),而开发成本是一次性的。
10.2 LanceDB 而非 ChromiaDB
零部署是硬约束。桌面应用不能让用户安装数据库服务。LanceDB 实现了「文件即数据库」——启动应用即启动数据库,退出应用即停止,用户无感知。
10.3 两步 CoT 而非单步
一步生成在 token 成本上更优,但质量不可靠:遗漏上下文矛盾、生成内容结构松散、无法自动规划页面拆分。两步设计额外消耗 30–50% token,但生成的 Wiki 页面具有一致的结构与内链密度,大幅降低了后续人工整理的工作量。
10.4 知识图谱本地存储而非云端
隐私(文档全文不出本地)、离线可用性、以及低延迟图谱遍历是本地存储的三个不可替代的优势。代价是本地算力受限——万级以上节点的大规模图谱分析会触发可感知的计算延迟。
10.5 GPL-3.0 许可
作者明确选择了强 Copyleft:自由使用、自由修改,但分发修改版本时必须同样以 GPL-3.0 开源。这保护了项目的公共属性,同时不限制个人或学术用途。
11. 学习路径建议
对于希望从源码层面理解该项目的开发者,建议按以下顺序推进:
1 | gantt |
| 优先级 | 目标 | 预计时间 |
|---|---|---|
| P0 | 能读懂 src/ 目录下的组件代码与数据流 |
~2 周 |
| P1 | 理解摄入管线、检索管线、图谱分析的核心机制 | ~4 周 |
| P2 | 能独立修改 Rust 后端与图可视化层 | ~5 周 |
如果目标是日常使用该工具而非二次开发,则无需学习以上任何技术——直接下载安装即可。
参考项目:nashsu/llm_wiki — Stars 9k+ · GPL-3.0
致新手:不必试图同时理解所有层。从 React 前端 + TypeScript 入手,让项目跑起来,再逐步下钻到 Rust 后端与 LLM 管线。有层次感的架构值得反复研读——每轮深入都会发现上一轮忽略的设计细节。







