从思想到工程:LLM+Wiki 五个开源项目的深度架构分析

学习博客 | 2026年5月

本文围绕 Andrej Karpathy 提出的「LLM Wiki」核心思想,深入分析五个不同架构的开源项目是如何将这个思想进行工程化落地的。关注点集中在知识摄取(Ingest)、查询(Query)、知识库健康检查与问题排查(Lint/Debug)、防幻觉(Anti-Hallucination)等核心能力的实现方案,不涉及具体编程语言或前端 UI 细节。


目录

  • 第一部分:核心思想解读 | 五项目概览 | 共同的三层架构 | Ingest 摄取管线深度对比
  • 第二部分:Query 查询策略对比 | 知识图谱的运用 | 防幻觉机制对比
  • 第三部分:知识库健康检查与问题排查 | 综合对比矩阵 | 架构选择指南 | 总结与思考

一、核心思想:LLM Wiki 到底是什么?

在分析五个项目之前,我们需要先理解 Karpathy 的原始思想。这个思想记录在 llm-wiki.md 文件中,核心论点是:

1.1 传统 RAG 的局限

大多数人的 LLM+文档 体验是这样的:上传一堆文件,LLM 在查询时检索相关片段,然后生成答案。这是经典的 RAG(Retrieval-Augmented Generation)模式。Karpathy 指出了它的根本问题:

LLM 在每次查询时都要从零开始重新发现知识。知识没有积累。

举个例子:假设你已经上传了 50 篇关于「注意力机制」的论文。当你问「自注意力和交叉注意力的本质区别是什么?」时,RAG 系统需要:

  1. 从 50 篇论文中检索相关片段
  2. 拼接这些片段
  3. 临时理解它们之间的关系
  4. 生成答案

下次你再问一个相关但略有不同的问题时,整个过程从头再来一遍。跨文档的关联、矛盾、综合理解,永远不会被「记住」。

1.2 LLM Wiki 的核心洞察

Karpathy 提出的替代方案是:

让 LLM 逐步构建并维护一个持久化的 Wiki —— 一个结构化的、互相关联的 Markdown 文件集合,位于你和原始资料之间。

关键区别:

维度 传统 RAG LLM Wiki
知识提取时机 查询时(每次重新提取) 摄取时(编译一次)
跨文档关联 查询时临时发现 摄取时预建好 wikilinks
矛盾处理 查询时可能发现(也可能漏掉) 摄取时主动标记
知识状态 每次查询后丢弃 持久化,持续复合增长
维护成本 无需维护 由 LLM 承担,边际成本接近零

Karpathy 将这个过程类比为:

Obsidian 是 IDE,LLM 是程序员,Wiki 是代码库。

人类负责策展源材料、提出好问题、指导分析方向。LLM 负责所有琐碎的「簿记工作」—— 摘要、交叉引用、归档、保持一致性。这就是这个模式能够工作的根本原因:Wiki 维护中最枯燥的部分,恰好是 LLM 最擅长的。

1.3 三层架构:所有项目的共同骨架

Karpathy 定义了一个三层架构,这成为了所有五个项目的共同骨架:

┌──────────────────────────────────────────┐
│              第三层:Schema               │
│   (CLAUDE.md / WIKI.md / schema.md)      │
│   规则+约定+工作流 → "如何维护 Wiki"       │
├──────────────────────────────────────────┤
│              第二层:Wiki                 │
│   (LLM 生成并维护的 Markdown 文件)         │
│   index.md + log.md + entities/ + ...    │
│   "持久化、复合增长的知识产物"              │
├──────────────────────────────────────────┤
│              第一层:Raw Sources          │
│   (原始资料,LLM 只读不写)                │
│   PDFs/论文/文章/笔记/录音/数据            │
│   "不可变的真实来源"                       │
└──────────────────────────────────────────┘

第一层 — 原始资料(Raw Sources):用户精选的源文档。LLM 可以读取它们但绝不修改。这是事实的来源。

第二层 — Wiki:LLM 生成的 Markdown 文件目录。LLM 完全拥有这一层 —— 它创建页面、更新内容、维护交叉引用、保持一致性。人类阅读,LLM 编写。

第三层 — Schema:一个配置文件(不同项目中叫法不同),告诉 LLM Wiki 的结构、约定和操作流程。这是把 LLM 从「通用聊天机器人」变成「有纪律的 Wiki 维护者」的关键。

1.4 三个核心操作

基于这三层架构,Karpathy 定义了三个核心操作:

  1. Ingest(摄取):放入新资料 → LLM 读取 → LLM 提取关键信息 → 创建/更新 Wiki 页面(一次摄取可能触及 10-15 个页面)→ 更新索引和日志

  2. Query(查询):提问 → LLM 检索相关 Wiki 页面 → 综合回答并附引用 → 好的答案可以存回 Wiki(让探索成果也复合增长)

  3. Lint(健康检查):定期检查 Wiki 健康状况 → 发现矛盾、过时声明、孤儿页面、缺失的交叉引用、知识空白

这构成了一个完整的知识管理生命周期:摄取 → 查询 → 健康检查 → 再摄取。每轮循环都让 Wiki 变得更好。

五个项目都遵循这个核心模型,但在实现方式上做出了截然不同的工程选择。接下来我们将深入分析每个选择背后的理由。
在这里插入图片描述


二、五个项目的架构概览

在深入具体细节之前,先对五个项目做一个快速定位:

项目 1:claude-obsidian(by AgriciDaniel)

架构类型:Claude Code 插件 + Obsidian 知识库
核心特征:11 个 Skill 构成的技能体系、DragonScale 记忆扩展、Hot Cache 会话恢复、全自动化 Git 提交
定位:最完整的「LLM 作为 Obsidian 维护者」方案
版本:v1.6.0(截至 2026-04-24)

项目 2:llm_wiki(by nashsu)

架构类型:Tauri 桌面应用(Rust 后端 + React 前端)
核心特征:可视化的知识图谱(sigma.js)、Louvain 社区检测、4 信号关联度模型、两步链式思维摄取、Chrome 网页裁剪扩展
定位:功能最全面的独立 GUI 应用
版本:v0.4.6(GPL v3)

项目 3:llm-wiki-agent(by nashsu)

架构类型:纯 Agent 驱动(Python 脚本 + LLM Agent 指令)
核心特征:零基础设施(无服务器、无数据库、纯 Markdown)、两步图构建(确定性提取 + LLM 推断)、三代理兼容(Claude Code / Codex / Gemini)
定位:最精简的 Agent 原生方案

项目 4:llm-wiki-compiler(CLI 名 llmwiki)

架构类型:TypeScript CLI 知识编译器
核心特征:两阶段编译(先提取所有概念,再生成所有页面)、SHA-256 增量编译、审阅队列(Review Queue)、MCP 服务器、Claim 级出处引用(精确到行号)
定位:最接近「编译器」隐喻的方案
版本:v0.6.0(MIT)

项目 5:llm-wiki-master(by nvk)

架构类型:Claude Code 插件(多运行时兼容)
核心特征:Hub 多主题 Wiki 架构、17 意图模糊路由器、研究流水线(5/8/10 Agent 可扩展)、审计系统(Audit)、反确认偏误机制、Lint 即迁移
定位:最强调研究深度和真值追踪的方案
版本:v0.7.0(MIT)

五项目的架构光谱

如果把这五个项目放在一条轴上:

Agent原生 ████████████████████████████████████████ 独立应用
    │          │              │           │          │
  Agent     Compiler      Master     claude-    llm_wiki
 (项目3)    (项目4)       (项目5)   obsidian   (项目2)
                                     (项目1)

项目 3 是最「轻」的 —— 它本质上就是一套 Agent 指令 + Python 辅助脚本。项目 2 是最「重」的 —— 完整的桌面应用,有图形界面、知识图谱、Web 裁剪扩展。其他项目分布在这两个极端之间。

这种多样性说明了 Karpathy 思想的灵活性:它不是一种固定的实现方式,而是一种可以适配不同场景和偏好的架构模式。


三、Ingest 摄取管线:知识如何从原始资料进入 Wiki

Ingest(摄取)是整个系统的入口。它决定了知识的质量、广度和结构。一个糟糕的摄取策略会导致后续的查询和健康检查都建立在有缺陷的知识上。五个项目在 Ingest 上做出了截然不同的设计选择,我们来逐一分析。

3.1 基本模式:单次调用 vs 分步提取

项目 3(llm-wiki-agent)—— 单次 LLM 调用

这个项目使用了一个相当直接的策略:将原始资料 + Wiki 上下文(index.md + overview.md + 最近 5 个源页面摘要)发送给 LLM,通过一次 API 调用产出所有结果 —— 包括源摘要页面、实体页面、概念页面、概述更新、矛盾列表和日志条目。

为什么这样设计?

  • API 成本最小化:一次调用完成所有工作
  • 一致性保证:所有共同创建的页面在同一上下文中生成,理论上更一致
  • 简化流程:没有中间状态,不需要编排多次调用

问题在哪里?

  • 提示词复杂度高:要求 LLM 在一次响应中同时做摘要、关系提取、结构化页面生成——任务切换可能降低每项的质量
  • 部分失败难处理:如果 LLM 响应中途截断,所有页面都受影响
  • 思考空间不足:LLM 没有中间分析步骤,直接从原始文本跳到文件结构

项目 2(llm_wiki)—— 两步链式思维摄取

这个项目对上述问题给出了明确的回答:将摄取分为两个连续的 LLM 调用

