知识图谱:笔记关系发现与可视化

本文面向:想了解 ChatCrystal 如何自动发现笔记间关联的开发者。 预计阅读时间:10 分钟


什么是「知识图谱」?

在 ChatCrystal 的语境下,知识图谱不是 Neo4j 那种企业级图数据库,而是一张笔记关系网络:节点是你的笔记,边是笔记之间的语义关系。每条边带三个属性------关系类型(比如「被解决」)、置信度(0-1)、来源(LLM 自动发现或手动创建)。

存储层很简单,一张 note_relations 表就够了:

sql 复制代码
CREATE TABLE note_relations (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  source_note_id INTEGER NOT NULL,
  target_note_id INTEGER NOT NULL,
  relation_type TEXT NOT NULL,
  confidence REAL DEFAULT 1.0,
  description TEXT,
  created_by TEXT NOT NULL DEFAULT 'manual',  -- 'manual' 或 'llm'
  created_at TEXT DEFAULT (datetime('now')),
  UNIQUE(source_note_id, target_note_id, relation_type),
  FOREIGN KEY (source_note_id) REFERENCES notes(id) ON DELETE CASCADE,
  FOREIGN KEY (target_note_id) REFERENCES notes(id) ON DELETE CASCADE
);

注意 UNIQUE 约束:同一对笔记、同一种关系类型只能存在一条记录。created_by 字段区分来源------manual 是用户手动创建的,llm 是自动发现的。

八种关系类型

ChatCrystal 定义了 8 种关系类型,覆盖了日常开发中笔记之间最常见的语义联系:

类型 含义 例子
CAUSED_BY A 的问题由 B 引起 「CORS 报错」← 由 ← 「Nginx 反代配置修改」
LEADS_TO A 导致了 B 的结果 「升级 Node 20」→ 导致 → 「ESM 模块加载失败」
RESOLVED_BY A 的问题被 B 解决 「SQLite 内存溢出」→ 被解决 → 「切换 sql.js WASM」
SIMILAR_TO 主题或技术相似 「React 状态管理方案对比」≈「Vue Pinia 选型分析」
CONTRADICTS 结论互相矛盾 「JWT 无状态优于 Session」╳「Session 更安全」
DEPENDS_ON A 依赖于 B 「GraphQL 分页实现」← 依赖 ←「Apollo Client 缓存策略」
EXTENDS A 是 B 的扩展 「Tailwind v4 迁移踩坑」← 扩展 ←「Tailwind v3 自定义主题」
REFERENCES A 引用了 B 的内容 「CI 流水线优化」← 引用 ←「GitHub Actions 缓存机制」

这 8 种类型由 Zod schema 约束,LLM 只能从这个枚举里选择,不会自由发挥。

自动发现:LLM 如何分析笔记关系

这是整个系统的核心。当你生成一条新笔记时,ChatCrystal 可以自动发现它与已有笔记之间的关系。整个流程分三步。

第一步:候选笔记筛选

discoverRelations(noteId) 首先获取源笔记的完整信息------标题、摘要、关键结论、标签,然后去数据库里找「可能有关系」的候选笔记:

typescript 复制代码
async function findCandidateNotes(noteId: number, noteTitle: string): Promise<CandidateNote[]> {
  // 优先用语义搜索找相似笔记
  let candidateIds: number[] = [];
  try {
    const searchResults = await semanticSearch(noteTitle, MAX_CANDIDATES);
    candidateIds = searchResults.map(r => r.noteId).filter(id => id !== noteId);
  } catch {
    // 语义搜索不可用,走降级逻辑
  }

  if (candidateIds.length > 0) {
    // 按语义相似度选出来的候选
    // ...
  }

  // 降级:取最近的 20 条笔记
  // ...
}

这里有两个关键设计:

  • 语义搜索优先:用 Embedding 模型把笔记标题向量化,在 vectra 索引里找到最相似的 20 条。这意味着如果两条笔记讨论的是同一个技术问题,即使措辞完全不同,也能被选为候选。
  • 降级兜底:如果 Embedding 模型没配置、索引为空,就退化为按时间倒序取最近 20 条。覆盖率低但不会报错。

