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

微软GraphRAG框架源码深度解读

类型:热点整理2026-05-30
微软开源的GraphRAG是基于知识图谱的检索增强生成系统,通过社区检测将知识图谱分解为模块化社区,利用大模型自下而上生成摘要,再以map-reduce方式汇总为全局答案。索引阶段借助LLM提取实体关系构建图谱,查询阶段支持全局与局部搜索,有效解决传统RAG难以回答高层次概括性问题。

近段时间,微软正式开源了GraphRAG——一个基于知识图谱构建的检索增强生成系统,迅速在技术社区引发了广泛关注。坦白说,我对这个框架关注已久,趁着热度深入研读了源码。本文权当一份学习笔记,记录下对GraphRAG系统架构、核心概念以及主要工作流的理解与总结。

(本次分析的代码对应 commit id 为 a22003c302bf4ffeefec76a09533acaf114ae7bb,更新于2024年7月5日。)

框架概述

在深入代码之前,有必要先厘清GraphRAG试图解决的核心问题。论文一针见血地指出了传统RAG的致命短板:

“然而,RAG在面对针对整个文本语料库的全局性问题时,比如‘这个数据集的核心主题是什么?’,就显得力不从心了。因为这本质上是一个聚焦于查询的总结性任务,而非一个明确的检索任务。”

简单来说,传统RAG难以胜任这种高层次的归纳性问题。GraphRAG给出了明确解法:采用社区检测算法,将整个知识图谱分解为多个模块化的社区,然后让大模型自下而上地为每个社区生成摘要。最后,通过一种“map-reduce”机制,让各社区并行回答查询,最终汇总成一个全局性的答案。

实现方式是什么?

与大多数RAG系统类似,GraphRAG的整个流水线也划分为索引和查询两个阶段。在索引阶段,利用LLM抽取实体、关系和协变量等结构化信息,构建知识图谱;接着运用社区检测技术进行切分,再由LLM生成摘要。到了查询阶段,则针对具体问题,将相关社区的摘要汇总起来,形成全局性的答案。

源码解析

官方文档虽已说明清晰,但要理解实现细节仍需深入源码。项目的整体结构如下:

.
├── cache
├── config
├── emit
├── graph
│ ├── embedding
│ ├── extractors
│ │ ├── claims
│ │ ├── community_reports
│ │ ├── graph
│ │ └── summarize
│ ├── utils
│ └── visualization
├── input
├── llm
├── progress
├── reporting
├── storage
├── text_splitting
├── utils
├── verbs
│ ├── covariates
│ │ └── extract_covariates
│ │ └── strategies
│ │ └── graph_intelligence
│ ├── entities
│ │ ├── extraction
│ │ │ └── strategies
│ │ │ └── graph_intelligence
│ │ └── summarize
│ │ └── strategies
│ │ └── graph_intelligence
│ ├── graph
│ │ ├── clustering
│ │ │ └── strategies
│ │ ├── embed
│ │ │ └── strategies
│ │ ├── layout
│ │ │ └── methods
│ │ ├── merge
│ │ └── report
│ │ └── strategies
│ │ └── graph_intelligence
│ ├── overrides
│ └── text
│ ├── chunk
│ │ └── strategies
│ ├── embed
│ │ └── strategies
│ ├── replace
│ └── translate
│ └── strategies
└── workflows
└── v1

这里面隐藏了大量值得深挖的细节,接下来将结合具体代码逐步展开。

Workflows

程序运行时,控制台会打印出完整的 workflow 列表,这是理解整个索引流水线的关键入口:

⠹ GraphRAG Indexer 
├── Loading Input (InputFileType.text) - 1 files loaded (0 filtered) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00 0:00:00
├── create_base_text_units
├── create_base_extracted_entities
├── create_summarized_entities
├── create_base_entity_graph
├── create_final_entities
├── create_final_nodes
├── create_final_communities
├── join_text_units_to_entity_ids
├── create_final_relationships
├── join_text_units_to_relationship_ids
├── create_final_community_reports
├── create_final_text_units
├── create_base_documents
└── create_final_documents
All workflows completed successfully.

Index

索引阶段是整个项目的核心,流程也最为复杂。启动索引的命令十分简洁:

python -m graphrag.index --init --root ./ragtest
python -m graphrag.index --root ./ragtest

实际调用的是 `graphrag/index/__main__.py` 的主函数,通过 argparse 解析参数后,会转调 `graphrag/index/cli.py` 里的 `index_cli` 函数。