Step 1(分析):LLM 读取源文件,产生结构化分析
  ├── 关键实体列表
  ├── 核心概念及其定义
  ├── 论证和发现的摘要
  ├── 与现有 Wiki 内容的关联
  ├── 与现有 Wiki 内容的矛盾
  └── 对 Wiki 结构更新的建议

Step 2(生成):LLM 基于分析结果,生成实际的 Wiki 文件
  ├── 源摘要页面(YAML frontmatter)
  ├── 实体页面(带交叉引用)
  ├── 概念页面([[wikilinks]])
  ├── 更新的 index.md
  ├── 更新的 log.md
  └── 更新的 overview.md

为什么这样更好?

  • 关注点分离:步骤 1 专注于理解,步骤 2 专注于结构化输出。每步都在做它最擅长的事
  • 思考空间:分析结果作为中间产物,让 LLM 在「理解」和「生成文件结构」之间有了一个显式的推理缓冲
  • 质量提升:项目作者明确提到两步法比单次传递产生「显著更好的质量」
  • 失败处理更优雅:如果步骤 2 失败,步骤 1 的分析结果还在,可以重试而不重新分析源文件

代价是什么?

  • API 调用翻倍:每次摄取需要两次 LLM 调用
  • 延迟增加:两次串行调用的总时间更长
  • 但考虑到 LLM 调用的速度远快于人类阅读速度,对于质量换时间的权衡是合理的

让我更形象地解释:单次调用就像让一个人「读这篇文章,同时写摘要、整理术语表、画关系图」——他可能做完,但每项都做不到最好。两步法就像先让他「读文章,做笔记,写分析稿」,然后让他「看着分析稿,写摘要、整理术语表、画关系图」——第二步的质量因为有高质量的中间产物而显著提升。

3.2 编译器模式:项目 4(llm-wiki-compiler)的两阶段编译

项目 4 将摄取和编译分成了两个独立步骤,并且其「编译」阶段的设计是五个项目中最复杂的:

第一步:ingest——将异构外部源转换为统一的 Markdown

