How to Use Personal Knowledge Graph

1. 这是什么

把 vault/ 里的 Markdown 编译成一张有向图,然后回答两个问题:

  • 我该先学什么?
  • 这个概念和我读过的哪篇东西有关?

它不是 RAG:没有向量、没有 embedding、没有”和我的笔记聊天”。 它不是笔记软件:写作仍然在 Typora / Obsidian,它只读 .md。 它不是自动抽取:边是手写的,不是模型猜的。

一句话区别:RAG 的智力发生在提问时,这张图的智力发生在记录时。

当前规模(2026-10-22):18 个节点、18 条边、26 条回归测试、0 errors。

2. 目录与第一次运行

2.1 目录

路径 作用
vault/ 人工维护的笔记,唯一真相源
vault/10-concepts/ 概念
vault/20-implementations/ 源码文件 / 函数
vault/30-resources/ 论文、文档、博客文章
vault/40-claims/ 技术判断
vault/50-projects/ 项目
ontology.yaml 类型白名单
tools/ update / build_graph / check / graphify / kgify / viewer
tests/selftest.py 26 条回归测试
graph/ 派生数据,可随时删掉重建,不进 git
inbox/ 博客原文快照,不参与构建
proposals/ 机器提出的候选边

2.2 依赖

Python 3.10+,以及两个包:

1
pip install pyyaml networkx

没有服务、没有数据库、没有网络、没有 API key。

2.3 一条命令,三步

平时只需要:

1
2
cd "E:\Personal Knowledge Graph"
python tools/update.py

它把下面三步串起来,但顺序有一处不同:先体检、后发布。

1
2
3
python tools/build_graph.py      # 编译 + 校验,有 E 就不写 graph/
python tools/check.py # 语义检查:孤立点、成环、证据锚点
python tools/graphify.py export # 生成 graph/graph.html

手动跑这三条时,build_graph.py 会先写 graph/,再跑 check.py;即使 check 报错,文件已经更新了。update.py 把发布放到体检之后,所以失败时 graph/ 不动。

update.py 的编译或体检失败就停:编译失败不进入体检;体检发现 error 在发布前停;两种情况 graph/ 都保持上一次的内容。

第一次跑应该看到类似:

1
2
3
4
5
6
[1/3] 编译  18 节点 / 18 边
[2/3] 体检 0 errors / 0 warnings / 10 info
[3/3] 出图 graph/graph.html (25 KB)

✓ 完成。打开 .../graph/graph.html
10 条结构缺口 —— python tools/graphify.py gaps

info 不是错误,是结构缺口,也就是下一篇的选题。

参数 作用
--open 跑完用默认浏览器打开图
-q / --quiet 只打结论,不刷明细
-v / --verbose 把 I 类提示也打出来

3. 数据模型:5 类节点,9 类边

3.1 节点

类型不是给”东西”分类,是给”你打算对它问的问题”分类。

你想问 类型 放哪 额外必填
它是什么?学它之前要会什么? Concept 10-concepts/ —
它怎么写的?在哪个文件? Implementation 20-implementations/ path
它说了什么?出处在哪? Resource 30-resources/ —
我信不信?谁支持/反驳? Claim 40-claims/ stance
我做到哪了?用了什么? Project 50-projects/ —

所有类型都必填 id 和 name。

3.2 边

关系 起点 → 终点 读作
prerequisite_of Concept → Concept 学 Y 之前先掌握 X
part_of Concept/Implementation → Concept X 是 Y 的组成部分
implemented_by Concept → Implementation X 由 Y 实现
explained_by Concept/Claim → Resource X 的解释在 Y
supports Resource/Claim → Claim X 支持 Y
contradicts Resource/Claim → Claim X 与 Y 冲突
about Claim → Concept/Implementation/Project 主张讨论的对象
applied_in Concept → Project X 用在 Y 里
related_to 任意 → 任意 兜底,别滥用

