Skip to main content

GraphRAG 学习笔记

·6269 words·13 mins·
Table of Contents

如果文章中有不准确的地方,欢迎留言指正。

1 说明
#

  • 知识图谱 用结构化方式表达知识中的实体和关系
  • RAG 从外部知识中检索相关内容,补充给大模型回答
  • GraphRAG 可以理解为在 RAG 中引入图结构,让检索不只依赖文本相似度,也能利用实体关系和全局结构
  • Neo4j 是一种图数据库,可以存储实体、关系和社区等图结构,让后续查询可以沿着节点和关系扩展上下文

普通 RAG 已经能解决很多知识问答问题,但它更擅长的是“从文档里找相关片段”。
如果问题涉及多个实体之间的关系,或者需要从整批文档中总结主题,单纯依赖向量相似度就不一定够用。

GraphRAG 要解决的正是这类问题:先把文本中的实体和关系抽取出来,构建知识图谱,再在查询时结合图结构、社区摘要和原文证据生成答案。

2 知识图谱
#

知识图谱的核心不是“图”这个形式本身,而是用结构化方式表达知识。

2.1 实体
#

实体是知识图谱中的节点,可以是人、组织、地点、概念、事件、产品等。

例如:

  • GraphRAG
  • 知识图谱
  • 向量数据库
  • Microsoft
  • Neo4j

在文本中,这些实体往往分散在不同段落甚至不同文档里。
如果只把文档看成一组 chunk,这些实体之间的联系就需要在查询时临时推断。

2.2 关系
#

关系描述实体之间的连接。

例如:

GraphRAG - 使用 - 知识图谱
RAG - 依赖 - 向量检索
知识图谱 - 包含 - 实体关系
Neo4j - 存储 - 图数据

关系的价值在于,它把文本中原本隐含的信息显式表达出来。
当这些关系被保存下来之后,后续检索就不只是“找相似文本”,还可以沿着实体之间的连接继续扩展。

2.3 三元组
#

知识图谱里常见的表达方式是三元组:

头实体 - 关系 - 尾实体

例如:

GraphRAG - 结合 - RAG
GraphRAG - 引入 - 图结构
实体 - 通过 - 关系连接
社区检测 - 用于 - 发现实体群组

三元组看起来很简单,但它能把一段自然语言拆成可以计算、可以查询、可以扩展的结构。

2.4 实体提及和实体消歧
#

文本里出现的一次实体名称,可以理解为一次实体提及。

例如:

LLM
Large Language Model
大语言模型

这三个说法在不同上下文里可能指向同一个实体。
如果构建图谱时把它们当成三个完全不同的节点,图谱就会被拆散,后续关系查询也会变得不准确。

实体消歧要解决的就是这个问题:判断不同提及是否指向同一个真实实体,并尽量把它们合并到统一节点上。

2.5 邻居节点
#

在图里,和某个节点直接相连的节点,就是它的邻居节点。

例如:

GraphRAG
  -> RAG
  -> 知识图谱
  -> 社区摘要
  -> 向量检索

这些节点都可以看作 GraphRAG 的一跳邻居。
在 GraphRAG 的 Local Search 中,常见做法就是先命中一个种子实体,再沿着它的邻居节点扩展上下文。

2.6 多跳关系
#

有些实体之间没有直接关系,但可以通过中间节点连接起来。

例如:

GraphRAG -> 知识图谱 -> Neo4j -> 图数据库

这种路径就是多跳关系。
它的价值在于,可以帮助系统发现间接关联,而不是只能依赖两个实体是否同时出现在同一个文本片段里。

2.7 子图
#

真实的知识图谱可能很大,查询时通常不会把整张图都交给模型。
更常见的做法是,围绕问题相关的实体和关系截取一小部分图,这部分图可以理解为子图。

例如用户问 GraphRAG 和知识图谱有什么关系,系统只需要取出和这两个实体相关的邻居、关系和原文证据,而不需要遍历整张图。

子图的作用,是在信息量和相关性之间做平衡:
既保留图结构,又避免把无关节点塞进上下文。

2.8 社区发现
#

社区发现是图分析中的一个重要概念。
如果一组节点之间连接很密集,而它们和外部节点的连接相对较少,就可以认为它们形成了一个社区。

