知识图谱:从 3 条边到 15955 条边
起点:一个装饰性图谱
我的知识库门户里一直有个”图谱”页面:力导向布局、节点按来源着色、能拖拽缩放。
看起来很像回事。
直到我看了一眼数据:
nodes=1104 links=3 |
1104 个节点,3 条边。 边全部来自正文里的 [[双链]],而我几乎不写双链。
这个页面唯一的用途是截图好看。
为什么不直接用 LLM 抽
2026 年这个方向最热的是 GraphRAG(微软)和 LightRAG(港大),
它们的做法都是:用 LLM 从文本里抽实体和关系。
我想过,但算了三笔账:
- 额度:免费 10,000 neurons/天。用 LLM 抽 1187 篇文档,一次就可能吃掉大半,
而且抽取质量不稳定,重跑一次结果就变; - 可复现:规范判定要求”同输入同输出”。LLM 抽出来的图,今天和明天不一样,
基于它做的判定就没法解释; - 可维护:词表写错了,我能定位到具体词条;模型抽错了,我只能”再抽一次看看”。
所以我选了确定性路线:结构化事实优先,LLM 留到以后按需补充。
三层抽取
| 层 | 来源 | 产出 | 覆盖率 |
|---|---|---|---|
| L0 结构事实 | 路径、front-matter | 文档→目录/来源/设备/标签 | 100% |
| L1 显式链接 | [[双链]]、Markdown 相对链接 |
文档→文档 | 约 1250 条 |
| L2 词典实体 | 内置词表 + 正则 | 文档→实体 | 约 540 条 |
L0 是覆盖率的关键:每篇文档都”属于”某个目录、来自某个工具、在某台设备上。
这些关系不需要”理解”,只需要解析。先把确定的东西全拿到,再谈语义。
最终:
nodes=1649 edges=15955 |
三个必须处理的坑
坑 1:枢纽节点会毁掉可视化
device 和 source 这两个节点几乎连到所有文档。从任意文档出发走两跳:
文档 → 设备 → 另外 295 篇文档 |
图上一秒变毛线球。解法是展示但不穿透:枢纽类节点画出来,但不再从它继续扩展;
再加单层 60 个节点的上限。
坑 2:伪双链
正文里的 Markdown 示例会被误当成真链接:
比如可以写 [[{ "tag": "md", "text": "..." }]] |
原来的 31 条”断链”里,有 24 条是这种噪声。加了一条过滤规则
(长度、非法字符、协议前缀)后剩 7 条真实断链。
坑 3:中文分词
FTS5 用 unicode61,会把一整串中文当成一个 token。
所以「索引管线怎么分批嵌入」这种整句查询,MATCH 命中是 0,LIKE 兜底也难。
短期靠”查两个关键词”,中期要接 jieba 预分词。
规范判定:让规则可写、可复现
规则放在内容仓的 policy/rules/*.yaml,和笔记一起版本化:
- id: fm.required |
关键设计是 kind 是封闭枚举,YAML 只传参数、不写 SQL。
内容仓是可写的(笔记软件、网页、客户端都能改),如果规则里能塞 SQL,
等于给内容仓开了一个注入面。
结果
1187 篇 × 13 条规则 → 2068 条判定记录 |
error = 0 说明敏感信息扫描(私钥、API Key)在内容仓里零命中——这是我最想看到的结果。
至于那 756 条”孤立文档”:它们全是机器导入的会话记录,本来就没有入链。
规则的意义不是消灭数字,而是让数字可解释。
还没做的
- 传递闭包目前只到 10 轮,复杂层级还没压测;
- 图谱的社区聚类(Leiden)没做——要做也打算用纯 TS,不调 LLM;
- 增量判定:现在每次全量重算,规模再大就得改。
下一步如果做,我会先做增量,而不是做更花哨的图算法。
因为真实使用中,”改一篇笔记后多久能看到判定更新”比”图好看不好看”重要得多。