一条边还有三个属性:

字段 可选值 说明
status verified / proposed / rejected 默认 verified;查询只认 verified
evidence 文件名.md#标题 标题必须完整匹配
source_type human / paper / source_code / doc / llm 谁为这条边背书

方向约定:边写在”本笔记”上,读作「本节点 —rel→ 目标」。反向边不要手写,查询会处理。

4. 加一篇笔记

4.1 先判断该不该进

形态 适合吗
原子概念笔记(200–800 字) ✅ 最好的输入
论文 / 文档摘要 ✅ 整篇导入
几万字的大合集 ⚠️ 当 Resource,别当 Concept
日记、Todo、安装教程、空壳 ❌ 不进

判据不是长度,是能不能给它一个”一句话定义”:

  • 一个都定义不了 → 不进
  • 定义得了一件事 → 一个节点
  • 定义得了一大堆 → 当 Resource,再逐个抽 Concept

4.2 入口 A:博客文章 → Resource

一篇博客 ≠ 一个 Concept。 文章本身是资料,文章里你读懂的那部分才是知识。

以 Paper Summary – OSWorld 为例:

  1. 算身份:
1
python tools/kgify.py "E:\myBlog\source\_posts\Paper Summary - OSWorld.md"

它只打印,不改文件。把建议的 kg: 块贴到博客 frontmatter 末尾:

1
2
3
4
5
6
kg:
type: Resource
id: res.paper-summary-osworld
name: "OSWorld"
aliases: ["Paper Summary – OSWorld"]
relations: []
  1. 复制进 vault,文件名用 slug:
1
cp "E:\myBlog\source\_posts\Paper Summary - OSWorld.md" "vault/30-resources/paper-summary-osworld.md"
  1. 边写在 Concept 上,不写在 Resource 上:
1
2
3
4
5
- rel: explained_by
to: "[[paper-summary-osworld]]"
evidence: "paper-summary-osworld.md#1. Introduction"
status: verified
source_type: paper
  1. 构建 + 体检 + 出图:
1
python tools/update.py

evidence 的 # 后面必须是文件里真实存在的标题。改标题就会报 W003——这是特性:它保证每条边都能点回原文。

4.3 入口 B:自己写一个 Concept

1
cp vault/_templates/concept.md vault/10-concepts/agent-skill.md

填 type/id/name/aliases/status,然后按模板写:

1
2
3
4
5
6
## 一句话定义
## 为什么存在 / 解决什么问题
## 前置知识
## 实现
## 我的理解(用自己的话,不许抄)
## 未解决的问题

三个门槛,缺一不可:

  1. 能被一句话定义;
  2. 能被反驳(「它和 X 有关」不是定义);
  3. 至少接得上 3 个已有节点。

4.4 入口 C:一个判断 → Claim

  • 必填 stance:tentative / believed / doubted / retracted;
  • about 指向概念 / 实现 / 项目;
  • 用 supports / contradicts 从 Resource 或另一个 Claim 连过来;
  • 证据写真实标题。

5. 边怎么写才不是标签墙

你想说 写这条
B 之前要先会 A A --prerequisite_of--> B
它属于更大的概念 X --part_of--> Y
出处是某篇资料 X --explained_by--> 资料
由某段代码实现 X --implemented_by--> 实现
用在某个项目里 X --applied_in--> 项目
想不出精确关系 related_to(兜底)

每写一条,问自己:这条边会被某个查询用到吗? 不会 → 写在正文的普通链接里就够了,不要进 relations。

related 必须包含 relations 的所有目标。从 2026-10-21 起,W002 比较的是解析后的节点 id,所以:

1
2
3
4
related: ["[[paper-summary-osworld]]"]      # Obsidian 认文件名
relations:
- rel: explained_by
to: "[[res.paper-summary-osworld]]" # 机器读 id

两种写法指向同一个节点,不会误报。

5.1 dash 归一化(2026-10-21)