简单梳理一下调用链路(上图中灰色标记的为核心函数):

  • cli.py::index_cli() 函数首先处理用户输入,比如检查是否需要初始化目录(`--init`),这一逻辑由 `cli.py::index_cli()` 执行,主要看配置文件、prompt 模板、`.env` 文件等是否存在,若不存在则创建。
  • 真正执行索引的是内部函数 `cli.py::index_cli()._run_workflow_async()`,其中又涉及两个关键函数:`cli.py::_create_default_config()` 和 `run.py::run_pipeline_with_config()`。

限于篇幅,这里只讨论默认配置的运行流程。理解逻辑后,你可自行修改配置。

  • 默认配置生成逻辑:`cli.py::_create_default_config()` 先检查根目录及 `settings.yaml`,然后调用 `cli.py::_read_config_parameters()` 读取系统配置,例如 LLM、数据分块、缓存、存储等。接下来的操作十分关键:根据当前参数创建一个 pipeline 配置,具体代码在 `create_pipeline_config.py::create_pipeline_config()`,这是整个项目中逻辑最复杂的模块之一。
  • 深入 `create_pipeline_config.py::create_pipeline_config()` 的代码,会发现其核心逻辑如下:
result = PipelineConfig(
root_dir=settings.root_dir,
input=_get_pipeline_input_config(settings),
reporting=_get_reporting_config(settings),
storage=_get_storage_config(settings),
cache=_get_cache_config(settings),
workflows=[
*_document_workflows(settings, embedded_fields),
*_text_unit_workflows(settings, covariates_enabled, embedded_fields),
*_graph_workflows(settings, embedded_fields),
*_community_workflows(settings, covariates_enabled, embedded_fields),
*(_covariate_workflows(settings) if covariates_enabled else []),
],
)

这段代码的意图十分清晰:根据不同的功能模块生成完整的 workflow 序列。需要注意的是,这里并不考虑 workflow 之间的依赖关系,只是单纯地根据 `workflows/v1` 目录下的模板生成一系列 workflow。

  • Pipeline 执行逻辑:`run.py::run_pipeline_with_config()` 先通过 `load_pipeline_config.py::load_pipeline_config()` 加载上一步生成的 pipeline 配置,接着创建目录(如 cache、storage、input、output 等),最后利用 `run.py::run_pipeline()` 依次执行每个 workflow,并返回结果。

`run.py::run_pipeline()` 才是真正的执行者,其核心逻辑包含两部分:

  • 加载 workflows:`workflows/load.py::load_workflows()`,除了常规创建工作流外,还会进行拓扑排序。
  • workflows/load.py::create_workflow():利用现有模板创建不同的工作流。
  • graphlib::topological_sort():根据 workflow 间的依赖关系,计算 DAG 的拓扑排序。
  • 执行过程中,还会调用 `inject_workflow_data_dependencies()` 进行依赖注入,`write_workflow_stats()` 写入统计信息,`emit_workflow_output` 保存输出。真正的 `workflow.run` 操作会在 `write_workflow_stats()` 之前执行。

Workflow

到目前为止,我们实际上还未触及索引阶段的业务逻辑,只是理清了 GraphRAG 内置的这套 pipeline 编排系统。现在以一个具体的 workflow——index/workflows/v1/create_final_entities.py——为例,看看它究竟是怎样运作的。

  • DataShaper:在深入 workflow 之前,有必要先了解 `datashaper`,这是微软开源的一个工作流处理库,内置了许多组件(官方称为 `Verb`)。通俗地讲,datashaper 就像一条流水线,每一步都对输入数据执行一种特定操作,例如裁剪、旋转、缩放。如果还不熟悉,建议先跑一下官方文档里的 demo 程序 examples/single_verb,应该就能明白。从功能上看,它有点像 Prefect。
  • 知识图谱构建:对应的 workflow 是 create_final_entities.py。查看源码可以发现,它依赖于 workflow:create_base_extracted_entities,并定义了 cluster_graphembed_graph 等操作。其中 cluster_graph 采用了 leiden 策略,具体代码在 index/verbs/graph/clustering/cluster_graph.py 中:
from datashaper import TableContainer, VerbCallbacks, VerbInput, progress_iterable, verb

@verb(name="cluster_graph")
def cluster_graph(
input: VerbInput,
callbacks: VerbCallbacks,
strategy: dict[str, Any],
column: str,
to: str,
level_to: str | None = None,
**_kwargs,
) -> TableContainer:

