LLM-wiki 智能知识生成
调研时间:2026年6月 | 系列:AI知识库系统调研 第3篇
目录
- 什么是 LLM-wiki:Karpathy 范式
- 三个核心操作
- claude-obsidian 深度解析
- llm-wiki-compiler 实战
- Zero-RAG 方案
- 知识图谱增强
- WikiChat:幻觉抑制设计
- 完整 wiki 生成 Pipeline 实现
- 与 Hermes + Obsidian 的结合
- 选型建议
1. 什么是 LLM-wiki:Karpathy 范式
1.1 Karpathy 其人
Andrej Karpathy 是 AI 领域最具影响力的工程师之一。他曾担任 OpenAI 的联合创始成员和研究科学家,后加入特斯拉担任 AI 总监,主导 Autopilot 感知系统的研发。2023年他离开特斯拉重返 OpenAI,并于2024年再次离职,专注于 AI 教育内容创作(包括广受欢迎的 karpathy/nn-zero-to-hero 课程)。
2026年4月,Karpathy 在 GitHub Gist 上发布了一篇引发广泛讨论的文章:
“Compile knowledge into a wiki instead of retrieving at query time” (把知识编译进 wiki,而非查询时检索)
这一想法虽然简洁,却从根本上挑战了过去两年 RAG(检索增强生成)的主流范式。
1.2 “编译知识 vs 检索知识”的核心理念
Karpathy 的核心类比来自编译器设计:
- 传统 RAG = 解释执行:每次查询都现场检索文档、分 chunk、向量相似度计算、召回、注入上下文
- LLM-wiki = 提前编译:在摄取阶段,LLM 已经理解并整合了知识,生成干净的 wiki 页面;查询时直接读取编译产物
这个类比非常精准。传统编程语言的解释器每次运行都重新解析源码,而编译器将源码一次性转换为机器码,之后执行零解析开销。LLM-wiki 的”编译”同理:把理解和整合的计算成本提前支付,换取查询时的高效低耗。
1.3 传统 RAG 的核心问题
传统 RAG 在工程实践中暴露出几个痛点:
问题一:查询时 token 消耗失控
每次查询都需要:
- 将问题转为 embedding(1次 API 调用)
- 向量相似度检索(通常召回 top-k = 5~20 个 chunk)
- 将 k 个 chunk 塞入上下文(每个 chunk 可能 500-1000 token)
- 加上系统提示、历史对话
一次查询的上下文可能高达 8000-20000 token,且每次查询都重复这个开销。
问题二:检索质量不稳定
Chunk 切分是个棘手问题:
- 按固定长度切分会割裂语义
- 一个概念可能跨越多个 chunk,单 chunk 召回导致信息不完整
- 向量相似度无法捕获”间接相关”的知识(需要关系推理)
问题三:知识无法演化
原始文档不可变,新知识无法与旧知识融合。两篇讨论同一概念的文章,在向量空间中孤立存在,LLM 每次查询都要重新”理解”它们的关系。
问题四:运维负担
维护一个向量数据库并非易事:embedding 模型更新时需要重建全量索引、分布式向量库的运维复杂度、嵌入向量的存储成本……
1.4 Karpathy 模式的优势
LLM-wiki 系统性解决了上述问题:
| 维度 | 传统 RAG | LLM-wiki |
|---|---|---|
| 知识整合 | 每次查询临时整合 | 摄取时一次整合 |
| token 消耗 | 高(大量 chunk 注入) | 低(精炼 wiki 页) |
| 知识质量 | chunk 质量参差 | LLM 编写,结构化 |
| 可引用性 | 引用来源文件 | 引用具体 wiki 页 |
| 可维护性 | 原始文档不可变 | wiki 页可由 LLM 更新 |
| 知识演化 | 无 | 新 ingest 自动合并 |
| 可读性 | 原始文档(可能杂乱) | LLM 重写,人类可读 |
1.5 核心目录结构
Karpathy 提出的目录结构极简且清晰:
knowledge-base/
├── raw/ # 不可变来源(原始文档、URL 内容)
│ ├── 2026-04-01-article.md
│ ├── paper-xyz.pdf
│ └── web-snapshot-001.md
├── wiki/ # LLM 维护的知识页(核心产物)
│ ├── index.md # 全局索引,所有页面的入口
│ ├── hot.md # 高频概念快速索引(可选)
│ └── concepts/
│ ├── transformer.md
│ ├── attention-mechanism.md
│ └── rag-vs-wiki.md
└── log.md # 摄取日志,记录每次 ingest 的操作
设计原则:
raw/只写不改,保留原始来源的完整性wiki/是”活文档”,由 LLM 负责创建和更新log.md提供审计轨迹,出问题时可追溯- wiki 页之间通过
[[wikilink]]语法互相链接,形成知识图
2. 三个核心操作
LLM-wiki 系统的所有功能可归纳为三个操作:Ingest、Query、Lint。这个设计极其干净,与数据库的 Write/Read/Vacuum 操作对应。
2.1 Ingest(知识摄取)
触发时机:用户提供新的知识来源(URL、本地文件、文本片段)
完整流程:
输入: URL / 文件路径 / 原始文本
│
▼
Step 1: 获取内容
│ - URL → HTTP 抓取 + Markdown 转换
│ - PDF → 文本提取(PyPDF2 / pdfplumber)
│ - 直接文本 → 原样使用
│
▼
Step 2: 保存到 raw/(加时间戳命名,不可变)
│
▼
Step 3: LLM 分析(核心步骤)
│ - 提取主要实体(人名、工具名、概念名)
│ - 提取核心概念(该文档讲了哪些知识点)
│ - 识别与现有 wiki 页的关系
│
▼
Step 4: 决策 —— 为每个概念
│ - 已有对应 wiki 页 → 更新/合并
│ - 尚无对应 wiki 页 → 创建新页
│
▼
Step 5: 生成/更新 wiki 页面
│ - 每页一个概念,结构化 Markdown
│ - 加入 [[wikilink]] 指向相关概念
│ - 加入来源引用(指向 raw/ 中的文件)
│
▼
Step 6: 更新 index.md(新页面注册进全局索引)
│
▼
Step 7: 写入 log.md(记录本次 ingest 的操作摘要)
wiki 页面的标准结构:
# Transformer 架构
**类别**:深度学习模型架构
**最后更新**:2026-04-15
**来源**:[[raw/attention-is-all-you-need.md]], [[raw/2026-04-blog.md]]
## 核心定义
Transformer 是一种完全基于注意力机制的神经网络架构,
由 Vaswani et al. 在 2017 年论文 "Attention Is All You Need" 中提出。
## 关键组件
- **Self-Attention**:见 [[注意力机制]]
- **Position Encoding**:见 [[位置编码]]
- **Feed-Forward Network**:每个 token 独立的全连接层
## 与相关概念的关系
- 取代了 [[RNN]] 和 [[LSTM]] 在序列建模中的地位
- 是 [[BERT]], [[GPT]], [[LLaMA]] 的基础架构
- 计算复杂度:O(n²·d),见 [[注意力复杂度分析]]
## 关键结论
1. 并行计算能力优于 RNN(无顺序依赖)
2. 长距离依赖捕获能力强
3. 规模化效果好(scaling laws)
## 未解问题
- 二次复杂度在超长序列上的瓶颈(参见 [[线性注意力]])2.2 Query(知识查询)
三级查找策略(从快到慢,找到即止):
用户问题: "Transformer 的注意力机制是怎么工作的?"
│
▼
Level 1: 查 hot.md(高频概念快速入口)
│ 命中 → 直接返回相关页面内容
│ 未命中 ↓
▼
Level 2: 查 index.md(全局索引,关键词匹配)
│ 命中 → 定位到具体页面(如 wiki/attention-mechanism.md)
│ 未命中 ↓
▼
Level 3: BM25 + 语义搜索(全文扫描 wiki/)
│ 召回相关页面
▼
组装回答:
- 引用具体 wiki 页(非原始文档)
- 表达置信度("根据 [[注意力机制]] 页面...")
- 标注不确定点("该 wiki 页未涵盖 X,建议 ingest 更多来源")
带引用的回答示例:
回答:Transformer 的注意力机制通过计算 Query、Key、Value 的点积来分配权重。
具体来说,对于每个 token,计算它与所有其他 token 的相关性分数,
softmax 归一化后作为权重对 Value 进行加权求和。
引用:
- [[wiki/attention-mechanism.md]] § 计算流程
- [[wiki/transformer.md]] § 关键组件 > Self-Attention
置信度:高(两个互相印证的 wiki 页)
2.3 Lint(知识维护)
Lint 是 LLM-wiki 系统的”健康检查”,类比代码 linter,定期运行以发现知识库的结构问题:
检查项 1:孤儿页面(Orphaned Pages)
# 孤儿页 = 没有任何其他页面链接到它的页面
orphaned = []
for page in wiki_pages:
if not any(page.name in other.links for other in wiki_pages if other != page):
orphaned.append(page)
# 处理:提示 LLM 检查是否应该链接到现有概念,或标记为待清理检查项 2:陈旧声明(Stale Claims)
# 陈旧 = 页面中的事实与 raw/ 中更新的来源冲突
for page in wiki_pages:
sources = page.get_source_references()
for src in sources:
if src.update_time > page.last_updated:
flag_as_stale(page, src)
# 处理:触发 re-ingest,更新 wiki 页检查项 3:死链(Broken Links)
# 死链 = [[wikilink]] 指向不存在的页面
for page in wiki_pages:
for link in page.wikilinks:
if not wiki.exists(link.target):
report_broken_link(page, link)
# 处理:自动创建占位符页面,或提示用户检查项 4:缺失交叉引用(Missing Cross-References)
# 两个页面讨论了相关概念,但没有互相引用
# 通过语义相似度发现潜在关联
for (page_a, page_b) in similar_page_pairs:
if not page_a.links_to(page_b) and not page_b.links_to(page_a):
suggest_cross_reference(page_a, page_b)3. claude-obsidian 深度解析
GitHub: https://github.com/AgriciDaniel/claude-obsidian
Stars: 6,397(2026年4月爆发)
定位:最成熟的 LLM-wiki 实现,深度集成 Claude Code
3.1 15个 Skills 完整列表
claude-obsidian 提供了一套完整的技能体系,覆盖从摄取到维护的全流程:
| Skill 名称 | 功能描述 |
|---|---|
wiki-ingest | 将 URL/文件摄取为 wiki 页面 |
wiki-query | 智能查询 wiki,带引用回答 |
wiki-lint | 健康检查(孤儿/陈旧/死链) |
wiki-update | 手动更新指定 wiki 页面 |
wiki-merge | 合并两个相似的 wiki 页面 |
wiki-graph | 生成知识图谱可视化 |
wiki-export | 导出 wiki 为多种格式 |
autoresearch | 自动研究某个话题(自动 ingest 多个来源) |
canvas | 在 Obsidian Canvas 中可视化概念关系 |
think | 基于 wiki 进行多步推理 |
daily-note | 生成每日笔记,整合 wiki 链接 |
retrospective | 回顾指定时间段内的知识增量 |
gaps | 分析 wiki 中的知识缺口 |
synthesize | 跨多个 wiki 页合成新见解 |
hot-rebuild | 重建 hot.md 高频索引 |
3.2 wiki-ingest 工作流详解
wiki-ingest 是整个系统的心脏,其内部 prompt 设计经过精心调校:
第一阶段:来源分析
系统提示(精简版):
你是一个知识摄取专家。你的任务是从给定内容中提取知识,
并将其组织为结构化的 Obsidian wiki 页面。
规则:
1. 每个独立概念对应一个 wiki 页面(不要把所有内容塞进一页)
2. 预计生成 8-15 个页面(太少说明提取不够,太多说明颗粒度过细)
3. 使用 [[wikilink]] 语法建立概念间连接
4. 每个页面必须包含:定义、关键要点、与其他概念的关系、来源引用
5. 优先复用现有 wiki 页(合并 > 新建)
现有 wiki 索引:
{{ index.md 内容 }}
待摄取内容:
{{ raw 文档内容 }}
第二阶段:页面生成
LLM 输出一个 JSON 结构,指定要创建/更新的页面:
{
"actions": [
{
"type": "create",
"path": "wiki/concepts/attention-mechanism.md",
"content": "# 注意力机制\n\n..."
},
{
"type": "update",
"path": "wiki/concepts/transformer.md",
"section": "与相关概念的关系",
"append": "- 核心依赖 [[注意力机制]]"
},
{
"type": "update_index",
"add_entries": ["注意力机制", "Transformer"]
}
]
}第三阶段:写入文件系统
claude-obsidian 通过 MCP 工具执行文件写入,整个过程无需人工介入。
3.3 混合检索机制(v1.7+)
claude-obsidian v1.7 引入了显著提升检索质量的混合检索机制,精度提升 +32pp top-1(基于 Anthropic 2024 内部研究):
三层检索架构:
查询: "什么是位置编码?"
│
├─ Layer 1: BM25 全文检索
│ - 关键词精确匹配
│ - 特别适合技术术语、专有名词
│ - 返回: top-20 候选页面
│
├─ Layer 2: 上下文前缀语义检索(Anthropic 2024 研究成果)
│ - 为每个 chunk 添加文档级上下文前缀:
│ "该页面属于 Transformer 架构系列,讨论序列位置信息的编码方式..."
│ - 解决"缺乏上下文的 chunk 语义漂移"问题
│ - 返回: top-20 候选页面(与 BM25 有重叠)
│
└─ Layer 3: Cosine Rerank
- 对 Layer 1+2 的候选取并集
- 用完整问题 embedding 重新计算余弦相似度
- 最终返回: top-5 精排结果
上下文前缀 Prompt(核心创新):
def add_context_prefix(chunk: str, page: WikiPage) -> str:
"""
Anthropic 2024 研究:为 chunk 添加文档级上下文
显著提升语义检索的准确性
"""
prefix = f"""该内容来自知识库页面《{page.title}》。
页面类别:{page.category}。
页面摘要:{page.summary[:200]}。
以下是具体内容:
"""
return prefix + chunk3.4 多写者安全设计(Advisory Locking)
当多个 Claude Code Agent 并行执行 wiki-ingest 时(例如 autoresearch 同时处理 10 个 URL),存在并发写入同一 wiki 页面的风险。claude-obsidian 通过 advisory locking 解决:
import fcntl
import os
from contextlib import contextmanager
@contextmanager
def wiki_page_lock(page_path: str):
"""
Advisory lock for wiki page writes.
多 Agent 写同一页面时,排队执行而非竞争覆盖。
"""
lock_path = page_path + ".lock"
lock_file = open(lock_path, 'w')
try:
# 获取排他锁(阻塞等待,非忽略)
fcntl.flock(lock_file, fcntl.LOCK_EX)
yield
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
lock_file.close()
os.unlink(lock_path)
# 使用方式
with wiki_page_lock("wiki/concepts/transformer.md"):
# 读取现有内容
existing = read_page("wiki/concepts/transformer.md")
# LLM 合并新内容
merged = llm_merge(existing, new_content)
# 写入
write_page("wiki/concepts/transformer.md", merged)3.5 四种知识组织方法论对比
claude-obsidian 支持在初始化时选择知识组织框架:
| 方法论 | 核心理念 | 适用场景 | 目录结构 |
|---|---|---|---|
| LYT(Linking Your Thinking) | Maps of Content (MOC) 作为导航层 | 个人知识管理、思维导图式 | atlas/ + spaces/ |
| PARA | 按可行动性组织(项目/领域/资源/档案) | 项目驱动型工作、GTD | Projects/ + Areas/ + Resources/ + Archives/ |
| Zettelkasten | 原子化笔记 + 唯一 ID + 双向链接 | 学术研究、长期知识积累 | fleeting/ + literature/ + permanent/ |
| Generic | 扁平结构 + 标签系统 | 通用场景、快速上手 | wiki/ + tags/ |
对于 AI 知识库场景,推荐 Generic 或 Zettelkasten:
- Generic:简单,LLM 生成的结构最自然
- Zettelkasten:适合需要严格学术引用追踪的场景
3.6 MCP 双模式配置
模式一:Obsidian Local REST API
// claude_desktop_config.json 或 .claude/settings.json
{
"mcpServers": {
"obsidian": {
"command": "npx",
"args": ["-y", "mcp-obsidian"],
"env": {
"OBSIDIAN_API_URL": "http://localhost:27123",
"OBSIDIAN_API_KEY": "your-api-key-here"
}
}
}
}需要在 Obsidian 中安装 “Local REST API” 插件并启用。
模式二:文件系统直接访问(@bitbonsai/mcpvault)
{
"mcpServers": {
"vault": {
"command": "npx",
"args": ["-y", "@bitbonsai/mcpvault"],
"env": {
"VAULT_PATH": "/home/user/ObsidianVault"
}
}
}
}无需 Obsidian 运行,直接读写文件系统,更适合自动化管道。
4. llm-wiki-compiler 实战
GitHub: https://github.com/atomicstrata/llm-wiki-compiler
Stars: 1,486
定位:npm 工具链,适合开发者工作流集成
4.1 安装和初始化
# 全局安装
npm install -g llm-wiki-compiler
# 初始化新的 wiki 项目
mkdir my-knowledge-base && cd my-knowledge-base
llmwiki init
# 初始化后的结构
# .llmwiki.json - 配置文件
# raw/ - 来源目录
# wiki/ - 编译输出
# wiki/index.md - 自动生成配置文件 .llmwiki.json:
{
"model": "claude-opus-4-5",
"api_key_env": "ANTHROPIC_API_KEY",
"wiki_dir": "wiki",
"raw_dir": "raw",
"language": "zh-CN",
"concepts_per_source": 10,
"link_style": "obsidian",
"incremental": true,
"hash_file": ".llmwiki.hashes"
}4.2 两阶段编译过程详解
Phase 1:概念提取
llmwiki compile raw/my-article.md --phase 1内部执行:
# Phase 1 内部逻辑(简化)
def phase1_extract_concepts(source_path: str) -> List[Concept]:
content = read_file(source_path)
prompt = f"""
从以下内容中提取 5-15 个核心概念。
对每个概念提供:
- name: 概念名称(英文/中文)
- aliases: 别名列表
- definition: 一句话定义
- importance: high/medium/low
- related_to: 与哪些其他概念相关
内容:
{content}
"""
return llm_extract(prompt) # 返回结构化概念列表Phase 2:跨链接页面生成
llmwiki compile raw/my-article.md --phase 2Phase 2 在 Phase 1 的基础上,考虑已有 wiki 的全局上下文,生成带有精确跨链接的页面:
def phase2_generate_pages(concepts: List[Concept], existing_wiki: WikiIndex):
for concept in concepts:
# 找出与现有 wiki 的关联
related_existing = existing_wiki.find_related(concept)
prompt = f"""
为概念 "{concept.name}" 生成一个 wiki 页面。
要求:
1. 使用 [[wikilink]] 链接到以下相关概念:{related_existing}
2. 包含定义、原理、应用场景、注意事项
3. 在文末列出来源引用
4. 语言:{config.language}
相关概念的现有内容摘要:
{existing_wiki.get_summaries(related_existing)}
"""
page_content = llm_generate(prompt)
write_wiki_page(concept.name, page_content)4.3 MCP Server 配置(Claude Code 集成)
# 启动 MCP Server
llmwiki serve --port 3001
# Claude Code 配置(.claude/settings.json){
"mcpServers": {
"llmwiki": {
"command": "llmwiki",
"args": ["serve", "--stdio"],
"cwd": "/path/to/my-knowledge-base"
}
}
}MCP Server 暴露的工具:
wiki_ingest(source)- 摄取新来源wiki_query(question)- 查询知识wiki_lint()- 运行健康检查wiki_list_pages()- 列出所有页面wiki_get_page(name)- 获取指定页面
4.4 评估框架:度量 wiki 质量
llm-wiki-compiler 内置了一套评估框架,输出 0-100 健康分:
llmwiki evaluate --output report.json评估维度:
class WikiHealthReport:
coverage_score: float # 来源内容的覆盖率(0-1)
citation_score: float # 引用覆盖率(有引用的声明/总声明)
link_density: float # 平均每页 wikilink 数量
orphan_ratio: float # 孤儿页比例(越低越好)
staleness_ratio: float # 陈旧页比例(越低越好)
llm_judge_score: float # LLM-as-judge 评估的准确性(0-1)
overall_health: int # 综合健康分(0-100)LLM-as-judge 实现:
def llm_judge_accuracy(wiki_page: WikiPage, source_docs: List[str]) -> float:
"""
让 LLM 作为裁判,评估 wiki 页面的事实准确性
"""
prompt = f"""
你是一个事实核查专家。
Wiki 页面内容:
{wiki_page.content}
原始来源文档:
{'\n---\n'.join(source_docs)}
请评估 wiki 页面中的每个声明是否有原始来源支持。
返回:{{"supported": 0-1, "contradicted": 0-1, "unsupported": 0-1}}
"""
return llm_evaluate(prompt)CI 门控集成:
# .github/workflows/wiki-health.yml
name: Wiki Health Check
on: [push]
jobs:
health:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install -g llm-wiki-compiler
- run: llmwiki evaluate --min-score 75 --fail-on-low
# 健康分低于 75 则 CI 失败4.5 增量更新:内容哈希检测
# 增量编译逻辑
def incremental_compile(source_path: str):
# 计算当前文件哈希
current_hash = sha256(read_file(source_path))
# 与上次编译时的哈希对比
stored_hash = load_hash(source_path)
if current_hash == stored_hash:
print(f"跳过 {source_path}(内容未变化)")
return
# 内容有变化,重新编译
compile(source_path)
# 更新哈希存储
save_hash(source_path, current_hash)5. Zero-RAG 方案
5.1 传统 RAG 的运维负担
实际运维一个 RAG 系统的工程师都深有体会:
- 向量数据库维护:Chroma/Pinecone/Weaviate 需要持续运维
- Embedding 模型依赖:更换模型时需要重建全量索引(可能几个小时)
- 索引同步问题:文档更新后向量索引不一定及时同步
- 向量存储成本:百万级文档的向量存储费用可观
- 检索调优:chunk size、overlap、top-k 等参数需要大量实验
5.2 Zero-RAG 的核心思路
MeMex-Zero-RAG (GitHub: https://github.com/JPeetz/MeMex-Zero-RAG) 提出了一个极端方案:
完全无嵌入、无向量数据库,纯 Markdown + Git
其核心假设是:当 LLM 的上下文窗口足够大时(Claude claude-opus-4-5 200K token、Gemini 1M token),不需要检索,直接把整个知识库塞进上下文。
实现方式:
knowledge-base/
├── index.md # 所有页面的结构化摘要(~5000 token)
├── pages/ # 实际页面
│ ├── page-001.md # 每页约 500-1000 token
│ └── page-002.md
└── meta.json # 页面元数据(标题、标签、更新时间)
查询时(Two-pass 策略):
Pass 1: 将 index.md 发给 LLM
→ LLM 识别相关页面编号
Pass 2: 将相关页面完整内容发给 LLM
→ LLM 生成带引用的回答
100页 wiki(每页 800 token)= 80,000 token 的索引。在 200K 上下文窗口内,可直接 Full Pass(一次性塞入所有页面),完全消除检索步骤。
5.3 Zero-RAG vs RAG 适用场景对比
| 维度 | Zero-RAG | 传统 RAG | LLM-wiki + RAG |
|---|---|---|---|
| 知识库规模 | < 500 页 | 任意规模 | < 5000 页 |
| 基础设施 | 零(纯文件) | 向量DB + Embedding服务 | 轻量(可选向量) |
| 检索精度 | 完美(无检索损失) | 中等(依赖 chunk 质量) | 高(结构化 wiki) |
| 运维成本 | 极低 | 高 | 低 |
| 查询成本 | 高(大上下文 token) | 低 | 中 |
| 知识实时性 | 高(文件即来源) | 需重建索引 | 高(ingest 即更新) |
| 适用场景 | 个人/小团队知识库 | 企业级大型文档库 | 中等规模精品知识库 |
规模阈值参考:
- 上下文 200K token,每页 1000 token → Zero-RAG 适用上限:约 150-200 页
- 超过此规模,Two-pass 策略可延伸到 1000 页
- 超过 1000 页,建议引入向量检索
6. 知识图谱增强
6.1 为什么纯向量搜索不够
向量搜索擅长”语义相似性”查找,但无法回答关系型问题:
- “A 是否与 B 存在因果关系?“(向量相似不代表因果)
- “所有依赖 X 技术的系统有哪些?“(需要图遍历)
- “从 A 到 B 的知识路径是什么?“(需要路径搜索)
这正是知识图谱的优势领域。
6.2 GraphRAG vs LightRAG 对比
Microsoft GraphRAG (GitHub Stars: 23K+)
核心流程:
文本 → 实体抽取(NER)→ 关系抽取(RE)→ 知识图谱
→ 社区检测(Leiden 算法)→ 层次化摘要
查询模式:
- Local Query:从特定实体出发,遍历邻近关系
- Global Query:跨社区语义搜索,适合宏观问题
LightRAG (GitHub Stars: 15K+)
核心流程(更轻量):
文本 → 实体+关系抽取 → 双层索引(实体级 + 关系级)
→ 混合检索(图 + 向量)
优势:
- 比 GraphRAG 快 5-10 倍(无社区检测步骤)
- 支持增量插入(GraphRAG 需全量重建)
- API 更简单
| 维度 | GraphRAG | LightRAG |
|---|---|---|
| 构建速度 | 慢(O(n²) 社区检测) | 快 |
| 全局问题质量 | 高(层次化摘要) | 中 |
| 增量更新 | 困难 | 支持 |
| 部署复杂度 | 高 | 低 |
| 适用规模 | 大型文档库 | 中小规模 |
6.3 Microsoft GraphRAG 快速上手
pip install graphrag
mkdir graphrag-demo && cd graphrag-demo
python -m graphrag init --root .配置文件 settings.yaml:
llm:
api_key: ${GRAPHRAG_API_KEY}
type: openai_chat
model: gpt-4o
# 或使用 Anthropic
llm:
api_key: ${ANTHROPIC_API_KEY}
type: anthropic_chat
model: claude-opus-4-5构建知识图谱:
# 将文档放入 input/ 目录
cp my-docs/*.md input/
# 构建图谱(耗时,建议异步)
python -m graphrag index --root .
# Local 查询(实体相关问题)
python -m graphrag query --root . --method local --query "Transformer 的发明者是谁?"
# Global 查询(宏观问题)
python -m graphrag query --root . --method global --query "深度学习的主要研究方向有哪些?"6.4 将知识图谱与 Obsidian 结合
知识图谱的可视化输出可以直接导入 Obsidian:
import networkx as nx
from graphrag_output import load_entities, load_relationships
def export_to_obsidian(output_dir: str, vault_dir: str):
"""
将 GraphRAG 输出转换为 Obsidian wiki 页面
"""
entities = load_entities(f"{output_dir}/entities.csv")
relationships = load_relationships(f"{output_dir}/relationships.csv")
for entity in entities:
# 找出该实体的所有关系
related = relationships.filter(
lambda r: r.source == entity.id or r.target == entity.id
)
# 生成 Obsidian 页面
page_content = f"""# {entity.name}
**类型**: {entity.type}
**描述**: {entity.description}
## 关系
"""
for rel in related:
other = entities.get(rel.other_id(entity.id))
page_content += f"- **{rel.relationship}** [[{other.name}]]\n"
write_file(f"{vault_dir}/{entity.name}.md", page_content)7. WikiChat:幻觉抑制设计
GitHub: https://github.com/stanford-oval/WikiChat
来源: 斯坦福大学 OVAL 实验室
核心贡献: 系统化的 7 步幻觉抑制管道
7.1 7步管道详解
WikiChat 的设计目标是:让 LLM 基于知识库回答问题时,不产生幻觉。
用户问题:"GPT-4 在 MMLU 上的得分是多少?"
Step 1: 查询生成(Query Generation)
将用户问题转换为多个检索查询
→ ["GPT-4 MMLU score", "GPT-4 benchmark performance", "MMLU leaderboard"]
Step 2: 检索(Retrieval)
对每个查询执行向量检索
→ 从知识库召回相关文档 chunks
Step 3: 事实提取(Fact Extraction)
让 LLM 从检索到的 chunks 中提取具体事实
→ ["GPT-4 MMLU: 86.4% (5-shot)", 来源: openai-gpt4-report.md, 第3页]
Step 4: 事实核查(Fact Checking)
对提取的事实进行交叉验证
→ 检查是否有矛盾来源
→ 标记不确定性("仅见于单一来源")
Step 5: 问题精炼(Question Refinement)
基于已核查的事实,精炼原始问题
→ 识别问题中的错误前提(如果有)
→ 确认问题的可回答性
Step 6: 草稿生成(Draft Generation)
基于核查后的事实生成初稿
→ 每个声明都有事实依据
Step 7: 精炼(Refinement)
检查草稿的内部一致性
→ 移除无来源的推测性内容
→ 添加必要的不确定性声明
→ 输出最终回答(带引用)
7.2 将 Wikipedia 替换为私有知识库
WikiChat 的原始实现使用 Wikipedia。替换为私有知识库只需修改检索接口:
# WikiChat 原始接口
class WikipediaRetriever:
def retrieve(self, query: str) -> List[Document]:
return wikipedia_api.search(query)
# 替换为私有知识库
class PrivateWikiRetriever:
def __init__(self, wiki_dir: str):
self.wiki = LLMWikiIndex(wiki_dir)
self.bm25 = BM25Index(wiki_dir)
def retrieve(self, query: str) -> List[Document]:
# 混合检索
semantic_results = self.wiki.semantic_search(query, top_k=10)
bm25_results = self.bm25.search(query, top_k=10)
# 合并去重,按相关性排序
combined = merge_and_rerank(semantic_results, bm25_results)
return combined[:5]
# 在 WikiChat pipeline 中使用私有检索器
chat = WikiChat(retriever=PrivateWikiRetriever("/path/to/wiki"))7.3 关键技术:Claim Extraction + Fact Checking
这是 WikiChat 抑制幻觉的核心机制:
def extract_claims(text: str) -> List[Claim]:
"""
将文本分解为原子化的可验证声明
输入: "GPT-4 在 MMLU 上得分 86.4%,由 OpenAI 于 2023 年发布"
输出: [
Claim("GPT-4 MMLU 得分 86.4%", verifiable=True),
Claim("GPT-4 由 OpenAI 开发", verifiable=True),
Claim("GPT-4 于 2023 年发布", verifiable=True)
]
"""
prompt = """
将以下文本分解为独立的、可事实核查的声明。
每个声明应该:
1. 是一个具体的事实陈述
2. 可以被独立验证
3. 不包含模糊表述
文本:{text}
"""
return llm_extract_claims(prompt.format(text=text))
def fact_check_claim(claim: Claim, sources: List[Document]) -> FactCheckResult:
"""
用来源文档核查单个声明
"""
prompt = """
请核查以下声明是否有来源文档支持:
声明:{claim}
来源文档:
{sources}
返回:
- supported: 有明确支持 / contradicted: 有矛盾 / unverifiable: 无法核查
- evidence: 相关引用文字
- confidence: 0.0-1.0
"""
return llm_fact_check(prompt)8. 完整 wiki 生成 Pipeline 实现
以下是基于 Karpathy 模式的完整 Python 实现,可直接运行:
#!/usr/bin/env python3
"""
LLM Wiki Pipeline - Karpathy 模式完整实现
依赖: pip install anthropic httpx pyyaml rank_bm25
"""
import os
import re
import json
import hashlib
import fcntl
from pathlib import Path
from datetime import datetime
from dataclasses import dataclass, field
from typing import List, Dict, Optional
import httpx
import anthropic
from rank_bm25 import BM25Okapi
# ==================== 数据结构 ====================
@dataclass
class WikiPage:
name: str
content: str
sources: List[str] = field(default_factory=list)
tags: List[str] = field(default_factory=list)
last_updated: str = ""
@property
def wikilinks(self) -> List[str]:
"""提取页面中所有 [[wikilink]]"""
return re.findall(r'\[\[([^\]]+)\]\]', self.content)
@property
def path(self) -> Path:
safe_name = re.sub(r'[^\w\-_]', '-', self.name)
return Path(f"wiki/{safe_name}.md")
@dataclass
class IngestResult:
source: str
pages_created: List[str]
pages_updated: List[str]
timestamp: str
@dataclass
class LintReport:
orphaned_pages: List[str]
stale_pages: List[str]
broken_links: List[tuple]
missing_crossrefs: List[tuple]
health_score: int
# ==================== 核心 Pipeline ====================
class LLMWikiPipeline:
def __init__(self, base_dir: str, model: str = "claude-opus-4-5"):
self.base_dir = Path(base_dir)
self.raw_dir = self.base_dir / "raw"
self.wiki_dir = self.base_dir / "wiki"
self.log_path = self.base_dir / "log.md"
self.index_path = self.wiki_dir / "index.md"
self.model = model
self.client = anthropic.Anthropic()
# 初始化目录结构
self.raw_dir.mkdir(parents=True, exist_ok=True)
self.wiki_dir.mkdir(parents=True, exist_ok=True)
if not self.index_path.exists():
self.index_path.write_text("# Wiki 知识库索引\n\n")
if not self.log_path.exists():
self.log_path.write_text("# Ingest 日志\n\n")
# ==================== INGEST ====================
def ingest(self, source: str) -> IngestResult:
"""
主摄取入口
source: URL / 本地文件路径 / 原始文本
"""
print(f"[Ingest] 开始处理: {source}")
# Step 1: 获取内容
content, source_id = self._fetch_content(source)
# Step 2: 保存到 raw/
raw_path = self._save_to_raw(content, source_id)
print(f"[Ingest] 已保存到 {raw_path}")
# Step 3: 读取现有 wiki 索引
existing_index = self.index_path.read_text()
# Step 4: LLM 分析并生成 wiki 页面
actions = self._llm_analyze(content, existing_index, source_id)
# Step 5: 执行动作(创建/更新 wiki 页面)
result = self._execute_actions(actions, source_id)
# Step 6: 更新索引和日志
self._update_index(result.pages_created)
self._write_log(result)
print(f"[Ingest] 完成:创建 {len(result.pages_created)} 页,"
f"更新 {len(result.pages_updated)} 页")
return result
def _fetch_content(self, source: str) -> tuple:
"""获取来源内容"""
if source.startswith("http"):
# URL:下载并转为纯文本
response = httpx.get(source, follow_redirects=True, timeout=30)
content = response.text # 实际应用中需要 HTML→Markdown 转换
source_id = hashlib.md5(source.encode()).hexdigest()[:8]
return content, f"web-{source_id}"
elif Path(source).exists():
# 本地文件
content = Path(source).read_text(encoding='utf-8')
source_id = Path(source).stem
return content, source_id
else:
# 直接文本
source_id = f"text-{datetime.now().strftime('%Y%m%d%H%M%S')}"
return source, source_id
def _save_to_raw(self, content: str, source_id: str) -> Path:
"""保存原始内容(加时间戳,不可变)"""
timestamp = datetime.now().strftime("%Y-%m-%d")
filename = f"{timestamp}-{source_id}.md"
path = self.raw_dir / filename
path.write_text(content, encoding='utf-8')
return path
def _llm_analyze(self, content: str, existing_index: str, source_id: str) -> dict:
"""LLM 分析内容,返回 wiki 操作动作"""
prompt = f"""你是一个知识摄取专家,负责将文档内容转化为结构化的 wiki 知识页面。
现有 wiki 索引(请复用已有页面而非重复创建):
{existing_index[:3000]}
待摄取文档(来源ID: {source_id}):
{content[:8000]}
请分析文档,输出 JSON 格式的操作列表:
{{
"actions": [
{{
"type": "create", // 或 "update"
"page_name": "页面名称",
"content": "完整的 Markdown 内容(含 [[wikilink]])",
"tags": ["标签1", "标签2"]
}}
]
}}
要求:
1. 提取 8-15 个核心概念,每个概念一个页面
2. 优先 update 现有页面,而非重复 create
3. 每个页面必须有 [[wikilink]] 指向相关概念
4. 来源引用格式:[[raw/{source_id}]]
5. 仅返回 JSON,不要有其他文字
"""
response = self.client.messages.create(
model=self.model,
max_tokens=8192,
messages=[{"role": "user", "content": prompt}]
)
# 解析 JSON 响应
json_text = response.content[0].text
# 提取 JSON 块(处理 LLM 可能输出的前缀文字)
json_match = re.search(r'\{.*\}', json_text, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return {"actions": []}
def _execute_actions(self, actions: dict, source_id: str) -> IngestResult:
"""执行 LLM 生成的操作"""
created = []
updated = []
for action in actions.get("actions", []):
page_name = action["page_name"]
content = action["content"]
safe_name = re.sub(r'[^\w\-_一-鿿]', '-', page_name)
page_path = self.wiki_dir / f"{safe_name}.md"
# 使用 advisory lock 防止并发冲突
with self._page_lock(page_path):
if action["type"] == "create" or not page_path.exists():
page_path.write_text(content, encoding='utf-8')
created.append(page_name)
else:
# update:合并现有内容
existing = page_path.read_text(encoding='utf-8')
merged = self._merge_page_content(existing, content, page_name)
page_path.write_text(merged, encoding='utf-8')
updated.append(page_name)
return IngestResult(
source=source_id,
pages_created=created,
pages_updated=updated,
timestamp=datetime.now().isoformat()
)
def _merge_page_content(self, existing: str, new_content: str, page_name: str) -> str:
"""LLM 合并两个版本的页面内容"""
prompt = f"""合并同一 wiki 页面的两个版本。保留所有独特信息,消除重复。
页面名称:{page_name}
现有版本:
{existing}
新增内容:
{new_content}
请输出合并后的完整页面内容(Markdown 格式)。"""
response = self.client.messages.create(
model=self.model,
max_tokens=4096,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
# ==================== QUERY ====================
def query(self, question: str) -> str:
"""带引用的知识查询"""
# 三级查找
relevant_pages = self._find_relevant_pages(question)
if not relevant_pages:
return "知识库中未找到相关信息。建议 ingest 更多相关来源。"
# 组装上下文
context = "\n\n---\n\n".join([
f"**来自 [[{page.name}]]**:\n{page.content[:1500]}"
for page in relevant_pages
])
prompt = f"""基于以下 wiki 知识库内容回答问题。
每个声明必须注明来源 wiki 页面。
如有不确定,明确说明。
问题:{question}
知识库内容:
{context}
"""
response = self.client.messages.create(
model=self.model,
max_tokens=2048,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
def _find_relevant_pages(self, question: str) -> List[WikiPage]:
"""BM25 检索相关 wiki 页面"""
pages = self._load_all_pages()
if not pages:
return []
# BM25 检索
corpus = [page.content.split() for page in pages]
bm25 = BM25Okapi(corpus)
query_tokens = question.split()
scores = bm25.get_scores(query_tokens)
# 返回 top-3
top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:3]
return [pages[i] for i in top_indices if scores[i] > 0]
def _load_all_pages(self) -> List[WikiPage]:
"""加载所有 wiki 页面"""
pages = []
for path in self.wiki_dir.glob("*.md"):
if path.name == "index.md":
continue
content = path.read_text(encoding='utf-8')
name = path.stem.replace('-', ' ')
pages.append(WikiPage(name=name, content=content))
return pages
# ==================== LINT ====================
def lint(self) -> LintReport:
"""知识库健康检查"""
pages = self._load_all_pages()
page_names = {p.name for p in pages}
# 1. 孤儿页检测
all_links = set()
for page in pages:
all_links.update(page.wikilinks)
orphaned = [p.name for p in pages if p.name not in all_links]
# 2. 死链检测
broken_links = []
for page in pages:
for link in page.wikilinks:
if link not in page_names and not link.startswith("raw/"):
broken_links.append((page.name, link))
# 3. 陈旧页检测(超过30天未更新的有日期声明的页面)
stale = []
cutoff = datetime.now().timestamp() - 30 * 86400
for path in self.wiki_dir.glob("*.md"):
if path.stat().st_mtime < cutoff:
stale.append(path.stem)
# 4. 计算健康分
total = len(pages) or 1
orphan_ratio = len(orphaned) / total
broken_ratio = len(broken_links) / (total * 5) # 假设平均每页5个链接
health = int(100 * (1 - orphan_ratio * 0.3 - broken_ratio * 0.2))
health = max(0, min(100, health))
report = LintReport(
orphaned_pages=orphaned,
stale_pages=stale,
broken_links=broken_links,
missing_crossrefs=[],
health_score=health
)
self._print_lint_report(report)
return report
def _print_lint_report(self, report: LintReport):
print(f"\n=== Wiki 健康报告 ===")
print(f"健康分: {report.health_score}/100")
print(f"孤儿页 ({len(report.orphaned_pages)}): {report.orphaned_pages[:5]}")
print(f"陈旧页 ({len(report.stale_pages)}): {report.stale_pages[:5]}")
print(f"死链 ({len(report.broken_links)}): {report.broken_links[:5]}")
# ==================== 辅助方法 ====================
def _page_lock(self, page_path: Path):
"""Advisory lock,防止并发写入冲突"""
from contextlib import contextmanager
@contextmanager
def lock():
lock_path = str(page_path) + ".lock"
with open(lock_path, 'w') as lf:
try:
fcntl.flock(lf, fcntl.LOCK_EX)
yield
finally:
fcntl.flock(lf, fcntl.LOCK_UN)
try:
os.unlink(lock_path)
except FileNotFoundError:
pass
return lock()
def _update_index(self, new_pages: List[str]):
"""更新全局索引"""
if not new_pages:
return
existing = self.index_path.read_text(encoding='utf-8')
additions = "\n".join([f"- [[{name}]]" for name in new_pages])
self.index_path.write_text(
existing + f"\n## {datetime.now().strftime('%Y-%m-%d')} 新增\n{additions}\n",
encoding='utf-8'
)
def _write_log(self, result: IngestResult):
"""写入摄取日志"""
log_entry = f"""
## {result.timestamp}
**来源**: {result.source}
**创建页面**: {', '.join(result.pages_created) or '无'}
**更新页面**: {', '.join(result.pages_updated) or '无'}
---
"""
with open(self.log_path, 'a', encoding='utf-8') as f:
f.write(log_entry)
# ==================== 使用示例 ====================
if __name__ == "__main__":
# 初始化 Pipeline
pipeline = LLMWikiPipeline(
base_dir="/home/user/my-wiki",
model="claude-opus-4-5"
)
# 摄取一篇文章
result = pipeline.ingest("https://example.com/transformer-explained")
print(f"摄取完成:{result.pages_created}")
# 查询
answer = pipeline.query("什么是 Transformer 的核心创新?")
print(answer)
# 健康检查
report = pipeline.lint()
print(f"健康分:{report.health_score}")9. 与 Hermes + Obsidian 的结合
9.1 三工具角色分工
在完整的 AI 知识库生态中,三个工具各司其职:
数据流向:
外部信息源(URL/文档/对话)
│
▼
Hermes(知识摄取触发器)
│ 触发 wiki-ingest skill
▼
LLM-wiki Pipeline(知识编译器)
│ 生成/更新 wiki/ 目录下的 .md 文件
▼
Obsidian Vault(存储 + 可视化)
│ vault 路径 = wiki/ 目录
▼
Claude Code(查询接口)
│ 通过 MCP 读取 wiki 页面
9.2 完整联动配置
目录结构(三工具共享同一 vault):
/home/user/ObsidianVault/
├── .obsidian/ # Obsidian 配置
├── raw/ # LLM-wiki 来源(Obsidian 不显示)
├── wiki/ # LLM-wiki 编译产物 = Obsidian vault 内容
│ ├── index.md
│ ├── hot.md
│ └── concepts/
├── log.md
└── .hermes/ # Hermes 配置(隐藏目录)
└── config.yaml
Hermes 配置(~/.hermes/config.yaml):
triggers:
- event: new_article_saved
action: wiki_ingest
skill: claude-obsidian/wiki-ingest
vault_path: /home/user/ObsidianVault
- event: conversation_end
action: wiki_ingest_if_new_knowledge
condition: "has_new_factual_claims"
auto_lint:
enabled: true
schedule: "0 2 * * *" # 每天凌晨2点
min_health_score: 75
alert_on_failure: trueClaude Code MCP 配置(.claude/settings.json):
{
"mcpServers": {
"wiki": {
"command": "npx",
"args": ["-y", "@bitbonsai/mcpvault"],
"env": {
"VAULT_PATH": "/home/user/ObsidianVault/wiki"
}
}
}
}9.3 触发流程示例
用户在 Obsidian 中粘贴了一篇论文 PDF
│
▼
Hermes 检测到新文件(文件系统 watch)
│
▼
Hermes 调用 Claude Code Skill: wiki-ingest
│ claude code /wiki-ingest raw/paper.pdf
▼
claude-obsidian 执行 wiki-ingest:
1. 读取 PDF 内容
2. LLM 提取 12 个核心概念
3. 创建/更新 12 个 wiki 页面
4. 更新 index.md
5. 写入 log.md
│
▼
Obsidian 自动同步(文件系统变化)
│ 用户可立即在 Obsidian 中看到新增页面
▼
用户在 Claude Code 中查询:
"这篇论文的核心贡献是什么?"
│
▼
Claude Code 通过 MCP 读取 wiki 页面
│ 返回带 [[引用]] 的精准回答
10. 选型建议
10.1 决策矩阵
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| Claude Code 用户 | claude-obsidian | 15个现成 Skills,深度集成,开箱即用 |
| 需要 npm 工具链 | llm-wiki-compiler | MCP Server + CI 评估框架,工程化程度高 |
| 隐私优先/极简 | MeMex-Zero-RAG | 零依赖,纯文件,无向量库 |
| 需要知识图谱 | GraphRAG + Obsidian | 发现隐性关系,适合大型文档库 |
| 高精度引用要求 | WikiChat 管道 | 7步幻觉抑制,适合学术/法律/医疗场景 |
| 个人快速上手 | karpathy-llm-wiki | 一键安装,跨工具兼容 |
| 多 Agent 并发写入 | claude-obsidian | Advisory locking 原生支持 |
10.2 规模阈值参考
知识库规模:
< 200 页 → Zero-RAG(无任何检索开销)
200-1000 页 → LLM-wiki + BM25
1000-5000 页 → LLM-wiki + 向量检索(claude-obsidian / llm-wiki-compiler)
> 5000 页 → GraphRAG + LLM-wiki(混合图谱+语义检索)
> 50000 页 → 专业向量数据库(Pinecone / Weaviate / Milvus)
10.3 入门路径建议
最快路径(30分钟上手):
# 使用 karpathy-llm-wiki(Claude Code Skill)
npx add-skill Astro-Han/karpathy-llm-wiki
# 然后在 Claude Code 中
/wiki-ingest https://example.com/your-article
/wiki-query "你的问题"
/wiki-lint进阶路径(生产级):
# 1. 安装 claude-obsidian(需要 Obsidian)
npx add-skill AgriciDaniel/claude-obsidian
# 2. 或安装 llm-wiki-compiler(npm 工具链)
npm install -g llm-wiki-compiler
llmwiki init && llmwiki serve
# 3. 配置 MCP Server(见第4节)
# 4. 设置定期 lint
# crontab: 0 2 * * * cd /path/to/wiki && llmwiki lint参考资料
- Karpathy LLM Wiki Gist(2026-04)
- claude-obsidian — 6,397⭐
- llm-wiki-compiler — 1,486⭐
- karpathy-llm-wiki — 1,027⭐
- MeMex-Zero-RAG — Zero-RAG 实现
- swarmvault — 531⭐,本地优先 LLM Wiki
- WikiChat — 斯坦福 OVAL 实验室,幻觉抑制
- Microsoft GraphRAG — 23K⭐
- LightRAG — 15K⭐
- Contextual Retrieval(Anthropic, 2024)— 上下文前缀检索研究
- 本系列其他文章:
本文档基于 2026年6月最新调研数据整理。LLM-wiki 生态发展迅速,建议定期关注上述项目的更新动态。