–(U+2013)和 -(U+002D)看起来一样,字节不同。现在 norm() 会把整个 dash 家族折叠成 ASCII -:

1
Paper Summary – X  ==  Paper Summary - X

所以手写 [[Paper Summary – Agent S]] 也能解析。代价:两个只差 dash 的节点会报 E010——它们确实是同一个名字。

6. 查询:图能回答什么

命令 回答的问题
python tools/graphify.py stats 有多少节点 / 边,按类型分布
python tools/graphify.py explain <id> 这个节点的全部出边和入边
python tools/graphify.py prereq <id> 学它之前要会什么(按层级)
python tools/graphify.py order 全局学习顺序 + 没声明依赖的概念
python tools/graphify.py gaps 结构缺口 = 待办
python tools/graphify.py cycles 前置关系成环
python tools/graphify.py path <a> <b> 两个节点之间的最短关联路径(无向)
python tools/graphify.py export 生成可交互的 graph/graph.html
python tools/graphify.py ontology 打印词汇表

改完笔记先跑 python tools/update.py;上面的命令都是查询,不改图。

几个真实输出:

1
2
3
4
5
6
7
8
9
$ python tools/graphify.py prereq concept.backpropagation

concept.backpropagation (反向传播)

直接前置:concept.gradient-descent

全部前置(按依赖层级,先学下面的,不含自身):
L1: concept.linear-regression
L2: concept.gradient-descent
1
2
3
4
5
6
$ python tools/graphify.py order

依赖分层(同层之间无先后约束):
L1: concept.linear-regression, concept.virtual-memory
L2: concept.gradient-descent, concept.page-table
L3: concept.backpropagation

gaps 里的每一条,都可以直接变成下一篇笔记的选题。

所有 graphify 子命令加 --all,可把 proposed 边也纳入查询。

7. 读输出:E 必修,W 判断,I 选题

7.1 E(阻断)

码 含义 修法
E001 断链 [[不存在]] 查拼写,或给目标加 aliases
E002 类型 / 关系未定义 查 ontology.yaml
E003 关系起终点类型越界 查关系表的 from/to
E004 同一条边写两次 删一条
E005 少必填字段 补 name / path / stance
E009 prerequisite_of 成环 环意味着永远学不完
E010 两个节点抢同一个解析键 改名或加别名
E013 status / stance 不在枚举里 查词汇表
E014 source_type: llm 没标 proposed 人确认后改成 human

7.2 W(能发布,但有代价)

码 含义
W001 孤立节点,一条边都没有
W002 related 缺 relations 的目标
W003 evidence 缺失 / 锚点标题不存在
W006 proposed 边写进了 vault
W007 rejected 边留在 vault 里

7.3 I(不是错误)

码 含义
I001 概念没有前置知识
I002 概念没连到任何代码实现
I003 主张没有任何证据支持

构建失败时 graph/ 一个字节都不会动——上一次可用的图保持原样。所以看到 E,不要拿旧的 graph/ 当新结果用。

8. 和博客结合

8.1 现在怎么接

博客文章只多一个 kg: 块,Hexo 完全不受影响:

1
2
3
4
5
6
kg:
type: Resource
id: res.paper-summary-osworld
name: "OSWorld"
aliases: ["Paper Summary – OSWorld"]
relations: []

build_graph.py 会把 kg 里的键合并到顶层同名键上;没有 kg 块的老笔记行为不变。

目前 vault 里已有 3 篇论文摘要(Agent S / VISTA / OSWorld-Human),它们被 concept.computer-use-agent 用 explained_by 连起来。下一批不按篇数,按簇:同一件事至少两篇,才长得出概念。

簇 论文 新节点
Skill 蒸馏 Trace2Skill、CODESKILL、PolySkill、NSI concept.agent-skill
计算机操作 Agent S、VISTA、OSWorld、OSWorld-Human、WebShop、ALFWorld concept.benchmark
代码修复 SWE-RL、SWE-smith、SWE-agent、SWE-bench、mini-swe-agent concept.swe-agent