可以看到,这里的 `leiden` 算法实际上来自另一个图算法库 `graspologic`。

  • Pipeline:厘清 workflow 的执行逻辑后,再结合上节提到的编排日志或 artifacts/stats.json 文件,就能整理出完整的索引流水线。官方文档也给出了非常详细的说明,不过根据源码提取出来的 pipeline 似乎仍存在一些差异,有兴趣的朋友欢迎留言探讨。

Query

查询阶段的 pipeline 相对简单。执行查询的命令如下:

# Global search
python -m graphrag.query 
--root ./ragtest 
--method global 
"What are the top themes in this story?"

# Local search
python -m graphrag.query 
--root ./ragtest 
--method local 
"Who is Scrooge, and what are his main relationships?"

这里有 Global/Local 两种搜索模式。生成函数调用关系图后,整体结构会更加清晰。

graphrag/query/__main__.py 中的主函数会根据参数不同,分别路由至 cli::run_local_search()cli::run_global_search()

Global Search

cli::run_global_search() 主要调用 factories.py::get_global_search_engine(),返回一个 GlobalSearch 类的实例。该类和 LocalSearch 类似,均基于工厂模式创建。其核心方法是 structured_search/global_search/search.py::GlobalSearch.asearch(),采用了 map-reduce 策略:先让大模型为每个社区的摘要并行生成答案,然后将所有答案汇总,形成最终结果。作者在相关的 prompts 中给出了非常详细的说明。

正因为这种 map-reduce 机制,Global Search 对 token 的消耗量相当大。

Local Search

与全局搜索类似,cli::run_local_search() 主要调用 factories.py::get_local_search_engine(),返回一个 LocalSearch 类的实例。这里的 asearch() 方法相对简洁,直接根据上下文给出回复。这种模式更接近常规的 RAG 语义检索策略,token 消耗也更少。

与 Global Search 不同,Local 模式综合了 nodes、community_reports、text_units、relationships、entities、covariates 等多种源数据。官方文档对此有详细说明,这里就不再赘述。

一些思考

  • GraphRAG 最核心的价值,在于它一定程度上解决了聚焦于查询的总结性(QFS)任务。以目前了解的情况来看,这应该是首创。此前思路最接近的项目是 RAPTOR,但后者并非基于知识图谱。QFS 与多跳问答是现阶段 RAG 系统难以解决的痛点,然而在数据分析等领域却有广泛应用。尽管当下 GraphRAG 的使用成本仍然较高,但它至少提供了一种可行的解决路径。
  • 相较于一般的知识图谱 RAG 项目,GraphRAG 更令人印象深刻的是它内置的那套完整的工作流编排系统。这在其他 RAG 框架中并不多见,也是值得深挖的方向。基于模板定义好大部分工作流,只提供少部分的配置灵活性,使每一个环节都可控、可追溯,而不是把一切都交给大模型。相比之下,常规的 RAG 部分(如 embedding、retrieval)反而没有太多需要特别讨论的地方,尽管加入了社区检测算法。
  • 在知识图谱的处理颗粒度上,GraphRAG 也做得相当细致,例如社区检测的 Leiden 算法、综合多源数据的 Local Search 等。一个有趣的细节是:项目中的实体抽取完全采用大模型实现,并且对三元组的 schema 没有设置任何约束。作者的逻辑是,反正后续要做相似性聚类,每次抽取的差异对最终结果影响不大。这一点在鲁棒性上确实是很大的提升。
  • 当然,目前的 GraphRAG 也有不少值得改进之处。例如,它创造了一些让人费解的术语(如 Emit、Claim、Verbs、Reporting),同时还“夹带私货”,用了一些微软自家相对小众的库,增加了理解门槛。此外,模块化方面也有待加强,特别是与 OpenAI 相关的部分,耦合度较高。

综合来看,微软这次推出的 GraphRAG 框架确实干货满满,值得花些时间去仔细研读和思考。

Reference

  • Welcome to GraphRAG (microsoft.github.io)
  • GraphRAG: LLM-Derived Knowledge Graphs for RAG (youtube.com)
  • GraphRAG is now on GitHub | Hacker News (ycombinator.com)
  • GraphRAG & Ollama - intelligent Search of local data : r/LocalLLaMA (reddit.com)
  • GraphRAG on narrative private data : r/LocalLLaMA (reddit.com)
来源:https://www.53ai.com/news/RAG/2024071828507.html

相关热点

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

延伸阅读

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