在知识图谱里,一个社区往往对应某个主题、事件或实体群组。

例如:

社区 A:RAG、向量数据库、Embedding、chunk
社区 B:知识图谱、实体、关系、Neo4j
社区 C:社区发现、社区摘要、Global Search

社区发现的作用,是把一张大图拆成更容易理解的主题块。
GraphRAG 之所以能处理全局性问题,很大程度上就依赖这一步。

2.9 社区摘要
#

社区发现只是完成了分组,分组本身还不能直接回答问题。
所以 GraphRAG 通常还会为每个社区生成摘要。

社区摘要可以包含:

  • 这个社区的主题
  • 关键实体
  • 关键关系
  • 主要事实
  • 这组实体共同表达的含义

有了社区摘要,系统在回答全局问题时,就不需要临时扫描所有原文。
它可以先判断哪些社区摘要和问题相关,再把相关社区的信息综合起来。

这也是 Global Search 的基础:
先基于社区摘要做全局聚合,再让模型生成最终答案。

2.10 知识图谱解决什么问题
#

知识图谱比较适合表达这些信息:

  • 实体之间的直接关系
  • 多跳关系
  • 跨文档出现的同一实体
  • 一组实体形成的主题或社区

它不直接替代文本,而是给文本增加了一层结构。
文本负责保留上下文和细节,图谱负责保存实体之间的连接。

3 RAG
#

RAG 是 Retrieval-Augmented Generation,也就是检索增强生成。

3.1 普通 RAG 的流程
#

普通 RAG 一般会这样工作:

  1. 把文档切成 chunk
  2. 对 chunk 做 embedding
  3. 把向量写入向量数据库
  4. 用户提问时,对问题做 embedding
  5. 从向量数据库中召回 top-k 片段
  6. 把召回片段交给 LLM 生成答案

这个流程的重点是:把大模型不知道的外部知识,通过检索的方式补充进上下文。

3.2 普通 RAG 擅长什么
#

普通 RAG 比较适合局部事实问答。

例如:

  • 某个概念是什么意思
  • 某段文档里提到了什么
  • 某个配置项怎么使用
  • 某个问题的直接答案是什么

这类问题通常能在少量相关 chunk 中找到答案。
只要召回质量足够好,LLM 就可以基于这些片段生成比较可靠的回答。

3.3 普通 RAG 的局限
#

普通 RAG 的核心依据是向量相似度。
它擅长找到语义相近的文本片段,但不天然擅长处理关系结构。

所以在这些场景里会比较吃力:

  • 多个实体之间存在间接关系
  • 答案分散在多个 chunk 中
  • 问题需要全局总结
  • 需要解释一批文档的整体主题

问题不在于向量检索没有价值,而在于它默认把知识组织成“文本片段集合”。
当问题本身更像是在问“这些实体之间有什么关系”时,就需要额外的结构来辅助检索。

4 GraphRAG
#

GraphRAG 可以理解为:在普通 RAG 的基础上,引入知识图谱和图分析能力。

4.1 核心思路
#

普通 RAG 主要在查询时做检索。
GraphRAG 会把一部分理解和聚合工作提前到索引阶段。

典型流程是:

  1. 从文档中抽取实体
  2. 抽取实体之间的关系
  3. 构建知识图谱
  4. 对图谱做社区检测
  5. 为社区生成摘要
  6. 查询时结合实体、关系、社区摘要和原文证据回答问题

这里最关键的变化,是 GraphRAG 不只索引文本,还会索引文本背后的结构。

4.2 和普通 RAG 的区别
#

普通 RAG 更像是在问:

哪些文本片段和这个问题最相似?

GraphRAG 更进一步,会问:

问题涉及哪些实体?
这些实体之间有什么关系?
它们属于哪个主题社区?
相关社区能提供什么全局背景?

所以 GraphRAG 并不是简单地把向量数据库换成图数据库,而是把检索对象扩展了。

普通 RAG 主要检索:

  • 文本片段

GraphRAG 还会利用:

  • 实体
  • 关系
  • 邻居节点
  • 社区摘要
  • 原文证据

4.3 为什么需要社区摘要
#