8.2 kg 块:完整说明

kg 是 frontmatter 里的一个普通键。Hexo 不认识它,图谱认识它。

它存在的唯一原因:同一个 .md 既要被 Hexo 发布,又要被图谱读取。如果直接把 type/id/name 写在顶层,它们会和 Hexo/主题的字段名打架;塞进 kg 子键后,Hexo 只多一个 page.kg,type/id 不会和主题抢名字。

8.2.1 读取顺序

build_graph.py 的 read_note 只做三件事:

  1. 解析整个 frontmatter;
  2. 如果 kg 是 mapping,执行 meta.update(kg)——kg 里的键覆盖顶层同名键;
  3. 没有 kg 块时,行为完全不变。

所以三种写法都合法:

文件 写法 原因
纯 vault 笔记 顶层 type/id/name 最简单,Obsidian 也能看
Hexo 文章 顶层 Hexo 字段 + kg 不和 Hexo/主题抢名字
两者兼任 顶层 Hexo + kg kg 覆盖顶层同名键

8.2.2 字段表

字段 必填 适用 说明
type ✅ 全部 Concept / Implementation / Resource / Claim / Project
id ✅ 全部 <前缀>.<slug>;前缀 concept impl res claim proj;冻结,别每次从 permalink 重算
name ✅ 全部 显示名;中文放这里;含 : 时加引号
aliases — 全部 链接解析用的别名;标题、缩写、permalink 都可以放;必须是列表
related — 全部 relations 的扁平投影;给 Obsidian 看;必须覆盖所有目标;必须是列表
relations — 全部 结构化边;图谱只读这里;必须是列表
path ✅ Implementation 源码路径;同时也是解析键([[eventpoll.c]] 能命中)
stance ✅ Claim tentative / believed / doubted / retracted
status — Concept learning / understood / applied
tags — Hexo 图谱完全不读

字段归属:

谁读 字段
图谱 type id name aliases related relations path stance status
Hexo title date updated permalink categories tags password comments copyright top description keywords

tags 是 Hexo 的,不是图谱的。把它放进 kg 也不会被读。

Resource / Concept / Claim 下面用 kg: 展示;Implementation / Project 通常不写成博客文章,所以直接用顶层字段。纯 vault 笔记把同样的键提到顶层即可(见 §8.2.1)。

8.2.3 五种节点

Resource:一般是博客里的论文/文档摘要。整篇导入,不拆。

1
2
3
4
5
6
7
kg:
type: Resource
id: res.paper-summary-agent-s
name: "Agent S"
aliases: ["Paper Summary – Agent S", "paper-summary-agent-s"]
related: []
relations: []

Concept:一个你能一句话定义的东西。边通常从它出发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
kg:
type: Concept
id: concept.computer-use-agent
name: 计算机操作类 Agent
aliases: [计算机操作类 Agent, computer-use agent, GUI agent]
status: learning
related: ["[[agent]]", "[[paper-summary-vista]]", "[[paper-summary-osworld-human]]", "[[paper-summary-agent-s]]"]
relations:
- rel: part_of
to: "[[agent]]"
evidence: "agent.md#一句话定义"
status: verified
source_type: human
- rel: explained_by
to: "[[paper-summary-vista]]"
evidence: "paper-summary-vista.md#1. Introduction"
status: verified
source_type: paper
- rel: explained_by
to: "[[paper-summary-osworld-human]]"
evidence: "paper-summary-osworld-human.md#1. Introduction"
status: verified
source_type: paper
- rel: explained_by
to: "[[paper-summary-agent-s]]"
evidence: "paper-summary-agent-s.md#1.1 Agent in OSWorld"
status: verified
source_type: paper

