❝Andrej Karpathy 提出了 LLM Wiki 架构——一种完全不同于 RAG 的知识管理范式。本文将深入解读其核心思想,并分享我从零实现一个 LLM Wiki 系统的完整实践。 ❞
过去两年,RAG(Retrieval-Augmented Generation)几乎成了 LLM 应用的"标配"。无论是企业知识库、智能客服还是个人笔记系统,大家的第一反应都是:把文档切块 → 向量化 → 存入向量数据库 → 查询时检索 → 拼进 Prompt。
这套流程确实有效,但用久了你会发现一些痛点:
这些问题并非 RAG 的 Bug,而是其设计哲学决定的——「RAG 本质上是"检索时理解"」。
而 Karpathy 提出了一个反直觉的思路:「为什么不在入库时就让 LLM 理解好?」
Karpathy 的 LLM Wiki 架构,核心就一句话:
❝「"不要让 LLM 在查询时去理解原始文档,而是提前让 LLM 把文档'编译'成结构化的知识。"」 ❞
这跟编程语言的编译器是同一个思路:
源代码 (.py) → 编译 → 字节码 (已优化) → 执行 → 结果
原始文档 (PDF) → Ingest → Wiki 页面 (结构化) → Query → 答案
「"编译"意味着什么?」
关键洞察在于:「如果你的知识库足够精炼,你根本不需要复杂的向量检索」。100 个 Wiki 页面,每个平均 500 tokens,总共才 50,000 tokens——现在的 LLM 动辄 128K 甚至 200K 的上下文窗口,完全可以把整个 Wiki 塞进去。
LLM Wiki 采用清晰的三层架构:
┌─────────────────────────────────────────────┐
│ Layer 3: Wiki(已编译的知识图谱) │
│ entities/ concepts/ summaries/ synthesis/ │
│ queries/ index.md │
├─────────────────────────────────────────────┤
│ Layer 2: Raw Sources(原始资料) │
│ IMA API / 本地文件 / 外部数据源 │
├─────────────────────────────────────────────┤
│ Layer 1: Schema(行为契约) │
│ AGENTS.md + SCHEMA.md │
└─────────────────────────────────────────────┘
在 Karpathy 的设计中,「Schema 是整个系统最核心的概念」。它不是数据库 Schema,而是一份「给 LLM 看的"行为契约"」。
Schema 分为两个文件:
AGENTS.md:全局行为规范,定义工作流(Ingest/Query/Lint)和通用约定SCHEMA.md:实例级约束,定义页面模板、标签分类、质量阈值「Schema 解决的核心问题:」
问题 | 没有 Schema | 有 Schema |
|---|---|---|
格式 | 混乱不统一 | 严格模板、标准结构 |
质量 | 长短不一、详略不同 | 质量稳定、可预测 |
维护 | 知识库逐渐混乱 | 知识库持续整洁 |
更强大的是——「修改 Schema 就能修改 LLM 行为,不需要改一行代码」。想让摘要更详细?改 SCHEMA.md 里的字数限制就行。想新增一种页面类型?在模板里加一个定义即可。
Wiki 中有五种页面类型,各司其职:
类型 | 目录 | 内容 | 示例 |
|---|---|---|---|
「Entities」 | entities/ | 人物、组织、项目 | geoffrey-hinton.md |
「Concepts」 | concepts/ | 技术概念、理论 | transformer.md |
「Summaries」 | summaries/ | 源文档摘要 | attention-is-all-you-need.md |
「Synthesis」 | synthesis/ | 跨文档综合分析 | evolution-of-nlp.md |
「Queries」 | queries/ | 归档的高质量问答 | query-20240115-scaling-laws.md |
每种页面都有严格的模板:YAML frontmatter 元数据 + 标准化的 Markdown 结构 + [[双向链接]] 交叉引用。
Ingest 是 LLM Wiki 最关键的操作,完整流程如下:
原始文档 → 内容提取 → LLM 生成摘要 → 创建 Summary 页面
→ LLM 提取实体 → 创建/更新 Entity 页面
→ LLM 提取概念 → 创建/更新 Concept 页面
→ 更新双向链接 → 更新 index.md → 记录 log.md
以摄入一篇论文为例,代码的核心逻辑是这样的:
def _ingest_content(self, source_id, source_title, content, source_type, metadata):
# 1. LLM 生成摘要
summary = self._summarize(content)
# 2. 创建摘要页
summary_page = self.wiki.create_page(
title=source_title, category="summaries", content=summary,
frontmatter={"source_id": source_id, "tags": ["summary"]},
)
# 3. LLM 提取并创建/更新实体页
for entity in self._extract_entities(content):
self._update_entity_page(entity, summary_slug)
# 4. LLM 提取并创建/更新概念页
for concept in self._extract_concepts(content):
self._update_concept_page(concept, summary_slug)
# 5. 更新索引和日志
self.wiki.write_index_file()
self.wiki.append_log("ingest", details, source=source_id)
这里的设计亮点是 「LLM 回调机制」:所有 LLM 相关功能(摘要、提取、问答)都通过回调函数注入,不提供回调时退化为简单实现(如截取前 500 字符做摘要)。这让系统可以灵活接入不同的 LLM 服务,也方便测试。
Query 的设计哲学是——「先查已编译的知识(Wiki),再查原始资料(Raw Sources),最后综合生成答案」:
用户提问 → 搜索 Wiki(BM25 算法)→ 搜索 IMA 原始知识库
→ 组装上下文 → LLM 综合生成答案
→ 判断是否值得归档 → (是)创建 Query 页面
搜索部分我实现了一个「轻量级 BM25 算法」,而非简单的子字符串匹配:
def search(self, query, category=None, top_k=20):
query_tokens = self._tokenize(query)
# BM25 参数
k1, b, title_boost = 1.5, 0.75, 3.0
# 计算每个查询词的 IDF
for token in set(query_tokens):
doc_freq = sum(1for doc in doc_contents if token in doc)
idf_scores[token] = math.log(
(n_docs - doc_freq + 0.5) / (doc_freq + 0.5) + 1.0
)
# 对每个文档计算 BM25 分数 + 标题加权
for page in candidate_pages:
score = sum(idf * tf_norm for each query_token)
if token in title_tokens:
score += idf * title_boost # 标题匹配额外加权
BM25 相比简单子字符串匹配的优势:支持多关键词分词、TF-IDF 加权、标题匹配加权。同时保持轻量——不需要外部依赖,也不需要向量数据库。
Query 还有一个精妙的设计:「有价值的问答会被自动归档成新的 Wiki 页面」。这意味着 Wiki 会通过使用不断"自我增长"——你问得越多,知识库越丰富。
Lint 操作借鉴了代码静态分析的理念,定期检查 Wiki 的"健康度":
最终输出一个健康分数和详细报告:
🔍 Wiki Health Report
Health Score: 85.0%
| Issue Type | Count |
|------------------|-------|
| Orphan Pages | 2 |
| Broken Links | 1 |
| Outdated Pages | 3 |
| Contradictions | 0 |
这是整个系统我最欣赏的设计。所有 LLM 调用都会自动注入 Schema 作为 System Prompt:
class WikiLLMFunctions:
def _build_system_prompt(self, task_instruction=""):
parts = [
"You are an intelligent wiki assistant.",
"You MUST follow the rules and conventions defined below.",
]
if self._agents_content:
parts.append("--- AGENTS.md (Global Behavior Rules) ---")
parts.append(self._agents_content)
if self._schema_content:
parts.append("--- SCHEMA.md (Instance-Specific Constraints) ---")
parts.append(self._schema_content)
if task_instruction:
parts.append(f"--- Current Task ---\n{task_instruction}")
return"\n\n".join(parts)
这意味着:
「想改变 LLM 的行为?修改 Markdown 文件即可,不需要动一行代码。」 这在传统 RAG 系统中是不可想象的——通常你需要改 Prompt、改代码逻辑、重新部署。
维度 | 传统 RAG | LLM Wiki |
|---|---|---|
「核心思想」 | 原始数据 + 智能检索 | 预编译 + 全量上下文 |
「数据处理」 | 分块存储,保留原文 | LLM 预处理,提炼精华 |
「检索方式」 | 向量相似度搜索 | BM25 关键词匹配(或全量加载) |
「信息密度」 | 低(包含冗余) | 高(经过精炼) |
「知识关联」 | 隐式(Embedding 空间) | 显式([[双向链接]]) |
「查询时 LLM 负担」 | 重(需理解原文) | 轻(知识已结构化) |
「更新成本」 | 低(增量更新向量) | 高(需要重新编译) |
「适合数据量」 | 大规模(10GB+) | 中小规模(< 500 页) |
「行为修改」 | 改代码、改 Prompt、部署 | 改 Markdown 文件 |
「它们不是替代关系,而是互补」。Karpathy 自己也说:
❝"RAG 是用技术复杂度换取内容量。LLM Wiki 是用前期精炼换取查询时的简单与准确。" ❞
Karpathy 的 LLM Wiki 并不是要"革 RAG 的命",而是提出了一种不同的知识管理哲学:
[[双向链接]] > Embedding 空间相似度对于个人知识管理来说,这种方式有一种独特的魅力:你的知识库不再是一个黑盒的向量数据库,而是一组精心组织的、人类可读的 Markdown 文件。你可以用 Obsidian 浏览它的知识图谱,可以直接阅读任何一个页面,可以手动编辑修正 LLM 的输出,也可以通过修改 Schema 来调整系统的行为。
「知识管理的终极形态,或许就是让 AI 帮你维护一个"活"的 Wiki——持久的、复利增长的知识产物。」
❝觉得有用的话,「点赞👍 + 分享👀 + 收藏⭐」 一键三连,下次找起来方便,也能让更多人看到。 ❞