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 | cd "E:\Personal Knowledge Graph" |
它把下面三步串起来,但顺序有一处不同:先体检、后发布。
1 | python tools/build_graph.py # 编译 + 校验,有 E 就不写 graph/ |
手动跑这三条时,
build_graph.py会先写graph/,再跑check.py;即使 check 报错,文件已经更新了。update.py把发布放到体检之后,所以失败时graph/不动。
update.py
的编译或体检失败就停:编译失败不进入体检;体检发现 error
在发布前停;两种情况 graph/ 都保持上一次的内容。
第一次跑应该看到类似:
1 | [1/3] 编译 18 节点 / 18 边 |
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 | python tools/kgify.py "E:\myBlog\source\_posts\Paper Summary - OSWorld.md" |
它只打印,不改文件。把建议的 kg: 块贴到博客 frontmatter
末尾:
1 | kg: |
- 复制进 vault,文件名用 slug:
1 | cp "E:\myBlog\source\_posts\Paper Summary - OSWorld.md" "vault/30-resources/paper-summary-osworld.md" |
- 边写在 Concept 上,不写在 Resource 上:
1 | - rel: explained_by |
- 构建 + 体检 + 出图:
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 | ## 一句话定义 |
三个门槛,缺一不可:
- 能被一句话定义;
- 能被反驳(「它和 X 有关」不是定义);
- 至少接得上 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 | related: ["[[paper-summary-osworld]]"] # Obsidian 认文件名 |
两种写法指向同一个节点,不会误报。
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 | $ python tools/graphify.py prereq concept.backpropagation |
1 | $ python tools/graphify.py order |
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 | kg: |
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 只做三件事:
- 解析整个 frontmatter;
- 如果
kg是 mapping,执行meta.update(kg)——kg 里的键覆盖顶层同名键; - 没有
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 | kg: |
Concept:一个你能一句话定义的东西。边通常从它出发。
1 | kg: |
Claim:一个技术判断。必填 stance。
1 | kg: |
Implementation:源码文件或函数。必填
path。通常不写成博客文章,所以直接用顶层字段:
1 | type: Implementation |
Project:项目本身。通常是 applied_in
的终点,边写在 Concept 上,所以 relations 可以为空:
1 | type: Project |
如果
Implementation/Project也要发成博客文章,把上面这些键原样套进kg:即可。
8.2.4 一条边的完整写法
1 | relations: |
| 字段 | 必填 | 说明 |
|---|---|---|
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) |
四条规则:
- 一条边只写一次。 同一对
(src, rel, dst)重复会E004。 - 不写反向边。
A --prerequisite_of--> B写在 A 里;反过来查由graphify做。 related要同步。 它是relations的扁平投影;缺了会W002。proposed不进 vault。 机器候选放proposals/;写进 vault 会W006。
8.2.5 完整例子:一篇博客文章 → 一个 Resource → 一条边
博客原文:
1 |
|
复制进 vault:
1 | python tools/kgify.py "E:\myBlog\source\_posts\Paper Summary – SWE-RL.md" |
在 Concept 上加边:
1 | related: ["[[paper-summary-swe-rl]]"] |
构建并验证:
1 | python tools/update.py |
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 → 提交 → 部署 → 转回本地)。处理笔记时要注意:
- 加
kg块不会自动上线。 触发阈值是 500 行;几篇笔记的kg块约 100 行,会判 SKIP。需要手动 commit,或等下次实质更新一起带上。 - 手动跑
--refresh-updated会把updated刷成当天。 首页按updated降序,顺序会变。要么接受,要么事后改回。 - 工作区在 local 模式时,图片是
E:\myBlog\...绝对路径。 直接复制会把这个路径带进副本;如果仓库要公开,从HEAD取版本,或者把图片行删掉。
另外:8 篇加密文章不要进图;permalink 大小写混用(如
paper-summary-SWE-RL/)要原样保留,id 用全小写
slug。
8.4 手动处理一篇的最小步骤
1 | # 1. 在博客原文加 kg 块(kgify 只打印建议) |
手动导入一篇约 5–10 分钟;12 篇约 2 小时;55 篇不值得。真正花时间的不是复制,是决定连哪条边。
9. 边界:现在不做的事
| 不做 | 原因 |
|---|---|
把文章当 Concept |
太大,问不出”它是什么” |
用 related_to 连一切 |
标签墙,信息量为 0 |
LLM 自动抽边进 verified |
E014 拦这个;要试只进 proposals/ |
| 图数据库 / 向量检索 | 个人规模下 networkx 够用 |
| 给 55 篇都建节点 | 先做同簇的两三篇 |
一个概念至少接 3 个已有节点。接不上的,现在还不是概念,是个标签。
10. 速查
1 | python tools/update.py # 编译 + 体检 + 出图 |
最小 Concept frontmatter:
1 |
|
最小 Resource 的 kg 块:
1 | kg: |