微软GraphRAG理论与实践全面解析
类型:热点整理2026-06-26
今年,微软正式开源了其自研的 GraphRAG 技术,这一举动在业内引发了广泛关注。开源不到半年,该项目在 GitHub 上的 Star 数已突破 1 8 万,增长势头十分强劲。 接下来,我们将深入剖析微软 GraphRAG 的原理,并结合源码,看看在实际运行时能达到怎样的效果。 微软 GraphR
今年,微软正式开源了其自研的 GraphRAG 技术,这一举动在业内引发了广泛关注。开源不到半年,该项目在 GitHub 上的 Star 数已突破 1.8 万,增长势头十分强劲。

接下来,我们将深入剖析微软 GraphRAG 的原理,并结合源码,看看在实际运行时能达到怎样的效果。
微软 GraphRAG 的原理
其实,它的原理并不复杂,下面这张图就能清晰展示。整体流程依然是从多个原始知识文档构建知识图谱起步——这部分与我们之前介绍过的各类 GraphRAG 实现大同小异。关键差异在于:利用社区检测算法(例如 Leiden 算法)将知识图谱划分为多个社区或簇,然后让大语言模型为这些社区生成自然语言摘要。当面对需要总结性回答的全局问题时,先在社区内部执行查询以获取中间结果,最终汇总得出全局答案。

简而言之,与以往 RAG 结合知识图谱的方式相比,微软 GraphRAG 最大的创新点就是引入了“社区”概念。所谓社区,就是围绕某一主题的一组紧密关联的实体和关系。举个例子——假如存在一个“三国演义”社区,那么它内部会关联众多诸侯国、主公、武将、谋士、事件等实体以及它们之间的关系。
从实现流程上看,微软 GraphRAG 仍然继承了传统 RAG 的两大环节:索引阶段与查询阶段。查询阶段相对简单,真正复杂的部分在于索引阶段。
索引阶段的主要流程如下:
1. **文本块拆分**:将原始文档拆分为多个文本块,这与经典 RAG 的处理方式完全相同。
2. **实体与关系提取**:借助大语言模型对文本块进行分析,提取出实体和关系——这与普通 Graph RAG 也基本一致。
3. **生成实体与关系摘要**:为每个提取出的实体和关系生成描述性信息。这一步是它区别于普通 Graph RAG 的关键。这些描述信息会作为属性存储到实体/关系的图节点中。好处是能够利用嵌入向量更精准地检索这些实体和关系;坏处也很明显——由于需要为大量实体和关系生成描述,必须频繁调用大模型,时间和 API 开销可能都非常高。
4. **检测与识别社区**:使用社区检测算法在图结构中划分出多个社区。
5. **生成社区摘要**:再让大语言模型为每个社区生成摘要信息,用于理解数据集的全局主题结构与语义。这正是微软 GraphRAG 的核心价值所在,也是回答全局性问题的关键。
接下来看检索查询阶段。微软 GraphRAG 提供了两种查询模式:local 模式与 global 模式。local 模式通常用于针对具体事实的问题;global 模式则用于支持依赖高层语义理解的全局性问题。