在图谱中,关系密集的一组实体通常可以形成一个社区。
社区可以理解为一组主题上更接近、连接上更紧密的实体集合。

对社区生成摘要之后,查询全局问题时就不需要临时扫描所有原文。
系统可以先看每个社区摘要和问题是否相关,再把相关社区的部分答案合并起来。

这也是 GraphRAG 和普通 RAG 差异比较明显的地方:
普通 RAG 更偏向局部召回,GraphRAG 可以通过社区摘要支持更强的全局聚合。

5 查询方式
#

GraphRAG 中常见的查询方式可以分成 Local Search、Global Search 和 DRIFT Search。 实际使用时,可以先判断问题类型,再选择对应的查询方式。
如果问题围绕具体实体或实体关系,更适合 Local Search;如果问题关注整体主题或全局总结,更适合 Global Search。

5.1 Local Search#

Local Search 适合回答和具体实体相关的问题。

例如:

  • A 是谁
  • A 和 B 是什么关系
  • 某个实体关联了哪些事件

它通常会从实体出发,查找实体邻居、关系和相关原文。
这种方式的重点不是全局总结,而是围绕一个或几个实体展开上下文。

大致流程:

问题
  ↓ embedding → 实体向量库检索 top-K 实体
  ↓ 对每个实体拉 1~2 跳邻居 + 相关三元组
  ↓ 查实体所属社区摘要作为背景补充
  ↓ 根据三元组里的 chunk_id 反查原文 chunk
  ↓ 组装“实体 + 关系 + 社区报告 + 原文”上下文
  ↓ LLM 基于上下文生成答案

5.2 Global Search#

Global Search 适合回答全局性问题。

例如:

  • 这批文档的核心主题是什么
  • 整体上有哪些关键矛盾
  • 不同社区分别在讲什么

它通常依赖预先生成的社区摘要。
查询时,系统会先判断各个社区摘要对问题的贡献,再综合相关社区的信息生成最终答案。

这种方式更适合“看整体”的问题,而不是只查某一个实体的细节。

大致流程:

问题
  ↓ 读取所有社区报告
  ↓ Map:LLM 对每份社区报告打相关度分数 + 生成 partial_answer(局部答案)
  ↓ 过滤 score < GLOBAL_MIN_SCORE 的低相关社区报告
  ↓ 按 score 排序,选出高相关 partial_answer
  ↓ Reduce:LLM 综合这些 partial_answer
  ↓ 生成全局答案

5.3 DRIFT Search#

DRIFT Search 介于 Local Search 和 Global Search 之间。

它可以先获得一个全局视角,再根据全局答案里的关键实体继续深入局部细节。

可以简单理解为:

问题
  ↓ Global Search 生成全局视角的初步答案
  ↓ LLM 从初步答案中抽取 2~4 个关键实体
  ↓ “关键实体 + 原问题”embedding → 实体向量库检索候选实体
  ↓ 对匹配到的关键实体拉邻居 + 相关三元组
  ↓ 组装关键实体的局部细节
  ↓ LLM 综合“全局答案 + 局部细节”生成最终答案

如果问题既需要全局判断,又需要具体实体支撑,DRIFT Search 会比单纯 Local 或 Global 更自然。

6 GraphRAG 的几种接入方式
#

GraphRAG 不是一种固定写法。
根据图谱参与的位置不同,它可以有几种不同的接入方式,大致可以分成三类:

  • 图谱作为"上下文补充器":6.1、6.2、6.3。在传统 RAG 链路里引入图谱,区别在于图谱介入的深度
  • 图谱作为"全局视角的载体":6.4。通过社区摘要支持全局聚合类问题,是 Microsoft GraphRAG 的核心特性
  • 图谱作为"Agent 的工具":6.5。把图谱查询和向量检索一起当作工具交给模型自主调用

实际系统里这几种方式可以组合使用,并不是互斥的选择。

6.1 图谱只补充上下文
#

最轻量的方式,是先按普通 RAG 召回文本,再根据召回文本反查图谱关系。

问题 -> 向量检索 -> chunks -> 反查实体和关系 -> chunks + triples -> LLM

