---
title: 从 RAG 到知识图谱：让个人知识检索保留关系
description: 区分关键词检索、向量搜索、RAG 与笔记关系图，结合 ChatCrystal 的候选召回和一跳扩展实现，说明关系能补充什么、不能证明什么。
image: "https://zengliangyi.com/og/blog/rag-and-knowledge-graphs.png"
---

“记得讨论过，但搜不到”是整理 AI 对话时很常见的问题。找到了相似内容之后，又会出现另一个问题：这条笔记是解决方案、前置条件，还是后来被推翻的结论？

向量搜索和笔记关系图分别处理这两个问题的一部分。它们可以配合使用，但不能简单排成一条“关键词被向量淘汰、RAG 又被图谱淘汰”的升级路线。本文结合 [ChatCrystal](/projects/chatcrystal) 提交 [`7642768`](https://github.com/ZengLiangYi/ChatCrystal/tree/764276814e5c5a33ecbfeae63754f74b9261a3a8) 的实现，说明两者怎样连接，以及结果有哪些边界。

## 先分清检索与生成

| 方式 | 适合回答的问题 | 需要留意的边界 |

| --- | --- | --- |

| 字面匹配 | 哪条记录包含这个错误码、符号或路径？ | 表达不同可能漏掉结果 |

| 全文检索 | 哪些文档匹配这些词，如何排序？ | 分词、语言和排序配置影响结果 |

| 向量搜索 | 哪些内容与这个问题在表示空间中接近？ | 相似不等于正确，也不等于因果 |

| 关系图 | 已记录哪些笔记之间的联系？ | 关系可能缺失、过期或被错误提取 |

| RAG | 怎样利用检索到的材料生成回答？ | 还需要上下文组织、生成和证据核对 |

[RAG 原论文](https://arxiv.org/abs/2005.11401) 讨论的是把检索到的外部信息与语言生成结合。只返回一组向量搜索结果，还没有完成生成这一步；图中的关系也可以成为生成时使用的材料。

关键词搜索依然适合精确定位 `ECONNREFUSED` 或某个函数名。全文检索也不能被统一描述成只会机械匹配：具体能力取决于分词、同义词和排序配置。尤其不能假设任意默认分词器都能把中文按预期切开。

## 语义相关不代表同一个原因

搜索“内存泄漏排查”时，“OOM”“对象未释放”“大批量文件读取”都可能成为相关线索，但它们不是同义词。OOM 也可能来自合理工作集超过内存预算，不能因为笔记接近就断言存在泄漏。

向量检索的价值，是在不知道原文措辞时给出候选材料。随后仍需要读正文，确认运行环境、触发条件、版本和验证方法是否适用于当前问题。

ChatCrystal 将笔记中的选定字段拼成索引文本，分块后生成向量，查询时再计算问题的向量。所用 Vectra `LocalIndex` 的向量路径按候选相似度评分，不应把它写成已经建立 HNSW 或 LSH 索引；具体选型见[本地向量数据库选型](/blog/local-vector-database-selection)。

应用还会对直接结果做字面匹配加权、低分过滤和按笔记去重。符合字面匹配条件的结果可能被提高分数，因此返回的 `score` 不能一律当作原始向量相似度。

## 这里的图谱以笔记为节点

ChatCrystal 的关系图主要连接笔记，不是自动抽取整个代码库中所有函数、模块和调用关系。理解这一点，才能判断它适合回答什么问题。

当前关系类型包括 `CAUSED_BY`、`LEADS_TO`、`RESOLVED_BY`、`SIMILAR_TO`、`CONTRADICTS`、`DEPENDS_ON`、`EXTENDS` 和 `REFERENCES`。其中有些关系带方向。例如下面只是一个构造的说明：



```text
笔记 A：缓存失效时出现重复请求  
  笔记 B：用共享 Promise 合并并发请求  
    
  A --RESOLVED_BY--> B
```



反向浏览时，可以从 B 找回它解决的问题，但不能把边反过来解释成 B 被 A 解决。`SIMILAR_TO` 表达的相似，也不能替代 `CAUSED_BY` 所要求的原因证据。

## 自动发现的关系是候选结论

[关系发现实现](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/services/relations.ts) 先通过语义搜索寻找候选；没有可用候选时，回退到最近的笔记。它把候选信息与新笔记内容交给语言模型，限制关系类型，并过滤目标 ID 与置信度。

因此，它没有把新笔记与知识库中的每条笔记逐一比较。候选召回遗漏了某条记录，后续模型也就没有机会判断这条联系。当前实现还截取摘要以控制提示长度，重要条件可能没有进入关系判断的上下文。

模型输出的置信度是筛选信号，不是经过校准的事实概率。一条 `CAUSED_BY` 边即使分数较高，也需要回到笔记及原始材料核对。图谱增加了可见的联系，同时也增加了要维护的假设。

## 当前搜索扩展是一跳

[语义搜索实现](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/services/embedding.ts) 先得到直接命中的笔记；开启关系扩展时，再查这些笔记相邻的关系，加入符合条件的关联笔记，并对分数折减。

这个循环遍历的是直接结果，不会把新加入的邻居继续无限展开。因此这里应理解为**直接召回加一跳关系扩展**，不能写成任意多跳推理引擎。关联结果带有 `viaRelation` 标记，但仅凭这个标记还不足以恢复完整的有向关系；需要时应读取关系记录。扩展结果的 `chunkText` 为空，正文也需要另行读取。

入口同样影响行为：[HTTP 搜索接口](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/routes/notes.ts) 通过 `expand=true` 开启扩展，而 MCP 的 `search_knowledge` 仅提供 `query` 和 `limit`，当前调用不会开启关系扩展。`recall_for_task` 则通过 `options.include_relations` 控制这一行为，默认开启。



```text
问题  
  ↓ 生成查询向量  
  直接命中的笔记  
  ↓ 可选的一跳关系扩展  
  直接结果与关联笔记  
  ↓ 调用方读取、筛选  
  组织上下文并生成回答
```



最后一步属于使用这些结果的客户端或 Agent 工作流。检索服务返回材料，不意味着每次搜索都会额外调用语言模型生成最终答案。

## 怎样判断关系扩展是否值得

先构造一小组你知道答案的任务。对同一任务分别查看直接检索与开启关系扩展后的结果，记录是否找到了必要前提、是否引入无关笔记，以及阅读额外材料需要多少时间。

至少包含三种情况：关系确实有帮助、关系过期、两条相似笔记实际给出了相反结论。评价目标应是当前任务能否据此作出更好的判断，而不是图上多了多少条边。

如果需要在此基础上生成答案，还应检查答案是否保留来源、是否把推测写成事实，以及缺少证据时能否说明不足。检索质量与生成质量要分别观察，避免把一次流畅回答当成整个系统正确的证明。

对个人知识库而言，关键词帮助精确定位，向量帮助发现候选，关系帮助检查上下文。怎样把这些材料交给 Agent 并写回经过验证的经验，见 [Agent 知识库设计](/blog/agent-knowledge-memory-loop)；模型调用的数据流见[本地优先与隐私边界](/blog/local-first-ai-data-boundaries)。

分享：

##  相关文章

[##  笔记关系与图视图：从候选发现到可验证扩展

按 ChatCrystal 当前关系 API、候选筛选与图投影说明自动发现、手动关系和标签图的实际边界。          曾亮懿       2026年10月3日      阅读约 3 分钟

#ChatCrystal

#知识图谱

#关系](/blog/note-relations-and-graph)    [##  语义搜索与关系上下文：发现隐藏依赖但保留验证

用构造的重构场景组合直接检索和一跳关系扩展，解释分数、候选和来源字段，不把关联线索写成依赖证明。          曾亮懿       2026年10月3日      阅读约 3 分钟

#ChatCrystal

#语义搜索

#知识图谱](/blog/semantic-search-relation-context)    [##  语义搜索流程：请求、候选去重与关系扩展

沿 ChatCrystal 实现检查查询向量、候选升级、词项加分和关系扩展，用实际 API 区分笔记列表筛选与语义检索。          曾亮懿       2026年10月3日      阅读约 3 分钟

#ChatCrystal

#语义搜索

#RAG](/blog/semantic-search-workflow)

##  从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录，欢迎交流实现细节。

[访问我的 GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "从 RAG 到知识图谱：让个人知识检索保留关系",
  "description": "区分关键词检索、向量搜索、RAG 与笔记关系图，结合 ChatCrystal 的候选召回和一跳扩展实现，说明关系能补充什么、不能证明什么。",
  "url": "https://zengliangyi.com/blog/rag-and-knowledge-graphs/",
  "image": "https://zengliangyi.com/images/blog/rag-and-knowledge-graphs.svg",
  "datePublished": "2026-06-05T00:45:00.000Z",
  "dateModified": "2026-10-03T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/blog/rag-and-knowledge-graphs/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "首页",
      "item": "https://zengliangyi.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "文章",
      "item": "https://zengliangyi.com/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "从 RAG 到知识图谱：让个人知识检索保留关系",
      "item": "https://zengliangyi.com/blog/rag-and-knowledge-graphs/"
    }
  ]
}
```