上图展示的是 local 模式的流程。其核心思路是:将相关的知识图谱结构化信息与原始文档中的非结构化数据相结合,拼凑出一份用于增强生成的上下文,然后交给大语言模型回答。这种模式特别适合回答关于特定事实的问题,比如“某个实体的信息以及它与其他实体的关系”。具体过程如下:
1. 查询时,先从知识图谱中找出与输入问题及对话历史最相关的实体(即 Neo4j 中标签为 `__Entity__` 的节点)。这一步主要依靠实体节点描述信息(description)的嵌入向量来实现。
2. 从这些实体出发,继续获取更多相关信息,例如关联的原始文本块、关联的社区、关联的实体和关系。然后对获取到的信息进行排序、筛选,最终形成参考上下文。
3. 将上下文和原始问题一起喂给大语言模型,配合提示模板,生成最终响应。
再看 global 模式。它的架构相对简单,背后是分布式计算中常见的 Map-Reduce 思路。可以概括为:
1. **MAP 过程**:根据用户问题与对话历史,去指定层次(community_level)的所有社区报告中查询。将这些报告分成多个批次,生成带评分的中间响应(RIR)。评分用于表示该观点的重要程度。
2. **Reduce 过程**:对中间响应进行排序,挑选出最重要的观点汇总成参考上下文,最后交给大语言模型生成最终结果。
3. global 模式存在一个问题:响应质量会受到输入的社区级别参数影响。如果层次设置得过低,报告会较为详细,响应可能更全面,但耗时与模型成本也会相应增加。实际使用中需要根据场景权衡。
微软 GraphRAG 实战
现在我们使用一份真实文档来构建基于微软 GraphRAG 的 demo 应用。这里重点聚焦索引阶段。先准备一份白话文三国演义片段的文本文件作为测试数据。
第一步,安装并熟悉微软 GraphRAG
使用 pip 安装 graphrag 库,建议在虚拟环境中操作。然后进行初始化,这里将应用目录命名为 msgraphrag:
python -m graphrag.index --init --root ./msgraphrag
初始化之后,会在指定目录下生成基础文件和目录结构。重要的包括:
- `input` 目录:存放输入原始文档(txt 或 csv),测试文件就放在这里
- `.env` 与 `settings.yaml` 配置文件:可以在这些文件中修改所有配置。默认情况下只修改 LLM 相关的配置即可。注意目前只支持 OpenAI 或 Azure OpenAI 的模型
- `prompts` 目录:里面存放了自动生成的 LLM 提示模板文件,共四个,会在流程的不同阶段使用:
- `entity_extraction`:从自然语言文本中提取实体和关系
- `summarize_descriptions`:为实体和关系生成描述性文本
- `community_report`:为社区生成摘要等报告信息
虽然可以使用初始化时生成的默认提示模板,但强烈建议使用 GraphRAG 提供的命令来创建自适应提示模板——它会提取输入数据的信息,再借助大语言模型分析并生成更有针对性的模板。
第二步,创建索引
运行下面命令创建自适应提示模板(更多参数请参考官方文档):
python -m graphrag.prompt_tune --language Chinese
执行成功后,查看 `prompts` 目录下新生成的模板文件内容。
从最终输出的信息中可以了解基本处理过程:从拆分输入文本开始,到提取实体与关系、生成摘要信息、构建内存中的图结构、从图中识别社区、创建社区报告、创建图中的文本块节点和文档节点等。
第三步,将索引集成到 Neo4j
完成索引后,GraphRAG 默认会将构建整个知识图谱所需的数据持久化到 `output` 目录下,以 parquet 格式文件存放。
parquet 文件很容易通过 pandas 库读取为 DataFrame 表,因此了解结构后,就可以使用 Cypher 语句导入到 Neo4j 图数据库的节点和关系中。
使用官方提供的笔记本文件在本地运行,即可成功将上面构建的知识图谱数据全部导入到 Neo4j。
第四步,检索查询
使用上面介绍的 local 模式进行检索。首先创建一个检索相关实体的向量索引。
由于需要根据用户问题从知识图谱所有节点中检索最相关的实体(导入时设置的标签为 `__Entity__`),这就需要用到实体节点的一个属性:`description_embedding`,即节点描述信息的嵌入向量。
因此要在 `description_embedding` 上创建向量索引,并基于该索引来检索实体。使用以下 Cypher 语句在 Neo4j 创建索引:
CREATE VECTOR INDEX entity_index IF NOT EXISTS FOR (e:__Entity__) ON e.description_embedding
OPTIONS {indexConfig: { `vector.dimensions`: 1536, `vector.similarity_function`: 'cosine'}}
然后提取更多相关信息。在检索出的多个实体基础上,进一步检索关联信息,包括关联的文本块、社区报告、内部关系、外部关系等,将这些信息组装成上下文,用于后续生成。
这里可以利用 Neo4j 的 Vector 组件的一个参数 `retrieval_query` 来实现这个过程。该参数的作用是自定义一段 Cypher 代码片段,它会追加到默认的 Neo4j 向量检索语句后面一起执行,最后必须返回 `text`、`score`、`metadata` 三个字段,以便 Langchain 构建检索返回对象。
现在定义一个后续处理的代码片段,在检索出的 node(即关联的实体)基础上进一步检索其他关联信息:
lc_retrieval_query = """
//接收向量检索输出的node,在此基础上进一步检索
WITH collect(node) as nodes
//查找最相关的文本块,输出text属性
WITH
collect {
UNWIND nodes as n
MATCH (n)<-[:HAS_ENTITY]->(c:__Chunk__)
WITH c, count(distinct n) as freq
RETURN c.text AS chunkText
ORDER BY freq DESC
LIMIT $topChunks
} AS text_mapping,
//查找最相关的社区,输出summary摘要(如果没有weight,用cypher设定)
collect {
UNWIND nodes as n
MATCH (n)-[:IN_COMMUNITY]->(c:__Community__)
WITH c, c.rank as rank, c.weight AS weight
RETURN c.summary
ORDER BY rank, weight DESC
LIMIT $topCommunities
} AS report_mapping,
//查找最相关的其他实体(nodes外部),输出描述
collect {
UNWIND nodes as n
MATCH (n)-[r:RELATED]-(m)
WHERE NOT m IN nodes
RETURN r.description AS descriptionText
ORDER BY r.rank, r.weight DESC
LIMIT $topOutsideRels
} as outsideRels,
//查找最相关的其他实体(nodes内部),输出描述
collect {
UNWIND nodes as n
MATCH (n)-[r:RELATED]-(m)
WHERE m IN nodes
RETURN r.description AS descriptionText
ORDER BY r.rank, r.weight DESC
LIMIT $topInsideRels
} as insideRels,
//输出这些实体本身的描述
collect {
UNWIND nodes as n
RETURN n.description AS descriptionText
} as entities
//返回text,score,metadata三个字段
RETURN {Chunks: text_mapping, Reports: report_mapping,
Relationships: outsideRels + insideRels,
Entities: entities} AS text, 1.0 AS score, {source:''} AS metadata
"""
这段 Cypher 语句虽然较长,但逻辑并不复杂:对向量检索输出的 node 进行收集,然后检索更多相关信息(社区报告、其他实体、关系等),最后合并输出。注意必须输出 `text`、`score`、`metadata` 三个属性,这是 Langchain 构建输出对象的要求。
剩下的工作就很简单了,将这个片段用 `retrieval_query` 参数传入即可:
#创建neo4j向量存储对象,注意传入retrieval_query参数
lc_vector = Neo4jVector.from_existing_index(
text_embedder,
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
index_name='entity_index',
retrieval_query=lc_retrieval_query,
)
#chain,并调用获得响应。此处可参考Langchain文档学习
chain = RetrievalQAWithSourcesChain.from_chain_type(
llm,
chain_type="stuff",
retriever=lc_vector.as_retriever(search_kwargs={"params":{
"topChunks": topChunks,
"topCommunities": topCommunities,
"topOutsideRels": topOutsideRels,
"topInsideRels": topInsideRels,
}})
)
response = chain.invoke(
{"question": "周瑜和孙权有什么关系?"},
return_only_outputs=True,
)
print(response['answer'])
如果一切顺利,应该能看到类似这样的输出:
周瑜是孙策的结拜兄弟,在孙策去世后,辅佐孙权在赤壁之战中击破曹操。
当然,在实际使用中,还可以根据自身需求进一步优化检索召回策略——例如查询重写、查询扩展、多级索引、Rerank 模型等方法,这些在《RAG实战篇系列》中有详细介绍。
除了 local 模式的查询,global 模式也可以自定义实现。如果说 local 模式的关键在于如何召回相关上下文,那么 global 模式的关键则在于 map 和 reduce 过程的提示模板。感兴趣的朋友可以查阅微软 GraphRAG 源代码中的提示模板,自行实现一遍。