游乐游手机版
首页/AI热点日报/热点详情

微软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 源代码中的提示模板,自行实现一遍。
来源:https://www.53ai.com/news/RAG/2024101573819.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。