第二步:LLM 分析

拿到候选列表后,系统构造一个结构化 prompt 发给 LLM。prompt 包含源笔记的详细信息和候选笔记的摘要列表,每条候选标注了 ID、标题、摘要(截取前 200 字)和标签:

makefile 复制代码
新笔记:
标题: SQLite WASM 内存优化方案
摘要: 在处理大规模数据导入时发现 sql.js 的内存占用过高...
标签: sqlite, wasm, performance

已有笔记:
[id=42] "Nginx 反代导致 WebSocket 断连" - 生产环境出现频繁断连... [nginx, websocket]
[id=43] "数据导入管道重构" - 将批量插入改为分批事务... [sqlite, batch]

LLM 使用 Vercel AI SDK 的 generateObject() 返回结构化 JSON 数组,每条关系包含 target_note_idrelation_typeconfidence(0-1)和 description(20 字以内)。系统 prompt 明确要求:

  • 只返回置信度 >= 0.5 的关系
  • 最多返回 5 条
  • 不要编造不存在的关系

第三步:过滤与持久化

LLM 的输出不是直接入库,还要过两道过滤:

  1. 候选 ID 验证target_note_id 必须在候选集合里------防止 LLM 幻觉出一个不存在的笔记 ID
  2. 置信度阈值confidence >= 0.5 的才保留,最多 5 条

通过过滤的关系以 INSERT OR IGNORE 写入数据库。OR IGNORE 处理 UNIQUE 约束冲突------如果同一条关系已经存在,跳过而不是报错。写入后立即 saveDatabase() 持久化到磁盘。

typescript 复制代码
const filteredRelations = rawRelations
  .filter(rel => candidateIdSet.has(rel.target_note_id) && rel.confidence >= MIN_CONFIDENCE)
  .slice(0, MAX_RELATIONS);

手动创建关系

除了 LLM 自动发现,你也可以通过 API 手动创建关系。这在 LLM 漏掉了一些你认为重要的关联时很有用:

bash 复制代码
curl -X POST http://localhost:3721/api/notes/42/relations \
  -H "Content-Type: application/json" \
  -d '{
    "target_note_id": 58,
    "relation_type": "RESOLVED_BY",
    "description": "被新的 WASM 方案解决"
  }'

手动创建的关系 confidence 默认为 1.0,created_bymanual。API 会验证两端笔记都存在,且不允许自引用(source_note_id === target_note_id)。

删除关系同样简单:

bash 复制代码
curl -X DELETE http://localhost:3721/api/relations/7

图数据 API:nodes + edges

知识图谱的可视化需要一个专门的图数据接口。GET /api/relations/graph 返回完整的节点和边集合:

bash 复制代码
# 获取全量图数据
curl http://localhost:3721/api/relations/graph

# 按项目过滤
curl http://localhost:3721/api/relations/graph?project=ChatCrystal

返回格式:

json 复制代码
{
  "success": true,
  "data": {
    "nodes": [
      {
        "id": 42,
        "title": "SQLite WASM 内存优化方案",
        "project_name": "ChatCrystal",
        "tags": ["sqlite", "wasm", "performance"]
      }
    ],
    "edges": [
      {
        "source": 42,
        "target": 58,
        "type": "RESOLVED_BY",
        "confidence": 0.85
      }
    ]
  }
}

节点数据来自 notesconversations 表的 JOIN 查询,通过子查询聚合标签。边数据从 note_relations 表取出后,会过滤掉两端节点不在当前视图中的边------如果你按项目过滤了节点,孤立的边不会出现在结果里。

关系扩展搜索

这是知识图谱最实用的功能之一。语义搜索只能找到与查询词直接匹配的笔记,但很多时候你需要的是「关联知识」。比如你搜「SQLite 内存问题」,直接命中的可能是「sql.js WASM 内存优化」,但沿着图谱边走下去,你可能还会发现「数据导入管道重构」------它通过 EXTENDS 关系连接到内存优化笔记。