URL / PDF / 图片 / 视频字幕 → 统一为 sources/*.md
(每个文件的 frontmatter 记录 sourceType、来源 URL、摄取时间戳)

第二步:compile——两阶段编译

Phase 1: 概念提取
  对每个源文件,LLM 通过 tool_use 提取结构化概念
  (概念标题、摘要、标签、置信度、出处元数据)

Phase 2: 页面生成
  基于提取的概念,LLM 生成 wiki 页面
  不同源文件中提取到的相同概念会被合并

这个设计解决的几个关键问题:

问题一:顺序依赖
如果先处理文件 A 生成页面 X,再处理文件 B 生成页面 Y,而 X 和 Y 应该引用一个共同的概念 Z —— 但 Z 在分别处理时还没有自己的页面。

项目 4 的两阶段方案解决了这个问题:先提取所有概念,再生成所有页面。 这样无论什么顺序,概念 Z 在页面生成前就已知了。

问题二:跨源概念合并
如果源 A 和源 B 都讨论了「自注意力机制」,应该生成一个综合页面还是两个独立页面?在逐个处理模式下,A 先产生一个「自注意力机制」页面,B 处理时要么创建重复页面,要么发现并更新已有的一个 —— 但更新意味着 LLM 需要重新阅读 A 的内容来综合。

项目 4 在两阶段框架中优雅地处理了这一点:A 和 B 各自提取概念 → 系统检测到相同条款(通过 slug)→ 合并为一次 LLM 调用,同时使用 A 和 B 的内容作为输入 → 生成综合页面。

问题三:增量编译
SHA-256 哈希检测哪些源文件发生了变化。只有变化的文件才会重新提取概念和重新生成页面。这避免了每次编译都重新处理整个知识库。

问题四:依赖追踪
项目 4 实现了一个精妙的受影响源检测机制:如果源 A 和源 B 都贡献给了概念 X,当 A 变化时,不仅 A 需要重新处理,B 也会被标记为「受影响」——因为 X 的重新生成需要同时使用 A 和 B 的内容。这防止了 A 变化后只用 A 的内容重新生成 X,从而静默丢失 B 的贡献。

Pre-extraction 检测(基于历史状态):

A 变化 → 查看上一次 compile 的状态记录
     → 发现 A 和 B 共同产出了概念 X
     → 标记 B 为「受影响」→ X 的重新生成会使用 A+B

Post-extraction 检测(基于新提取的概念):

A 变化 → A 重新提取概念 → 提取到新概念 Z
                       → 检查 Z 的 slug 是否匹配 B 现有的概念
                       → 如果匹配但没有被 pre-extraction 检测到
                       → 标记 B 为「late-affected」→ 重新生成 Z

这很重要,因为新源文件和改后获取了新概念的源文件,在 pre-extraction 阶段没有状态记录。Post-extraction 作为安全网补上了这个缺口。

3.3 自动化研究摄取:项目 1 和项目 5 的创新

当你没有本地源文件,想让 LLM 去互联网上找资料时怎么办?项目 1 和项目 5 在这方面各有创新。

项目 1(claude-obsidian)—— Autorresearch(自主研究)

这是一个三轮研究循环

Round 1: 探索
  ├── 将主题分解为 3-5 个角度
  ├── 每个角度 2-3 次 Web 搜索
  ├── 抓取靠前结果
  └── 提取声明/实体/概念

Round 2: 缺口填补
  ├── 针对缺失或被矛盾的信息进行定向搜索
  └── 最多 5 个查询

Round 3(可选): 深层追踪
  └── 如果矛盾仍然存在,再做一轮定向搜索

关键设计理念是有节制的自动化:最多 3 轮、每轮有限的搜索次数、用户最终决定研究方向。这不是让 LLM 无限漫游,而是在明确边界内做结构化探索。

项目 5(llm-wiki-master)—— 多 Agent 研究流水线

这个项目的做法更为激进:将研究问题分解,分派给多个"Agent"并行执行

标准模式(5 Agent):
  Academic Agent(学术视角)   → 搜索论文、学术文章
  Technical Agent(技术视角)   → 搜索技术文档、GitHub
  Applied Agent(应用视角)     → 搜索案例研究、实践报告
  News Agent(新闻视角)        → 搜索最新动态
  Contrarian Agent(对立视角)  → 搜索反面/批评意见 ← 关键!

深度模式(+3 Agent):
  + Historical Agent(历史视角)
  + Adjacent Agent(相邻领域)
  + Data/Stats Agent(数据统计)

极限模式(+2 Agent):  
  +2 Rabbit Hole Agent(任意发散)
  (跳过规划,激进摄取)

这里面最精妙的设计是 Contrarian Agent(对立视角 Agent)。LLM 天然有「迎合用户」的倾向——它更容易找到支持用户观点的材料,而忽略反面证据。通过强制安排一个专门寻找反面意见的 Agent,系统在机制上对抗了确认偏误。

此外,项目 5 还有独立的可信度审查步骤(Credibility Scoring)

在摄取前评估每个来源:
  peer-reviewed  ➜ +2 分
  recent         ➜ +1 分
  authority      ➜ +1 分
  bias           ➜ -1 分
  corroboration  ➜ +1 分/Agent, 最多 +2

分级:
  4-6 分 → High   → 直接摄取
  2-3 分 → Medium → 摄取
  0-1 分 → Low    → 选择性摄取
  <0 分  → Reject → 跳过

这个设计解决了一个微妙但重要的问题:「狐狸看守鸡舍」——如果让负责摄取的那个 LLM 来评估它自己找到的源的质量,它会有动力高估质量(因为它找到了这些源)。独立的可信度评分步骤在摄取前介入,在一定程度上隔离了这种利益冲突。

3.4 增量与缓存:避免重复工作的艺术

LLM API 调用的成本(时间和金钱)使得有效的缓存策略至关重要。五个项目在这方面各有做法。

项目 2(llm_wiki)—— SHA-256 增量缓存

最朴素的方案:对源文件内容做 SHA-256 哈希。如果哈希没变,完全跳过该文件的处理。简单、可靠、内容驱动而非时间戳驱动(时间戳在文件恢复、git checkout 等操作中不可靠)。

项目 1(claude-obsidian)—— .raw/.manifest.json 增量追踪

项目 1 用 manifest.json 记录每个源文件的哈希值和已分配的资源标识。它更进一步:manifest 中维护了 address_map,映射 Wiki 页面路径到其确定的 DragonScale 地址,确保重新摄取时的地址稳定性。

项目 4(llm-wiki-compiler)—— 多层状态管理

最复杂的缓存方案:

.llmwiki/state.json        # 每源 SHA-256 + 概念列表 + 编译时间戳
.llmwiki/embeddings.json   # v2, 页面级+块级向量嵌入
                           # 内容哈希感知的块复用 → 不变块不需要重新嵌入

项目 4 的嵌入存储 v2 设计尤其值得关注:页面被分割成段落/标题边界的块(chunks),每个块基于其内容哈希独立管理。 当仅部分内容变化时,只有变化的块需要重新嵌入,大大减少了向量搜索索引的刷新成本。这在嵌入通常比 LLM 调用便宜但仍不可忽略的前提下,是一个务实的优化。

项目 5(llm-wiki-master)—— 派生索引协议

项目 5 最大的增量创新不在缓存层面,而在索引层面:_index.md 是派生缓存,而非源数据。 索引在被读取时从实际文件的前置元数据(frontmatter)重建。两个会话可以同时写文件,下一次索引读取会收敛到正确的状态。这个设计实现了无锁并发

3.5 特殊格式处理

源文件不只是 Markdown。五个项目面临同样的挑战:如何处理 PDF、Office 文档、图片、网页?

项目 2(llm_wiki)的处理能力最全面:

格式 处理方式
PDF Rust 后端 pdfium-render,带文件缓存
DOCX docx-rs + 手动 XML 回退
PPTX ZIP + XML,逐张幻灯片提取
XLSX/XLS/ODS calamine → Markdown 表格
图片 原生预览;可选视觉 LLM 描述生成(2-4 句,纯事实性)
视频内嵌图片 PDF/PPTX/DOCX 中提取 → SHA-256 去重 → 视觉 LLM 描述
网页 Chrome 扩展(Mozilla Readability.js + Turndown.js)
YouTube 字幕 youtube-transcript 获取

关键设计:图片描述的纯事实性要求。 项目 2 的 VLM prompt 明确要求「Factually describe this image… 2-4 sentences, no speculation」。在 LLM Wiki 的上下文中这是正确的做法——描述你在图片中看到什么,不要把推理和推测当事实记录。推测属于 Synthesis 层面,应该被明确标记。

项目 4(llm-wiki-compiler)的策略相对节俭:

  • PDF → pdf-parse(延迟加载,避免启动开销)
  • 网页 → Mozilla Readability → Turndown → Markdown
  • 图片 → 传给 LLM 的视觉能力生成描述

项目 1(claude-obsidian)额外集成了 defuddle:

这是一个外部 npm 工具,用来在摄取前剥离网页中的广告、导航栏和杂讯。项目声称能节省 40-60% 的 token。这看起来是一个好主意——为什么要把 LLM 的上下文窗口浪费在导航菜单和广告上?但也要注意剥离过程本身可能移除有用信息(比如侧边栏中的相关文章链接)。

3.6 摄取设计总结

维度 项目1(claude-obsidian) 项目2(llm_wiki) 项目3(agent) 项目4(compiler) 项目5(master)
LLM调用次数 1次 2次(分析+生成) 1次 2阶段(提取+生成) 多次(研究→摄取→编译)
增量策略 manifest.json SHA-256 无(Agent手动触发) SHA-256状态文件 派生索引
批量处理 支持(并行代理) 串行队列 1个文件 所有源批量编译 并行搜索+串行编译
源格式支持 MD/URL/图片 PDF/DOCX/PPTX/XLSX/图片/网页/字幕 18+格式(markitdown) URL/PDF/图片/字幕/会话 网页/Git仓库/Wiki/消息归档
网页预处理 defuddle(40-60% token节省) Chrome扩展+Readability markitdown转换 Readability+Turndown Readability(Wayback)
自主研究 3轮循环 深研(Tavily API) 5/8/10 Agent并行
可信度评估 confidence字段 无独立评估 无独立评估 置信度+出处 独立可信度评分(0-6分)

核心权衡:

  1. 质量 vs 成本:两步/两阶段方案(项目2、项目4)产出的质量更高,但 API 调用次数也更多。单次调用方案(项目1、项目3)成本更低但质量可能参差不齐。

  2. 批量编译 vs 逐文件摄取:项目4的批量编译能够做跨源合并和去重,是更「聪明」的方案,但只适合离线运行。项目1、2、3的逐文件摄取是更实时的交互方式,适合「读一篇入一篇」的工作流。

  3. 增量编译的复杂度:项目4实现了最复杂的增量策略(Pre + Post extraction 两轮受影响源检测),在理论上最完备,但也增加了代码复杂度。项目1、2的简单哈希策略在大多数场景已经足够。

  4. 自主研究的边界:项目5的 5/8/10 Agent 研究是最激进的自动化方案,但引入了对搜索质量和 Agent 协调的依赖。项目1的 3 轮循环是更保守但更可控的选择。


四、Query 查询策略:如何在 Wiki 中高效找到知识

如果说 Ingest 决定了知识如何「入库」,那么 Query 决定了知识如何「出库」。一个好的查询系统需要在三个互相矛盾的维度上取得平衡:检索全面性(找到所有相关页面)、效率(不浪费 Token)、答案质量(准确且有引用支撑)。

五个项目在这三个维度上各有取舍,发展出了各自独特的检索策略。

4.1 索引驱动检索:为什么不需要向量数据库?

在讨论具体策略之前,需要理解 LLM Wiki 架构隐含的一个重要前提:

Karpathy 原始设计中明确指出了一个关键洞察:在中等规模下(几百页 Wiki),用 index.md 作为检索入口比用向量嵌入检索更有意义。 理由是:

  • index.md 是人工可读、LLM 可导航的内容目录
  • LLM 读 index.md 找相关页面 → 再深入具体页面内容,这个流程和人类使用 Wiki 的方式一致
  • 查全率(recall)依赖 index.md 的质量 —— 而 index.md 是 LLM 在每次摄取时自动维护的
  • 避免了向量嵌入基础设施的复杂性和维护成本

但是,index.md 方案有上限。当 Wiki 增长到数千页时,index.md 本身就会溢出 LLM 的上下文窗口。五个项目在「多大程度上依赖 index.md」以及「什么情况下引入更复杂的检索机制」上做出了不同选择。

4.2 三个查询深度层级(Query Depth Levels)

项目 1(claude-obsidian)和项目 5(llm-wiki-master)都使用了「查询深度」的概念,但它们的设计方向不同。

项目 1 的三层深度(基于 Token 预算):

Quick(~1,500 tokens):
  只读 hot.md + index.md
  → 用于简单事实性查询
  → 成本最低,但覆盖最窄

Standard(~3,000 tokens,默认):
  读 hot.md + index.md + 3-5 个最相关页面
  跟随 wikilinks 展开到深度2
  → 大多数查询的首选深度
  → 在覆盖和效率间取得平衡

Deep(~8,000+ tokens):
  读全 Wiki
  → 用于跨主题综合、对比分析、"告诉我一切"
  → 始终将结果存入 Wiki

项目 5 的三层深度(基于导航策略):

Quick:
  只读索引 → 从摘要和标签中回答
  → 适用于"这个 Wiki 里有哪些关于 X 的内容?"

Standard:
  3 跳导航:主索引 → 分类索引 → 相关文章 + 全文搜索
  → 最常用的查询模式

Deep:
  读全 Wiki + 搜索原始资料 + 窥视相邻 Wiki 是否有相关
  → 用于深度研究,"没有遗漏任何东西"

两者的区别在于:项目 1 的深度是以 token 预算来定义的(限制读取量),项目 5 是以导航范围来定义的(限制搜索跳数)。项目 1 的方案更精确控制成本,项目 5 的方案更贴近人类浏览 wiki 的思维模型。

4.3 项目 2(llm_wiki)的四阶段检索管线

这是五个项目中最复杂的查询系统。它将检索分解为四个阶段:

Phase 1:分词搜索(Tokenized Search)

输入:查询文本
  ├── 英文:分词 + 停用词去除
  ├── 中文:CJK 二元组(bigram)分词 ← 关键设计!
  │   例如 "注意力机制" → ["注意", "意力", "力机", "机制"]
  │   因为中文词之间没有空格,传统分词器效果差
  ├── 标题匹配加分(+10)
  └── 评分权重:
       文件名精确匹配: 200
       短语在标题中:    50
       短语在内容中:    20/次(最多10次)
       标题词匹配:       5
       内容词匹配:       1
  ├── 16 路并发文件读取(Promise.all 批处理)
  └── 产出:按得分排序的文件列表

中文 bigram 分词是个重要但容易被忽视的设计。如果不做 CJK 特殊处理,像「注意力机制」这样的词在英文分词器中会被当作一个连续的 token 或者被错误切分,导致搜索质量大幅下降。项目 3 也做了类似的 CJK bigram 处理,说明这是一个在中文场景中必须解决的问题。

Phase 1.5:向量语义搜索(可选,默认关闭)

条件:用户启用了向量搜索(需要配置 embedding endpoint)
  ├── 通过任意 OpenAI 兼容的 /v1/embeddings 接口获取嵌入
  ├── 存储在 LanceDB(Rust 后端,嵌入式)
  ├── 余弦相似度排序
  ├── 通过 RRF(Reciprocal Rank Fusion)与分词搜索结果合并
  └── 基准测试:召回率从 58.2% 提升到 71.4%

为什么默认关闭向量搜索?项目文档明确指出向量搜索需要额外配置(embedding endpoint、LanceDB),而且分词搜索在几百页规模下已经足够好。向量搜索是在规模增大、纯关键词失效时的升级路径。这是一个务实的「按需启用」设计,而非无条件的「技术炫耀」。

Phase 2:图谱扩展

输入:Phase 1 的 Top 搜索结果作为种子节点
  ├── 4 信号关联度模型计算每个邻居的关联度
  ├── 2 跳遍历(从种子节点出发,最多走两步)
  ├── 衰减权重(距离越远,权重越低)
  └── 产出:扩展后的相关页面集合

图谱扩展的核心价值是发现那些不含查询关键词但语义上高度相关的页面。举个具体例子:如果你查询「Transformer 的注意力机制」,一个标题为「Self-Attention 的计算复杂度分析」的页面可能不含"Transformer"这个词,但图谱中的 wikilinks 已经建立了它和 Transformer 页面的连接。基于关键词的搜索会漏掉它,图谱扩展能补上。

Phase 3:预算控制

输入:所有候选页面
  ├── 上下文窗口配置: 4K ~ 1M tokens
  ├── 比例分配:
  │   ├── 60% → Wiki 页面
  │   ├── 20% → 聊天历史
  │   ├── 5%  → index.md
  │   └── 15% → System Prompt(purpose.md + 语言规则 + 引用格式)
  ├── 按综合得分排序(搜索分 + 图谱关联分)
  └── 产出:能塞进预算的最优页面集合

预算控制本身是一个被忽视但至关重要的设计。如果不做预算控制,一个有 500 页相关知识库的查询会:

  1. 超出 LLM 上下文窗口 → API 报错
  2. 或者挤入过多低质量相关页面 → 稀释关键信息 → 答案质量下降

Phase 4:上下文组装

System Prompt =
  purpose.md("为什么要建这个 Wiki"的上下文)
  + 语言规则
  + 引用格式要求(用 [1], [2] 标注引用来源)
  + index.md(为 LLM 提供整体导航)

用户提示 =
  编号的 Wiki 页面(完整内容)
  + 近期聊天历史

LLM 响应 = 流式答案 + 引用面板(按类别分组显示引用了哪些页面)

这里有一个容易被忽视的设计:将 index.md 放入 System Prompt。这给了 LLM 一个「全局地图」,让它知道即使某些页面的完整内容没有被包含在当前上下文中,它们的存在也是 LLM 的常识。这比简单堆砌页面内容更智能,因为 LLM 可以利用 index.md 的摘要来判断哪些信息需要在回答中提及(或标注为不详)。

4.4 项目 4(llm-wiki-compiler)的两步查询

项目 4 的查询有一个独特之处:将页面选择和答案生成显式拆分,并为选择步骤引入 BM25 重排序。

Step 1: 页面选择
  ├── 块级语义检索(chunk-level,优先)
  │   ├── BM25 重排序 ← 关键设计
  │   └── 余弦相似度 × BM25 混合 → 综合排名
  ├── 或:页面级嵌入预筛选
  ├── 或:完整索引回退(无嵌入可用时)
  └── LLM 通过 tool_use 从候选集中选择最相关的页面

Step 2: 答案生成
  ├── 加载选中页面的完整内容
  ├── 流式生成带引用的答案
  └── 引用严格限于被选中的 Wiki 页面内容

为什么需要 BM25 重排序?

纯语义搜索有一个已知缺陷:它对「语义上相关但不含关键词」的内容有很好的召回,但对「关键词完全匹配」的内容(如搜索一个术语的精确定义)可能排名不够靠前。BM25(经典的基于词频的排序算法)恰好擅长关键词匹配。两者混合:

最终得分 = α × 余弦相似度 + β × BM25得分

当查询包含明确的关键词时(如「multi-head attention」),BM25 确保精确匹配的页面不会因语义漂移而被挤到下面。当查询是模糊的自然语言问题时(如「模型是怎么关注不同位置的」),语义搜索负责找到那些不含关键词但讨论了位置编码和注意力权重的页面。

这种混合检索比纯语义或纯关键词都更健壮

4.5 Hot Cache:会话上下文管理

项目 1(claude-obsidian)引入了一个独特的设计:wiki/hot.md——一个约 500 字的「最近上下文缓存」。

hot.md 格式:
  ├── Last Updated: 最后更新时间
  ├── Key Recent Facts: 最近的关键事实(3-5 条)
  ├── Recent Changes: 最近变更(创建/更新/标记的页面)
  └── Active Threads: 活跃的讨论或研究方向

更新触发时机:
  ├── 每次摄取后
  ├── 重大查询交流后
  └── 会话结束时(通过 Hook 自动触发)

Token 经济学:
  ├── 读 hot.md:~500 tokens → 回答"上次做到哪了?"
  ├── 不读 hot.md 的替代方案:读 index.md(~1,000 tokens)
  │     + 多个分类子索引 + 具体页面 → 总成本可能达 3,000-5,000 tokens
  └── 每会话节省:~2,500-4,500 tokens / 次

这个设计看起来简单,但解决了 LLM Agent 工作流中一个普遍但被忽视的问题:「冷启动」。当你打开一个 Wiki 会话时,你不知道上次做了什么。Hot Cache 用 500 tokens 解决了这个问题。

项目 1 还通过 Claude Code 的 Hook 系统将 Hot Cache 自动化:

  • SessionStart Hook:会话启动时静默读取 hot.md
  • PostCompact Hook:上下文压缩后重新读取 hot.md(因为压缩会清除非持久化上下文)
  • Stop Hook:会话结束时提示更新 hot.md

这里一个容易被忽视的细节是 PostCompact Hook。Claude Code 有时会自动压缩上下文(当对话历史过长时),压缩后的 LLM 会丢失 Hook 注入的上下文。PostCompact Hook 在压缩后立即重新注入 hot.md,确保上下文恢复的连续性。

4.6 查询结果归档:让探索成果复合增长

Karpathy 原始设计中的一个关键洞察是:

好的答案可以存入 Wiki 作为新页面。你提出的对比分析、你发现的关联、你的探索——这些都是有价值的,不应该消失在聊天记录里。

五个项目各自实现了这个思想的不同变体。

项目 1(claude-obsidian)—— save skill

用户:"这个分析存下来"
  → LLM 将对话重新表述为声明式现在时态
  → 确定页面类型(综合/概念/来源/决策/会话记录)
  → 创建 wiki 页面
  → 更新索引和日志

关键设计是 「重新表述」 这一步。聊天内容通常是「你问我答」的形式,但 Wiki 页面应该以话题为中心。把「你问了我关于 X 的问题,我回答了 Y」改写成「关于 X 的关键要点是 Y」——这个转换确保存入 Wiki 的是知识而不是聊天记录。

项目 4(llm-wiki-compiler)—— query --save

llmwiki query "问题" --save
  → 生成答案
  → 保存为 wiki/queries/<slug>.md
  → 重建 index.md(立即可被后续查询发现)
  → 更新嵌入(参与后续语义搜索)

这里的关键在于 「立即可发现」 ——保存的查询结果通过 index.md 和嵌入向量的双重路径,立即成为后续查询的候选知识源。这让知识库的复合增长有了反馈循环:你查询得越多,可用的知识就越多。

项目 5(llm-wiki-master)—— 查询即为研究入口

项目 5 的查询系统更为激进:它不仅回答问题,还主动建议后续研究方向。

查询结果包含:
  ├── 答案(带引用)
  ├── 置信度标注
  ├── 明确的知识空白:"我知道的不够多,具体缺少……"
  └── 建议的后续研究:"你可以用 /wiki:research 深入调查 X"

这种设计把 Query 当作持续研究过程中的一个环节,而非终点。它承认了 Wiki 的知识总是不完整的,而通过透明化这种不完整性来指导下一步行动。

4.7 查询设计总结

维度 项目1 项目2 项目3 项目4 项目5
检索入口 hot.md → index.md 4阶段管线 index.md + 关键词 块级语义+BM25 索引3跳导航
向量搜索 无(依赖索引) 可选(LanceDB) 嵌入+BM25混合
图谱扩展 2跳 + 4信号模型 图谱邻居扩展
预算控制 按深度固定 Phase 3 动态比例 15页上限 上下文窗口分割 按深度选择
会话恢复 Hot Cache 聊天持久化 –resume模式
CJK支持 依赖LLM Bigram分词 Bigram分词 Unicode \p{L} 依赖LLM
结果归档 save skill 存到wiki/queries/ 可选存到syntheses –save flag 存为查询页面

核心权衡:

  1. 纯索引 vs 混合检索:项目 1、3、5 在几百页规模下只用 index.md 工作良好,项目 2、4 面向更大规模引入了向量搜索。这不是技术优劣的问题,而是规模化策略的差异。

  2. 图谱在查询中的角色:项目 2 将图谱作为检索扩展的正式环节(2 跳扩展),项目 3 用它作为关键词匹配不足时的补充,项目 1 和 4 不在查询时使用图谱。图谱的有效性取决于图谱构建的质量(见第五节)。

  3. 会话恢复的成本:Hot Cache(项目 1)是最低成本的方案,聊天持久化(项目 2)提供更完整的上下文但成本更高。项目 3、4 没有专门的会话恢复机制,依赖 Agent 的自然语言理解能力。


五、知识图谱:如何让隐含的关联变得可见

知识图谱在 LLM Wiki 的上下文中扮演了一个有趣的角色:它既是一个分析工具(帮我们发现未知的联系),也是一个检索基础设施(帮查询找到语义相关的页面)。

5.1 图谱构建的两种策略

策略一:确定性构建(项目 3 —— llm-wiki-agent)

项目 3 采用了最直接的方法:

Pass 1: 正则提取所有 [[wikilinks]]
  ├── 类型:EXTRACTED(确定性边)
  ├── 置信度:1.0(因为是显式链接)
  └── 覆盖范围:所有显式链接

Pass 2: LLM 推断隐含关系
  ├── 类型:INFERRED(置信度 ≥ 0.7)或 AMBIGUOUS(置信度 < 0.7)
  ├── 逐页面检查,一次一个页面
  └── LLM 回答:"这个页面与哪些其他页面有未被链接的关系?"

两级置信度的设计意图:

显式 wikilinks 是作者写入的主观关联——如果一个页面链接到另一个页面,这是有意为之的。置信度 1.0 是合理的。

推断的边是 LLM 发现的客观关联——可能对也可能错。INFERRED(≥ 0.7)和 AMBIGUOUS(< 0.7)的区别在于使用场景

  • INFERRED 边 → 可以用于查询扩展
  • AMBIGUOUS 边 → 仅在可视化中间色显示,供人类审阅,不作为自动决策依据

这是一个优雅的设计:把置信度的概念编码到图的边类型中,让下游消费者(查询系统、可视化、分析工具)可以根据自己的需求决定使用哪些边。

策略二:4 信号关联度模型(项目 2 —— llm_wiki)

项目 2 不使用 LLM 推断边,而是构建了一个多信号加权关联度模型:

信号 1: 直接链接(Direct Link)———— 权重 3.0x
  [[wikilinks]] 中的显式链接
  → 最好的信号,但覆盖率低

信号 2: 源重叠(Source Overlap)———— 权重 4.0x
  两个页面引用相同的原始资料(通过 frontmatter sources[] 判断)
  → 权重最高的信号!因为来自同一源的内容,天然有强关联

信号 3: Adamic-Adar ———— 权重 1.5x
  两个页面共享共同邻居,按邻居度加权
  → 图的拓扑结构反映了"这两个页面在同一主题群中"

信号 4: 类型亲和力(Type Affinity)———— 权重 1.0x
  相同页面类型加分(entity-entity, concept-concept)
  → 最弱的信号,但有助于在稀疏区域发现关联

为什么源重叠的权重(4.0x)比直接链接(3.0x)还要高

这是一个看似反直觉但实际合理的设计选择。直接链接只代表「有人想到了建立这个链接」。但同一段源文本中讨论的多个概念,它们之间的关联是源作者建立的,比 Wiki 编辑者的跨页面链接更底层、更客观。此外,直接链接的覆盖率低 —— 不可能也不应该链接每对相关的页面 —— 而源重叠可以自动为共享同一来源的任意两个页面建立关联。

策略三:两策略结合

项目 3 的图构建本身更接近项目 2 —— 两者都使用 wikilinks 作为确定性边的来源。但项目 3 额外引入了 LLM 推断边,而项目 2 用多信号模型代替 LLM 推断。这是一个有趣的取舍:

  • LLM 推断边:成本高(每页一次 LLM 调用),但可能发现非显而易见的关联
  • 多信号模型:零 LLM 成本,但只能发现从已有数据可推导的关联

哪种更好?

取决于场景。在一个主题聚焦、源文档有重叠的研究型 Wiki 中,多信号模型(项目 2)很可能就够了 —— 源重叠信号已经捕获了大部分有意义的关联。在一个跨领域、源文档离散的个人知识库中,LLM 推断(项目 3)可能发现多信号模型无法发现的「意外关联」—— 比如你分别从两篇完全不同的文章中摄取了「复利效应」和「习惯养成」,LLM 可能推断出它们之间的隐含联系,而基于 wikilinks 和源重叠的模型则不能。

5.2 图谱分析:发现未知

图谱不仅是检索基础设施,也是知识发现工具。项目 2 和项目 3 都在图谱上运行了更深层的分析。

Louvain 社区检测(项目 2)

算法:graphology-communities-louvain
  ├── 自动发现知识聚类
  ├── 计算每个社区的凝聚力得分(cohesion)
  │   = 社区内的实际边数 / 社区内可能的边数
  └── 低凝聚力社区(< 0.15)被标记为"松散聚类"

可视化:
  ├── 节点颜色:按页面类型 或 按检测到的社区(可切换)
  ├── 节点大小:按链接数(平方根缩放)
  └── 边的粗细和颜色:按关联权重

Louvain 社区检测的实际价值在于:

  1. 验证预期:如果「注意力机制」相关页面自动聚成一个社区,说明你的 Wiki 结构合理
  2. 发现意外:如果一篇关于「图像分类」的论文和一篇关于「自然语言处理」的论文被算法归入同一个社区,这说明你的 Wiki 中某个跨领域的模式(比如「Transformer 架构」)正在自行浮现
  3. 识别碎片:高节点数但低凝聚力的社区说明这个主题的页面虽然多,但关联松散,可能需要一篇综合页面来整合

项目 2 还有两个创新的图谱分析功能:

意外关联检测(Surprising Connections)

综合得分 = 
  +3(跨社区边)         ← 连接了两个不同知识领域
  +1 或 +2(跨类型链接) ← entity-concept 或 concept-comparison
  +2(外围-中心连接)    ← 低度节点连接到高度节点
  +1(低权重强关联)     ← 页面虽连接但权重不高

这些卡片可被忽略(dismissable)
点击卡片 → 在图谱中高亮对应的节点和边

真正有价值的是「跨社区边」。当两个分属不同 Louvain 社区的概念通过 wikilinks 相连时,这意味着 Wiki 编辑者(在这个例子中是 LLM)发现了一个跨领域的联系。对于研究者来说,这些跨社区连接经常是创新的种子。

知识空白检测(Knowledge Gaps)

三种空白类型:
  1. 孤立页面(degree ≤ 1):没有充分连接到 Wiki 的其余部分
  2. 稀疏社区(cohesion < 0.15):主题存在但内部的关联松散
  3. 桥接节点(连接 3+ 个社区):关键枢纽节点,一旦过时会大面积影响

发现了知识空白后,系统会自动生成 Deep Research 按钮,预填 LLM 生成的研究主题。点击确认后自动执行补充研究。这是一个「检测 → 行动」的闭环设计。

项目 3 的图谱健康报告

项目 3 在图谱构建后生成的结构化健康报告也值得关注:

Health metrics:
  ├── 边/节点比(edges/node ratio):平均每个页面有多少 wikilinks
  ├── 孤儿节点比例:没有入站链接的页面百分比
  ├── 社区数量:独立知识聚类的数量
  ├── 平均链接密度
  └── God Nodes(degree > mean + 2*stddev):超级中枢节点

Phantom Hubs(幻影中枢):
  ├── 被 ≥ 2 个页面引用但指向不存在页面的 wikilinks
  └── 信号:这些可能是应该创建的新页面

Phantom Hubs 是一个特别聪明的概念。当一个术语被多个页面用 [[wikilink]] 格式引用,但该术语还没有自己的页面时,这说明:

  1. 这个术语在知识库中确实重要(多人引用)
  2. 应该为它创建一个定义/解释页面
  3. 这是一个高度可信的「缺失页面」信号(相比 LLM 推测的缺失概念)

这正是项目 3 的 heal.py 工具做的事情——自动发现幻影中枢并生成定义页面。


六、防幻觉机制:如何确保 Wiki 内容是可信的

幻觉(Hallucination)是 LLM 最根本的可靠性挑战。在 LLM Wiki 的上下文中,幻觉有两种形式:

  1. 事实幻觉:生成的 Wiki 内容与源材料不符
  2. 结构幻觉:生成的 wikilinks 指向不存在的页面,或声称页面间存在不存在的关联

五个项目各自构建了多层次防御体系。我们将其归类为三个防线:生成时防御生成后验证持续监控

6.1 第一道防线:生成时的约束

项目 1(claude-obsidian)—— 知识边界声明

Wiki 查询的系统指令明确要求:
  "Only answer from wiki content."
  "If the wiki lacks information on X, say so clearly."
  "Identify the specific gap."
  "Suggest finding a source to fill the gap."
  "Do not fabricate. Do not answer from training data
   if the question is about the specific domain in this wiki."

这看起来像是简单的 prompt engineering,但它实际上定义了一个关键契约:LLM 在这个系统中的角色是 Wiki 内容的综合者,而不是通用知识提供者。 当 Wiki 中没有关于某个主题的足够信息时,诚实地说「不知道」,比用训练数据中的泛泛之谈来填补空白更有价值。

为什么?因为 Wiki 的价值就在于它是经过策展、有来源支撑的知识集合。如果 LLM 在这里引入了无法追溯到源文件的信息,它破坏了 Wiki 本身的可信度。

项目 4(llm-wiki-compiler)—— 出处元数据系统

ProvenanceMetadata(每个页面都携带):
  ├── confidence: 0-1(LLM 自己评估的置信度)
  ├── provenanceState: extracted | merged | inferred | ambiguous
  │   extracted  = 直接来自一个源
  │   merged     = 综合多个源
  │   inferred   = LLM 推理得出
  │   ambiguous  = 多个源说法不一致
  └── contradictedBy: [slugs...](哪些概念与本页矛盾)

段落级引用:
  每个段落可尾附 ^[source.md] 引用(支持精确到行号 ^[source.md:42-58])

置信度在 frontmatter 中显式呈现:
  ---
  confidence: 0.85
  provenanceState: merged
  sources:
    - neurips2023-attention-survey.md
    - icml2024-flash-attention.md
  ---

这个设计中最值得关注的是 confidenceprovenanceState 的分离。置信度回答了「这个页面有多可靠?」,而出处状态回答了「这个内容是从哪里来的?」。两者回答不同的问题:

  • provenanceState: extracted, confidence: 0.9 → 直接从源提取,LLM 高度自信理解正确
  • provenanceState: inferred, confidence: 0.6 → LLM 推理,自己也觉得把握不大
  • provenanceState: ambiguous, confidence: 0.4 → 多个源说法不一,LLM 不确定

一个关键的设计教训来自项目 4 的开发历史:

在 v0.3 之前,inferredParagraphs 的计数来自 LLM 在提取阶段的自我估计。但团队发现这个估计「在页面生成之前就做出了,而且经常与模型实际产生的内容不一致」。v0.4 改为了直接从已生成页面中推导(body-derived)——统计没有源引用的散文字段数量。

这个教训揭示了防幻觉设计中的一个根本原则:不要相信 LLM 的自我评估。检查实际输出。

6.2 第二道防线:生成后的验证

项目 1(claude-obsidian)—— 矛盾标记系统

当新摄取的信息与已有页面声称的内容冲突时:

不静默覆盖旧声明。
而是在双方页面上添加:
  > [!contradiction]
  > 旧声明(来自 source-A.md): "注意力机制的复杂度是 O(n²)..."
  > 新声明(来自 source-B.md): "在稀疏注意力中复杂度可以降到 O(n log n)..."
  > 两个来源的引用
  > 需要人工审阅来解决

这解决了经典的知识管理问题:如何处理新信息与旧信息的冲突?

静默覆盖的危险在于:你可能丢失了一个虽然过时但仍然有价值的视角,或者新来源本身就是错误的但你没有进行独立验证。保留冲突、标记冲突、等待人类裁决——这个策略承认了 LLM 不应该是最终的真值仲裁者。

矛盾标记发生在两个时间点:

  1. 摄取时:摄取 prompt 明确要求 LLM 对比新旧信息
  2. Lint 时:语义层面的跨页面对比可以补充摄取时遗漏的矛盾

项目 4(llm-wiki-compiler)—— 审阅队列(Review Queue)

llmwiki compile --review
  → 生成的候选页面写入 .llmwiki/candidates/(不直接进入 wiki/)

llmwiki review show <id>
  → 显示候选页面
  → 预计算的 schema 违规和 provenance 违规

llmwiki review approve <id>
  → 候选页面正式迁入 wiki/
  → 刷新 index/MOC/embeddings
  → 持久化源状态

llmwiki review reject <id>
  → 归档候选(不删除,保留审计轨迹)

这个设计的核心思想是:把 LLM 的生成和人类的质量判断分离为独立步骤。 人类不需要实时盯着 LLM 的每一次操作,而是可以在方便时批量审核。审核时也不需要重新调用 LLM(候选页面存储了完整内容)。

为什么审阅队列比直接写入+回滚更好?

  1. 不污染 Wiki:低质量的候选从未进入 wiki/,避免了可能被其他 LLM 操作误引用的风险
  2. 可批量处理:积累一批候选后批量审阅,比每次摄取都打断流程更高效
  3. 有审计轨迹:拒绝的候选被归档,可以事后分析「为什么这个候选被拒绝了」,改进摄取策略

6.3 第三道防线:持续监控

项目 4 的 Lint 规则与防幻觉直接相关的有:

broken-citation         [error]   ^[file.md] 引用不存在的源文件
malformed-claim-citation[error]   引用格式错误(非数字行号、行号0、倒序范围)
low-confidence          [warning] 置信度 < 0.5
contradicted-page       [warning] 非空 contradictedBy
excess-inferred-paragraphs [warning] 未引用段落过多(> 2)

excess-inferred-paragraphs 规则值得特别提及。它检测的是:一个页面中有多少段落是没有源引用的纯 LLM 产物。如果有太多段落没有引用任何源文件,这可能意味着 LLM 在用自己的知识填充空白——这正是幻觉的典型表现。项目 4 使用 Unicode 属性(\p{L})来正确识别 CJK 等非拉丁语言中的段落。

项目 5 的审计系统(Audit)

项目 5 有一个独特的 4 阶段审计流程,被设计为跨全 Wiki 的真值追踪

Pass 1: Wiki 内容阶段
  ├── 复用或运行 librarian 扫描过时/低质量文章
  └── 评估 staleness 得分和质量得分

Pass 2: 输出依赖与漂移阶段
  ├── 遍历 output/ 下的所有产物
  ├── 解析依赖链: output -> wiki page -> raw source
  ├── 检查漂移:依赖的 wiki 页面是否比产物更新
  └── 分类: clean / drifted / provenance-gap / weak-evidence /
            contradicted / unresolved

Pass 3: 真值升级阶段(最关键)
  ├── 当信任存疑时,不停止在本地状态
  ├── 抓取原始来源
  ├── 使用支持性查询(寻找确认证据)和
  │   对抗性查询(寻找反驳证据)
  ├── 当 Agent tool 可用时,分为 support 和 attack 两个并行分支
  └── 产出:supported / weakened / contradicted / unresolved 判定

Pass 4: 会话出处阶段
  └── 评估执行历史的完整性: replayable / partial / missing

这个设计的核心洞察是:当怀疑信息不可靠时,不要止步于本地 Wiki 状态做检查。去外部验证。

审计系统与 Librarian 系统有明确的边界:

  • Librarian 留在 wiki/ 层内,评估 Wiki 内容的质量、时效性和一致性
  • Audit 跟踪证据到任意深度,包括在必要时从互联网上获取新鲜信息来验证或反驳 Wiki 的主张

这是一个两个层次的真值保证。Librarian 是例行质量控制(便宜、可频繁运行),Audit 是深层真值调查(昂贵、按需运行)。

6.4 防幻觉机制总结

防线 项目1 项目2 项目3 项目4 项目5
源接地 所有内容源于.raw 所有内容源于raw 所有内容源于raw 所有内容源于sources 所有内容源于raw
置信度 摄取时设置 摄取时设置 LLM自评 置信度+出处状态分离 摄取时设置
矛盾处理 [!contradiction]标记 摄取时+lint时检测 摄取flag contradictedBy元数据 审计pass检测
出处追溯 wikilinks sources[]字段 源页面链接 段落级+行号级引用 sources[]字段
人类审阅 可选 review items 无强制流程 Review Queue 摄取gate
格式验证 确定性后验证 validate_ingest 确定的wikilink+引用检查 15项lint规则
外部验证 Deep Research Audit(4阶段)
内容验证 10类lint 生成后验证 语义lint 11条lint规则 Librarian+Audit

核心设计原则总结:

  1. 不可变源层:所有项目都将源文件设为只读。这是防幻觉的最底层保障——Wiki 中的每个论断理论上都可以追溯到不可变的源文件。

  2. 置信度透明化:不假装 LLM 的每个输出都一样可靠。通过 frontmatter 中的 confidence/provenanceState 字段让读者知道哪些内容需要额外验证。

  3. 矛盾可见化:不静默解决矛盾。矛盾被标记、展示、等待人类判断。这防止了知识库在 LLM 的错误判断下悄悄退化。

  4. 人类在环路中:所有项目都保留了人类审查的入口(无论是审阅队列、审查项目、还是审计流程),承认 LLM 不应该也不可能是最终的真值裁决者。


七、知识库健康检查与问题排查

当 Wiki 增长到数百个页面、几十个源文件后,问题自然出现:有些页面开始过时,有些 wikilinks 断掉,有些概念被反复提及却没有自己的页面,有些源文件已被删除但其生成的页面仍留在 Wiki 中。如何系统性地发现和修复这些问题?五个项目在这个方面展现了丰富的设计思想。

7.1 「先便宜后贵」的原则

多个项目共享一个核心设计原则:先用零成本(无 LLM 调用)的确定性检查发现问题,只有在必要时才引入 LLM 进行语义分析。 这并非简单的成本考量——如果直接对有结构缺陷的 Wiki(空文件、断链接、索引不一致)做 LLM 语义分析,结果本身也是不可靠的。

项目 3(llm-wiki-agent)的层次化健康检查最清晰地体现了这个原则:

Health Check(每次会话都运行,零 API 调用):
  ├── 空/桩文件检测(内容 < 100 字符)
  │   → 捕获 rate-limit 导致的截断
  ├── 索引同步(index.md 的条目 vs 实际磁盘文件)
  │   → 双向检查:index 中有但磁盘没有的 → 假条目
  │   → 磁盘中有但 index 没有的 → 遗漏条目
  └── 日志覆盖(source 页面有无对应的 ingest 记录)

Lint Check(每 10-15 次摄取后运行,包含 LLM 调用):
  确定性部分:
    ├── 孤儿页面(无入站 wikilinks)
    ├── 断链接([[wikilinks]] 指向不存在页面)
    ├── 缺失实体页面(被 3+ 次引用但没有专属页面)
    ├── 稀疏页面(出站 wikilinks < 2)
    └── 图谱感知检查(需要 graph.json):
        ├── 中枢桩节点(高度数但内容 < 500 字符)
        ├── 脆弱桥接(两个社区间只有一条边连接)
        └── 孤立社区(零外部连接的知识聚类)

  LLM 辅助部分:
    ├── 语义矛盾检测(跨页面对比)
    ├── 过时内容
    ├── 知识空白 + 建议的新源
    └── 浅层概念(内容太短,需要扩充)

为什么 Health 和 Lint 要分开?

运行语义分析的前提是 Wiki 的结构是健康的。如果你的 index.md 与实际文件不同步(Health Check 的第 2 项),那么 Lint 阶段基于 index.md 做的所有分析都不可靠。在数据管道术语中,这是 「先验证 schema 和完整性,再做内容质量分析」

此外,Health Check 设计为每次会话自动运行——因为它零成本且运行极快——这意味着你永远不会在「不知道 Wiki 有结构问题」的情况下开始工作。

7.2 项目 1(claude-obsidian)的 10 类 Lint 检查

项目 1 将 Lint 系统化为 10 个检查类别,其中包含了 DragonScale 特有的一些高级检查:

C1:  孤儿页面(无入站 wikilinks)
C2:  死链接(wikilinks 指向不存在页面)
C3:  过时声明(被新源推翻但未更新的断言)
C4:  缺失页面(常被提及但缺少专属页面的概念)
C5:  缺失交叉引用(实体被提及但未链接)
C6:  Frontmatter 空缺(缺少必须字段:type, status, created, updated, tags)
C7:  空章节(标题下无内容)
C8:  过时的索引条目(已重命名/删除的页面仍在 index 中)
C9:  DragonScale 地址有效性(格式检查、唯一性检查、计数器一致性)
C10: DragonScale 语义平铺(基于嵌入的重复页面检测,余弦相似度)

两类 DragonScale 检查的工程价值:

C9(地址有效性)是形式检查——页码格式对不对、计数器有没有冲突。这种检查不可或缺,因为手动修复地址格式错误会产生级联影响(所有引用该地址的页面都需要更新)。

C10(语义平铺)更复杂。它用本地的 nomic-embed-text(通过 ollama)生成页面嵌入向量,然后计算余弦相似度。设定了分级的阈值:

  • ≥ 0.90 → 错误(几乎相同的两个页面,其中一个也许是重复生成)
  • 0.80-0.90 → 需审查(高度相似,可能有内容重叠)
  • < 0.80 → 正常

有个值得注意的细节:项目明确标注这些阈值是「保守的初始值,未经校准」。这种诚实的自我认知在技术文档中难得见到——它承认了阈值调校是一个需要实际操作和反馈的持续过程,而不是某个可以一次性算出的「正确值」。

7.3 项目 4(llm-wiki-compiler)的 11 条纯静态 Lint 规则

项目 4 的 Linter 有一个激进的设计决策:所有 11 条规则都是纯静态分析,零 LLM 调用。

broken-wikilink             [error]    [[链接]]指向不存在的页面
orphaned-page               [warning]  frontmatter标记为孤儿的页面
missing-summary             [warning]  摘要为空或缺失
duplicate-concept           [error]    多个页面不区分大小写同名
empty-page                  [warning]  有frontmatter但内容极短(<50字符)
broken-citation             [error]    ^[file.md]引用不存在的源或越界行号
malformed-claim-citation    [error]    引用格式错误(非数字范围、行号0、倒序)
low-confidence              [warning]  置信度 < 0.5
contradicted-page           [warning]  contradictedBy 非空
excess-inferred-paragraphs  [warning]  未引用段落 > 2
schema-cross-link-minimum   [warning]  wikilinks少于其page kind要求的最小值

为什么全是静态分析?

这个设计选择的逻辑是自洽的:Lint 应该是对已经发生的事物的机械验证,而非对内容质量的主观判断。 空文件、断链接、格式错误——这些都是不依赖 LLM 就可以确定的事实。内容质量的主观评估——比如「这个概念的描述是否足够深入?」——应该留给 Librarian 类型的系统(像项目 5 那样),或者直接由人在审阅队列中判断。

项目 4 把 low-confidencecontradicted-page 也归入静态分析的范畴,因为 confidence 和 contradictedBy 是存储在 frontmatter 中的结构化元数据——检查它们只是读取已有数据并比较阈值,不涉及 LLM。

Schema-aware 规则(schema-cross-link-minimum 允许不同页面类型设置不同的最小 wikilink 要求。例如,概念页面(concept)可能需要至少 3 个出站链接才能确保它充分连接到 Wiki 的其余部分,而比较页面(comparison)可能需要至少 2 个。这个规则是基于「某些页面类型天然比另一些更需要高连接度」的合理假设。

7.4 项目 5(llm-wiki-master)的 Lint 即迁移

项目 5 的 Lint 系统(15 项检查 C1-C15)有一个独特的哲学立场:

“Lint IS the migration.” —— 当结构或 schema 规则改变时,更新 lint 规则。不要写迁移代码。

这个思想值得花时间理解。通常,当一个知识库系统的目录结构或命名约定改变时,开发者会编写一个迁移脚本把旧格式转换为新格式。在项目 5 中,这不是一个脚本——而是 lint 规则。

关键规则:
  C11: 规范位置(文件的实际路径应符合 frontmatter 中的声明)
  C13: Frontmatter 别名(键名和值的等价映射表)
  C15: 缺失的 volatility 字段

工作机制:
  1. Lint 扫描发现路径不正确的文件(比如本该在 wiki/concepts/ 的文件
     出现在了 wiki/sources/)
  2. 自动修复将文件移动到正确的位置

  为什么旧版本的文件可能路径不对?
    可能是用户在旧版本中手动放置的
    也可能是旧版本的 AI 按旧规则放置的

  在 C11 的视角下,这两种原因是等效的——都是"文件在错误的地方"

Lint 即迁移的好处:

  • 简化代码:不需要维护版本特定的迁移脚本
  • 幂等性:多次运行 lint 不会产生不同的结果——它只是把每个文件放到它该在的位置
  • 统一处理:用户错误和版本迁移被同一种机制处理

Lint 即迁移的潜在风险:

  • 如果自动修复做出了错误的判断(将文件移动到了错误的地方),没有单独的迁移脚本可以回滚
  • 复杂的迁移(需要内容重写而非简单移动文件)不能仅仅通过 lint 规则表达
  • 依赖 lint 规则的精确性——一个有 bug 的 lint 规则在每次运行时都会造成损害

但总体来说,当你的数据格式是普通 Markdown 文件(而非数据库记录或二进制格式),文件的移动和 frontmatter 的更新确实是主要迁移操作。Lint 即迁移在这种场景下是合理的。

7.5 项目 5 的 Librarian:内容质量的量化评估

Librarian 是项目 5 中专用于评估 Wiki 内容质量的子系统,与 Audit(外部真值追踪)形成了互补。

Staleness Score(过时评分,0-100):
  4 个维度各 25 分:
    源文件新鲜度     × 由 volatility 等级决定的衰减曲线
    验证新鲜度       × hot=30天半衰期, warm=90天, cold=365天
    编译新鲜度       × 自上次 LLM 重新处理以来的时间
    源链完整性       × 所有源文件是否仍然可访问

Quality Score(质量评分,0-100):
  4 个维度各 25 分:
    深度(内容的详尽程度)
    源质量(引用的源文件的权威性)
    连贯性(逻辑清晰、结构合理)
    实用性(对理解主题的帮助程度)

两级升级策略:
  Level 1:仅元数据(便宜)
    → 扫描所有页面的 frontmatter,不需要读取页面内容
    → 对大部分文章来说,元数据已经提供了足够的评分信号

  Level 2:深度阅读(贵)
    → 仅对 Level 1 中识别出的问题文章,读取完整内容
    → 仅在需要时使用,控制成本

衰减曲线的设计值得注意:

Staleness 不是简单的「距上次更新已经 N 天了」,而是通过衰减曲线按 volatility 进行缩放。hot(易变话题)的内容每 30 天半衰,cold(稳定知识)的内容每 365 天半衰。这符合知识的实际特征——一篇关于最新 AI 基准测试的文章几天后可能就过时了,而一篇关于基础数学定理的文章几年后仍然有效。

为什么有两个独立评分(Staleness 和 Quality)?

因为它们是正交的。一个高质量的、深度撰写的概念页面可以是过时的(比如写于两年前,而该领域已经大幅进展)。一个新鲜出炉的页面也可以是浅薄的。分开评分让你可以独立追踪这两个维度,做出不同的处理决策:

  • 高 Staleness + 高 Quality → 需要刷新(内容基础好,只需要更新)
  • 高 Staleness + 低 Quality → 需要重写(旧且差,不值得保留)
  • 低 Staleness + 低 Quality → 需要重写(新但差,摄取可能有缺陷)
  • 低 Staleness + 高 Quality → 健康(最好的状态)

7.6 项目 5 的 Refresh:安全地更新源

当源文件的内容在外部发生了变化(比如一篇文章被更新了,或者 GitHub 仓库有了新的 commit),如何处理?

项目 5 的 Refresh 命令采用了最保守的策略:

流程:
  1. 重新抓取源 URL
  2. 对比内容 → 分类变更:
     cosmetic(格式/排版变化)  → 忽略
     additive(新增内容)        → 人类评估
     contradictory(内容矛盾)  → 人类评估
  3. 呈现差异摘要
  4. 人类决定:是否重新摄取?
  5. 绝不自动重新编译

为什么不自动重新摄取?

因为源的变化可能大到需要创建全新的 Wiki 页面(而不是更新已有页面),也可能小到只是改了个错别字。LLM 自己不能做出这个判断——它需要人类判断这个变更的重要性以及它对 Wiki 结构的潜在影响。

此外,自动重新摄取可能产生级联效应。如果源 A 的变化导致 Wiki 页面 X 被更新,而 X 又被 5 个其他页面引用,这 5 个页面是否需要更新?人类需要判断这个传播范围,而非让 LLM 自行决定。


八、五项目综合对比矩阵

经过三个部分、七个大章节的详细分析,现在可以给出一个高层次的综合对比。

8.1 核心能力对比

能力 claude-obsidian(1) llm_wiki(2) agent(3) compiler(4) master(5)
摄取策略 1次LLM调用+文件/URL/图片 2步链式思维 1次LLM调用 两阶段批量编译 研究→摄取→编译管线
查询检索 index+hot cache, 3层深度 4阶段管线(分词语义图谱预算) index+关键词+图谱回退 chunk级语义+BM25重排 index 3跳导航,3层深度
知识图谱 无内置图,用Obsidian图 sigma.js+4信号+社区检测 2步构建(确定+推断) 无内置图 无内置图
防幻觉 源接地+矛盾callout+边界声明 置信度+review items 源接地+后验证 出处系统+审阅队列+11条lint 独立可信度评分+审计4阶段
健康检查 10类lint+Hot Cache+DragonScale检查 生成后验证 Health+Lint分层 11条纯静态lint 15类lint+Librarian+Audit
自主研究 3轮autoressarch循环 Deep Research(Tavily) 5/8/10 Agent并行
会话管理 Hot Cache+Hook自动化 多会话持久化 Agent原生恢复 无专门机制 –resume模式
批量操作 并行Agent批量摄取 串行队列+崩溃恢复 手动逐个 增量批量编译 并行搜索+串行编译
多主题 独立Wiki 场景模板 独立Wiki 独立Wiki Hub多主题架构
运行方式 Claude Code插件 桌面应用 Agent指令+脚本 CLI工具 Claude Code插件+多运行时

8.2 设计哲学对比

维度 项目1 项目2 项目3 项目4 项目5
核心理念 LLM是Wiki维护者 可视化知识引擎 零基础设施Agent 知识编译器 研究驱动的Wiki
复杂度谱 高(11 Skills + DS) 极高(全功能GUI) 低(脚本+指令) 中高(完整CLI) 高(17命令+5模式)
学习成本 低(GUI直观) 高(需要理解Agent) 高(概念最多)
扩展性 好(Plugin生态) 受限于GUI 极好(零依赖) 好(CLI可组合) 极好(Hub架构)
审计/追溯 git+log.md git+log.md git+log.md 块级引用+审阅归档 4阶段审计+事件日志
目标用户 Obsidian用户, 个人知识管理 所有人(有GUI门槛低) 技术用户, Agent用户 开发者, CI集成 深度研究者
最突出的创新 DragonScale记忆扩展, Hot Cache 知识图谱+社区检测, 可视交互 零基础设施的Agent原生设计 审阅队列, 块级出处, 增量编译 反确认偏误, Hub架构, Lint即迁移

8.3 技术栈对比

维度 项目1 项目2 项目3 项目4 项目5
主要语言 Markdown + Shell Rust + TypeScript Python TypeScript Markdown + Shell
运行环境 Claude Code Tauri桌面应用 Python + LLM Agent Node.js >= 24 Claude Code
LLM后端 Claude(通过Claude Code) 多Provider(7种) Claude/Codex/Gemini Anthropic+OpenAI+Ollama Claude(通过Claude Code)
存储 Markdown文件 Markdown + LanceDB Markdown文件 Markdown + LanceDB Markdown文件
向量搜索 可选(LanceDB) 嵌入+BM25
CI/CD Git hooks + Obsidian Git GitHub Actions GitHub Actions + Husky 结构测试 + 同步测试

九、架构选择指南:你应该用哪个?

没有一个项目适合所有场景。以下是基于需求和偏好的选择建议:

场景 1:「我是 Obsidian 用户,想让 AI 帮我维护 Wiki」

选项目 1(claude-obsidian)

理由:它是专门为 Obsidian 设计的,11 个 Skill 覆盖了所有核心操作,Hot Cache 让每会话的上下文恢复几乎无成本,DragonScale 提供了可选的记忆扩展。如果你的主要工作流是「在 Obsidian 中浏览 Wiki,让 Claude Code 在后台维护」,这个项目是为你量身定做的。

需要注意:它要求你使用 Claude Code 作为 Agent 平台,且需要一定的 CLI 操作能力。

场景 2:「我想要一个完整的桌面应用,不想碰命令行」

选项目 2(llm_wiki)

理由:它是唯一一个提供完整 GUI 的项目。三栏布局、可视化知识图谱、Chrome 网页裁剪扩展、拖拽式的文件导入、实时活动面板——这些让非技术用户也能轻松使用。知识图谱的社区检测和意外关联功能对研究者特别有价值。

需要注意:Tauri 应用需要安装和配置 LLM API key。相比 Agent 方案,GUI 的灵活性受限于应用的功能边界。

场景 3:「我想用最轻量、最灵活的方式开始」

选项目 3(llm-wiki-agent)

理由:它是五个项目中最轻量的。没有桌面应用、没有 CLI 工具、没有数据库。本质上就是一套 Python 脚本和 Agent 指令。你可以用任何 LLM Agent (Claude Code, Codex, Gemini CLI)来驱动它。零基础设施意味着你可以立即开始,且可以随意修改来适应自己的需求。

需要注意:缺乏自动化和可视化支持。所有的摄取和查询都需要手动触发。对 Agent 的工作原理需要一定理解。

场景 4:「我需要 CI 集成、自动化编译和严格的出处管理」

选项目 4(llm-wiki-compiler)

理由:它是唯一一个以「编译」为核心隐喻的项目,拥有最成熟的增量编译、审阅队列、MCP 服务器和 Claim 级别的出处引用。如果你需要把知识编译集成到 CI/CD 流水线中(例如:每次有新的研究论文加入仓库时自动编译 Wiki),或者需要严格的出处追溯能力,这是最佳选择。

需要注意:CLI 工具需要 Node.js 环境,学习曲线中等。

场景 5:「我在做深度主题研究,需要多视角、反偏误的研究流程」

选项目 5(llm-wiki-master)

理由:它的多 Agent 并行研究、独立可信度评分、反确认偏误机制和 4 阶段审计系统在深度研究场景中无出其右。Hub 多主题架构适合同时维护多个相关但独立的 Wiki。Lint 即迁移的设计让长期维护压力最小。

需要注意:概念最多、学习成本最高。目前以 Claude Code 为主要目标平台。多 Agent 研究可能产生较高的 API 费用。


十、跨项目的共性洞察与思考

在对五个项目进行了详细分析后,有几个跨项目的观察值得分享:

10.1 核心思想的弹性

Karpathy 的原始设计只有 75 行,是个「想法文件」。但五个项目从这个想法中衍生出了完全不同的实现形态——从最轻量的 Agent 指令到完整的桌面应用。这说明 Karpathy 捕捉到的是一个真正有弹性的模式,而不是一个特定的技术方案。

10.2 共同的未被解决的问题

尽管五个项目都非常优秀,但它们在以下几个问题上的答案都还不完美:

规模化挑战:所有项目都面临 Wiki 规模增长(数百→数千→数万页面)带来的挑战。Index.md 的策略在几百页时完美工作,但超出这个规模需要引入向量搜索。而引入向量搜索又带来了嵌入模型选择、存储、更新和混合检索的全套复杂性。目前只有项目 2 和 4 部分解决了这个问题。

多 Agent 并发:当多个 LLM Agent 会话同时操作同一个 Wiki 时怎么办?项目 5 的派生索引协议是一个聪明的答案,但它在高并发下的表现还需要实际验证。大多数项目目前默认单 Agent 操作。

信任衰减追踪:当一个 Wiki 页面的源文件被删除或修改了,什么程度的变化会导致下游信任的重新评估?项目 5 的 Audit 系统开始了这个方向的工作,但完整的信任传播模型——包括概率依赖和条件独立的处理——还是一个开放问题。

Schema 演化:随着使用,Wiki 的结构(分类体系、frontmatter 约定、命名规则)不可避免地会演化。项目 5 的 Lint 即迁移是最优雅的方案,但还不够——如何验证一个结构变化的全局一致性?如何确保 LLM 在结构变更后继续遵循新的约定?这些问题还没有系统性的答案。

10.3 五个项目互相可以学习什么

从分析者的视角,每个项目都可以从其他项目中汲取灵感:

  • 项目 1 可以学习项目 4 的审阅队列机制——在将 LLM 生成的页面写入 Wiki 前增加可选的人类审查环节
  • 项目 2 可以学习项目 5 的独立可信度评分——在摄取前独立评估源的质量,而非仅依赖 LLM 的置信度
  • 项目 3 可以学习项目 1 的 Hot Cache——在多个 session 之间快速恢复上下文
  • 项目 4 可以学习项目 5 的反确认偏误机制——在自主研究时引入对立视角
  • 项目 5 可以学习项目 4 的 Claim 级出处引用——将引用精度从页面级提升到行号级

每个项目都有其独特的创新,但它们构成了一个互相补充的知识体系。通过研究这五个项目,你不仅理解了 LLM Wiki 这个模式的各种工程实现方式,也看到了个人知识管理在 AI 时代的可能形态。

10.4 最后的一点个人思考

Karpathy 的原始文章提到了 Vannevar Bush 1945 年的 Memex 构想——一个可以存储个人所有书籍、记录和通信的设备,具有关联式导航和快速检索能力。Bush 写道,Memex 是「人类记忆的扩大和直接补充」。

回头看,网页成为了 Bush 设想的反面:它是公开的、被动的、关联由他人决定(超链接由网页作者创建)。LLM Wiki 在某种程度上是一种回归——私有的、主动策展的、关联由自己(通过 LLM)决定的个人知识系统。

Bush 无法解决的核心问题是「谁来维护?」。70 年后的今天,LLM 可以在可承受的成本下承担这个角色。这是这五个项目共同的洞察——也是它们共同试图解决的问题。

每个项目采用了不同的架构来实现这个洞察,但它们都验证了同一个核心命题:当一个 LLM Agent 被赋予了明确的规则(Schema)、不可变的源材料(Raw)、和持久化的知识产物(Wiki),它能够成为一个称职的个人知识管理者。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