Claim:一个技术判断。必填 stance。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kg:
type: Claim
id: claim.epoll-o1-misleading
name: "「epoll 是 O(1)」这个说法应该被限定"
stance: believed
related: ["[[epoll-is-o1]]", "[[epoll]]"]
relations:
- rel: contradicts
to: "[[epoll-is-o1]]"
evidence: "epoll-is-o1.md#主张原文"
status: verified
source_type: human
- rel: about
to: "[[epoll]]"
evidence: "epoll.md#常见的错误理解"
status: verified
source_type: human

Implementation:源码文件或函数。必填 path。通常不写成博客文章,所以直接用顶层字段:

1
2
3
4
5
6
7
type: Implementation
id: impl.fs.eventpoll.c
name: fs/eventpoll.c
path: fs/eventpoll.c
aliases: [fs/eventpoll.c, eventpoll.c]
related: []
relations: []

Project:项目本身。通常是 applied_in 的终点,边写在 Concept 上,所以 relations 可以为空:

1
2
3
4
5
6
type: Project
id: proj.agent-runtime
name: Agent 执行环境
aliases: [agent-runtime]
related: []
relations: []

如果 Implementation / Project 也要发成博客文章,把上面这些键原样套进 kg: 即可。

8.2.4 一条边的完整写法

1
2
3
4
5
6
relations:
- rel: explained_by
to: "[[res.paper-summary-swe-rl]]"
evidence: "paper-summary-swe-rl.md#2. Implementation"
status: verified
source_type: paper
字段 必填 说明
rel ✅ 9 类之一,见 §3.2
to ✅ [[...]];写 id、slug、文件名、别名都可以;大小写 / 空白 / dash 会折叠
evidence — 文件名.md#标题;标题必须真实存在,否则 W003
status — verified(默认)/ proposed / rejected;查询只认 verified
source_type — human / paper / source_code / doc / llm;llm 必须配 proposed(E014)

四条规则:

  1. 一条边只写一次。 同一对 (src, rel, dst) 重复会 E004。
  2. 不写反向边。 A --prerequisite_of--> B 写在 A 里;反过来查由 graphify 做。
  3. related 要同步。 它是 relations 的扁平投影;缺了会 W002。
  4. proposed 不进 vault。 机器候选放 proposals/;写进 vault 会 W006。

8.2.5 完整例子:一篇博客文章 → 一个 Resource → 一条边

博客原文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
title: Paper Summary – SWE-RL
date: 2026-08-04 23:25:58
updated: 2026-09-16 19:25:40
permalink: paper-summary-SWE-RL/
tags: [Agent, LLM, Reinforcement Learning]
kg:
type: Resource
id: res.paper-summary-swe-rl
name: "SWE-RL"
aliases: ["Paper Summary – SWE-RL", "paper-summary-SWE-RL"]
related: []
relations: []
---

复制进 vault:

1
2
python tools/kgify.py "E:\myBlog\source\_posts\Paper Summary – SWE-RL.md"
cp "E:\myBlog\source\_posts\Paper Summary – SWE-RL.md" "vault/30-resources/paper-summary-swe-rl.md"

在 Concept 上加边:

1
2
3
4
5
6
7
related: ["[[paper-summary-swe-rl]]"]
relations:
- rel: explained_by
to: "[[paper-summary-swe-rl]]"
evidence: "paper-summary-swe-rl.md#2. Implementation"
status: verified
source_type: paper

构建并验证:

1
2
python tools/update.py
python tools/graphify.py explain res.paper-summary-swe-rl

8.2.6 常见错误

现象 原因 修法
复制进 vault 后 E002 空 kg 且没有顶层 type;或根本没写 type 补 type / id / name
E003 关系起终点类型不对 查 §3.2 的 from/to
E004 同一条边写两次 删一条
E005 少了 name / path / stance 按类型补
E009 prerequisite_of 成环 环意味着永远学不完
E001 链接找不到 用 id 或 slug;检查别名
E010 两个节点抢同一个解析键 改名;或删掉重复别名
W002 related 缺目标 补 related
W003 evidence 的标题不存在 改成真实标题
E014 llm 边没标 proposed 改 proposed,或人确认后改 human