这种方式里,图谱不参与召回排序,只负责补充结构化上下文。
它适合在已有 RAG 系统上做渐进增强,改动比较小。

6.2 图谱参与召回
#

更进一步,可以让图谱直接参与召回。

问题 -> 抽实体 -> 实体链接 -> 图谱遍历 -> triples -> 反查 chunks -> LLM

这里的"实体链接"是把 LLM 抽出来的实体名字对齐到图谱里真实存在的节点。
例如 LLM 抽出 GraphRAG,图谱里可能存的是 Graph RAG图检索增强生成,需要做一次对齐。
常见做法是:精确匹配 -> 别名匹配 -> 向量相似度兜底。

这种方式适合实体关系比较明确的问题,例如:

GraphRAG 和 Neo4j 有什么关系?

系统可以先识别出 GraphRAGNeo4j,再沿着图谱查找它们之间的关系、邻居节点和证据来源。

6.3 图谱参与重排
#

图谱也可以不直接进入上下文,而是参与 chunk 排序。

问题 -> 向量 / BM25 召回 chunks
chunks -> 映射实体
根据实体距离、路径关系、社区归属重新打分
重排 chunks -> LLM

具体打分可以做得很简单,例如:

  • chunk 中提到的实体,在图谱中距离种子实体的最短跳数越小,加分越高
  • chunk 中涉及的多个实体属于同一社区,加分
  • chunk 中涉及的实体之间存在直接关系,加分

这种方式的好处是对原有 RAG 链路影响较小。
图谱只负责告诉系统哪些 chunk 和核心实体更相关,最终给 LLM 的仍然主要是文本证据。

Rerank 看文本相关性,图谱重排看结构相关性。

6.4 社区摘要支持全局检索
#

如果问题偏全局总结,就可以使用社区摘要。

文档 -> 抽实体关系 -> 构图 -> 社区发现 -> 社区摘要
问题 -> 社区摘要评分 -> 聚合相关社区 -> LLM

这里"评分 + 聚合"的具体执行方式是典型的 Map-Reduce

  • Map:对每一个社区摘要单独调用 LLM,让它判断这份摘要和问题的相关度,并写出能贡献的部分答案
  • Reduce:把高分的部分答案合并起来,再让 LLM 综合生成最终答案

这种方式适合回答:

  • 这批文档的核心主题是什么
  • 不同实体群体之间有哪些主要关系
  • 整体上有哪些关键发现

它的重点不是查某个实体的一跳邻居,而是利用社区摘要获得全局视角。

需要注意的是,社区摘要不仅服务于 Global Search。
Local Search 也会把种子实体所属社区的摘要作为背景信息一并放进上下文,让模型在回答具体问题时仍然能看到全局结构。

6.5 Agent 调用图谱工具
#

还有一种更灵活的方式,是把图谱查询、向量检索、关键词检索都作为工具交给 Agent。

问题 -> Agent 判断下一步
     -> 调用向量检索
     -> 调用图谱查询
     -> 调用关键词检索
     -> 汇总答案

这种方式适合复杂分析任务,但成本和稳定性也更难控制。
LLM 自己决定何时调用、调用哪个工具、调几次,对模型的指令遵循和工具使用能力要求较高。
工程上还要在编排层加上步数上限、循环检测、错误兜底等保护,避免 Agent 卡在工具调用循环里。

7 Local Search 示例
#

完整代码:Yu-Yantao/GraphRAG

7.1 入库阶段
#

Local Search 能跑起来,前提是入库时已经做好两件事:

  1. 把 chunk 里的实体和关系抽出来,建立图谱
  2. 把所有实体向量化,建立实体向量库

入库流程:

flowchart TD
Doc["Markdown 文档"]
Doc --> Split["切分 chunks"]
Split --> Chunks["chunks"]

    Chunks --> ChunkEmbed["chunks 向量化"]
    ChunkEmbed --> ChromaC[("Chroma
chunks 向量库")] Chunks --> Extract["LLM 抽实体和关系"] Extract --> Graph[("Neo4j
图谱: Entity + RELATED_TO")] Graph --> Louvain["Louvain 社区检测"] Louvain --> CommunitySum["LLM 生成社区摘要"] CommunitySum --> Community[("Neo4j
Community 节点")] Graph --> EntityEmbed["实体向量化
(name + type)"] EntityEmbed --> ChromaE[("Chroma
实体向量库")]

第 2 步的实体向量库是查询阶段的入口。
它把所有实体的 name + type 做 embedding 存进向量库,相当于把每个实体变成一个可被相似度检索的向量。

7.2 查询阶段
#

查询时的流程:

flowchart TD
Q["问题"]
Q --> Embed["问题向量化"]
Embed --> Search["实体向量库检索
top-k 种子实体"] Search --> Seeds["种子实体"] Seeds --> Walk["图谱 N 跳遍历"] Walk --> Triples["相关三元组"] Triples --> Reverse["按 chunk_id
反查原文 chunks"] Seeds --> CommunityRead["读取所属社区报告"] Seeds --> CTX["context"] Triples --> CTX Reverse --> CTX CommunityRead --> CTX CTX --> LLM["LLM 生成答案"]

注意流程里没有"LLM 抽问题实体 + 实体链接"那两步。
入库时已经把所有实体向量化了,查询时直接用问题向量在实体库里做相似度检索,就能找到种子实体。
这等价于"LLM 抽实体 + 实体链接"两步合并,但只需要一次 embedding 调用,比 LLM 推理便宜得多,也天然处理了"陆 CTO"、“那个搞 NebulaCore 的人"这种说法模糊的问题。

例如用户问:

陆向晚是谁?

问题向量化后,在实体向量库里命中:

陆向晚 (人物)     ← 余弦相似度最高
林夏 (人物)
周屿 (人物)

这几个就是种子实体。
后续沿着每个种子在图谱里做 N 跳遍历,得到相关三元组,再按三元组里的 chunk_id 反查回原文。
N 通常取 1 或 2。N=3 之后容易触发邻居爆炸,反而把无关信息塞进上下文。

7.3 最终上下文
#

最终给 LLM 的 context 由四部分组成:

  • 种子实体与邻居:种子实体及其类型、所属社区、一跳邻居
  • 实体间关系:种子实体相关的三元组
  • 相关社区背景:种子实体所属社区的摘要
  • 原文证据:三元组涉及的原文 chunk

一个具体的上下文长这样:

【种子实体与邻居】
1. 陆向晚 (人物) [community=0]
   邻居: 林夏, 周屿, 星海科技, NebulaCore, 墨弦科技

【实体间关系】
1. 陆向晚 -[co_founded]-> 星海科技
   证据: 由林夏、陆向晚、周屿三人在杭州联合创办
2. 陆向晚 -[serves_as]-> CTO
3. 陆向晚 -[creates]-> NebulaCore
4. 陆向晚 -[leaves]-> 星海科技

【相关社区背景】
1. 星海核心团队 (cid=0)
   摘要: 这个社区由星海科技的三位创始人和早期产品构成...
   关键点:
   - 林夏、陆向晚、周屿三人共同创立星海科技
   - 陆向晚是 NebulaCore 的核心设计者
   - 陆向晚在 2023 年离开公司创办墨弦科技

【原文证据】
chunk_3:
星海科技成立于 2018 年春天,由林夏、陆向晚、周屿三人在杭州联合创办...

这种结构的好处是,LLM 不只看到原文片段,也能看到结构化关系,还能拿到社区层面的全局背景。
每条关系都能在原文里找到出处,避免变成孤立断言。

8 适用场景
#

GraphRAG 并不是所有场景都比普通 RAG 更合适。

8.1 适合使用 GraphRAG 的场景
#

  • 文档里有大量实体和关系
  • 需要跨文档分析
  • 经常需要全局总结
  • 问题涉及多跳关系
  • 希望回答时能解释实体之间的联系

这类场景中,问题本身通常不只是“哪段文本最相关”,而是“这些信息之间如何组织”。

8.2 不太适合使用 GraphRAG 的场景
#

  • 简单 FAQ
  • 文档规模很小
  • 问题大多是局部事实查询
  • 数据更新非常频繁,重建图谱成本过高

如果普通 RAG 已经能稳定召回答案,就没有必要为了引入图结构而增加额外复杂度。

Yu Yantao
Author
Yu Yantao
Software Engineer