当搜索请求带 expandRelations=true 参数时,ChatCrystal 会沿着图谱边扩展搜索结果:

  • 从直接命中的笔记出发,遍历所有关联边
  • 只扩展置信度 >= 0.5 的边
  • 关联笔记的得分 = 原始得分 x 0.7 x 边置信度

0.7 的折扣系数是关键:它确保关联笔记永远排在直接命中之后,但又不至于被完全忽略。这个设计让搜索结果既精准又有广度。

批量发现

如果你的笔记库里已经有大量笔记但还没有关系,可以一键触发批量发现:

bash 复制代码
curl -X POST http://localhost:3721/api/relations/batch-discover

这个接口会找出所有「没有作为任何关系的 source」的笔记,为它们逐个排队触发 LLM 发现。任务进入 p-queue 队列(并发度 1,每秒 1 次请求),不会打爆你的 LLM API。返回值告诉你排队了多少任务:

json 复制代码
{
  "success": true,
  "data": {
    "queued": 15,
    "queue": { "pending": 3, "running": 1 }
  }
}

批量发现的筛选逻辑很精确------只处理完全没有出边的笔记,不会重复处理已有关系的笔记。如果你想重新发现某个笔记的关系,可以先删除旧关系再单独触发。

可视化

Web 界面的知识图谱页面消费的就是 GET /api/relations/graph 返回的 nodes + edges 数据。前端用力导向图(force-directed layout)渲染,节点大小反映关系数量,边的颜色区分关系类型,悬停显示置信度和描述。

你可以通过项目过滤器切换视图------只看某个项目的知识图谱,避免节点过多导致图谱混乱。

下一步

知识图谱系统目前的几个已知方向:

  • 双向关系 :当前边是有方向的(source -> target),但某些关系类型(如 SIMILAR_TO)天然是双向的。未来可以考虑在查询时自动对称展开。
  • 关系强度衰减:随着时间推移,旧的关系可能不再重要。可以引入时间衰减因子,让近期的关系在搜索扩展中权重更高。
  • 图谱分析:基于图结构做一些简单的分析------比如找出「枢纽笔记」(关系最多的节点)、发现「知识孤岛」(没有任何关系的笔记)。
  • 增量发现:当前批量发现是全量扫描,未来可以在每次导入新对话后自动触发增量发现,只处理新增笔记。

知识图谱把 ChatCrystal 从一个「笔记搜索工具」提升为「知识网络」。当你积累了足够的笔记和关系,它就能帮你发现那些你自己都忘了的关联------这恰恰是 AI 对话知识管理最有价值的地方。


项目地址:github.com/ZengLiangYi...

相关推荐
来一斤小鲜肉2 小时前
如何在 Claude Code 中使用 MCP
ai编程
plainGeekDev2 小时前
你以为大模型在"思考"?它只是在猜下一个词
aigc·ai编程
ZengLiangYi2 小时前
sql.js WASM 实战:浏览器里跑 SQLite
aigc·ai编程
先吃饱再说2 小时前
我的第一次「Claude Code」实战:用 AI 敲出一个外卖 App 落地页
ai编程
常威正在打来福2 小时前
frontend-design入门指南:OpenClaw/Claude Code/Codex 三平台安装教程
人工智能·aigc·ai编程
wangruofeng3 小时前
GitHub AI 月榜解读:8 大趋势告诉你该关注什么
github·ai编程
爱吃的小肥羊3 小时前
又上新闻!OpenAI 称推翻困扰数学界近 80 年的「平面单位距离猜想」
aigc·openai·ai编程
视觉&物联智能3 小时前
【杂谈】-企业人工智能超越实验:安全拓展的实践路径
人工智能·安全·aigc·agent·agi
码途漫谈3 小时前
让 AI 编程不断线:9Router 的本地模型路由与 Token 节流术
人工智能·ai·开源·ai编程