8.2.7 空的 kg 块

1
kg: {}

它是惰性的:build_graph.py 会把它当空 dict,merge 不到任何键;复制进 vault 仍然 E002。 它只是占位符,不是”已导入”。不要在全部文章里批量加空块——用 scaffolds/post.md 给新文章留槽位,或者按需填真实的 type/id/name。

kg: {} 比 kg:(null)好一点:未来的模板里 page.kg 是对象,不是 null。

8.3 夜间管道的三个坑

博客每晚 21:00 有一条自动管道(压缩图片 → 转相对路径 → 高亮转换 → 刷新 updated → 提交 → 部署 → 转回本地)。处理笔记时要注意:

  1. 加 kg 块不会自动上线。 触发阈值是 500 行;几篇笔记的 kg 块约 100 行,会判 SKIP。需要手动 commit,或等下次实质更新一起带上。
  2. 手动跑 --refresh-updated 会把 updated 刷成当天。 首页按 updated 降序,顺序会变。要么接受,要么事后改回。
  3. 工作区在 local 模式时,图片是 E:\myBlog\... 绝对路径。 直接复制会把这个路径带进副本;如果仓库要公开,从 HEAD 取版本,或者把图片行删掉。

另外:8 篇加密文章不要进图;permalink 大小写混用(如 paper-summary-SWE-RL/)要原样保留,id 用全小写 slug。

8.4 手动处理一篇的最小步骤

1
2
3
4
5
6
7
8
9
10
# 1. 在博客原文加 kg 块(kgify 只打印建议)
python tools/kgify.py "E:\myBlog\source\_posts\Paper Summary - OSWorld.md"

# 2. 复制进 vault,文件名用 slug
cp "E:\myBlog\source\_posts\Paper Summary - OSWorld.md" "vault/30-resources/paper-summary-osworld.md"

# 3. 在 Concept 上加一条 explained_by,evidence 指向真实标题

# 4. 构建 + 体检 + 出图
python tools/update.py

手动导入一篇约 5–10 分钟;12 篇约 2 小时;55 篇不值得。真正花时间的不是复制,是决定连哪条边。

9. 边界:现在不做的事

不做 原因
把文章当 Concept 太大,问不出”它是什么”
用 related_to 连一切 标签墙,信息量为 0
LLM 自动抽边进 verified E014 拦这个;要试只进 proposals/
图数据库 / 向量检索 个人规模下 networkx 够用
给 55 篇都建节点 先做同簇的两三篇

一个概念至少接 3 个已有节点。接不上的,现在还不是概念,是个标签。

10. 速查

1
2
3
4
5
6
7
8
9
10
11
python tools/update.py                      # 编译 + 体检 + 出图
python tools/build_graph.py # 只编译
python tools/check.py # 只体检
python tools/graphify.py stats # 统计
python tools/graphify.py explain <id> # 出边/入边
python tools/graphify.py prereq <id> # 前置链
python tools/graphify.py order # 学习顺序
python tools/graphify.py gaps # 结构缺口
python tools/graphify.py export # graph.html
python tools/kgify.py <博客文章> # 算 id / kg 块(只打印)
python tests/selftest.py # 26 条测试

最小 Concept frontmatter:

1
2
3
4
5
6
7
8
9
---
type: Concept
id: concept.example
name: 示例概念
aliases: [示例概念, example]
status: learning
related: []
relations: []
---

最小 Resource 的 kg 块:

1
2
3
4
5
6
kg:
type: Resource
id: res.example
name: "示例"
aliases: ["Example"]
relations: []

项目地址:personal_knowledge_graph

本文链接:https://wangyier.top/how-to-use-personal-knowledge-graph/

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 The Great Library!