← 返回上一页

tech

GraphRAG 到底解决什么:先把文档变成关系网,再回答问题

从“为什么普通 RAG 找不全答案”切入,讲清 Microsoft GraphRAG 的索引、知识图谱、社区摘要、四种查询方式、数据存储,以及它没有内置的检索优化。

假设公司积累了一万份会议纪要、项目周报和供应商邮件。现在问:

项目 A 为什么延期?涉及哪些供应商?其他项目有没有相同风险?

答案可能不在同一份文档里。周报只写了项目 A 等待电池,邮件说电池厂依赖某家芯片公司,另一份纪要又提到项目 B 使用同款电池。普通 RAG 能找到与“项目 A 延期”相似的几段文字,却不一定顺着供应链把这些分散的事实串起来;如果问“所有项目有哪些共同风险”,问题本身甚至没有足够明确的关键词,向量检索很难知道应该取哪几段。

本文分析的对象,是 Microsoft 开源的 GraphRAG 仓库及其发布到 PyPI 的 graphrag Python 包。下文所说的 GraphRAG,均指这套具体实现,而不是泛指所有“知识图谱 + RAG”方案。代码行为以仓库中的 3.1.2 版本为准。

Microsoft GraphRAG 想解决的,就是这种跨文档关联和全局归纳问题。 它先用 LLM 从原始文本中抽取实体与关系,组成知识图谱;再用图算法发现关系紧密的社区,为每个社区预先生成摘要。查询时,它可以从某个实体向外找证据,也可以在社区摘要之上回答整个资料库的问题。

这不是一种新模型,也不需要训练模型参数。它是一个 Python 数据处理与查询工具包:模型负责抽取和总结,图算法负责分组,检索代码负责选择上下文。

Microsoft 在 GraphRAG 仓库的 README 中把它定位为研究项目,并说明项目已经基本进入维护状态,主要处理缺陷、依赖更新和安全问题。它适合直接使用和二次集成,但不是带用户、权限、上传页面和聊天记录的完整知识库产品。


普通 RAG 为什么不总够用

传统 RAG 的主干很短:

文档切块 → 生成向量 → 用问题检索相似文本块 → LLM 根据文本块回答

它擅长定位具体事实,例如“退款期限是多少”“某个接口如何配置”。困难通常出现在两类问题上:

  • 关系问题:某个人、公司、项目和其他对象有什么联系?影响是怎样传递的?
  • 全局问题:整个资料库有哪些主题、共同风险或主要观点?

向量检索衡量的是语义相似度,不等于事实链完整度。查询“项目 A 为什么延期”时,一段只写“芯片厂减产”的文字可能并不像问题,却恰好是原因链的重要一环。查询“资料里最重要的五个主题”时,也不存在一段天然与这个问题最相似的原文。

GraphRAG 的办法是把这些关系提前整理出来,并把全库内容预先压缩成不同层级的社区报告。

建索引:先把文档整理成图和摘要

Microsoft GraphRAG 3.1.2 的标准索引流程,可以简化成下面这张图:

flowchart TD
  docs[原始文档] --> chunks[按 token 切成文本块]
  chunks --> extract[LLM 逐块抽取实体与关系]
  extract --> merge[合并重复实体与关系]
  merge --> graph[形成知识图谱]
  graph --> cluster[Leiden 分层社区发现]
  cluster --> reports[LLM 生成社区报告]
  chunks --> embed[生成文本块向量]
  merge --> embed
  reports --> embed
  embed --> store[(向量存储)]
  chunks --> tables[(结构化表)]
  merge --> tables
  cluster --> tables
  reports --> tables

第一步:文档切块

长文档不能整份交给模型抽取。GraphRAG 3.1.2 默认按 token 切块,配置是:

chunking:
  type: tokens
  size: 1200
  overlap: 100

size: 1200 表示每块最多 1200 token;overlap: 100 表示相邻块重复 100 token,减少一句话刚好被截断造成的信息损失。切块结果成为 text_units。

块越小,单次抽取越聚焦,但模型调用数量会增加,也更容易把跨段关系切开;块越大,上下文更完整,噪声和单次调用成本也会增加。修改配置后需要重新建立索引,旧文本块不会自动变化。

第二步:逐块抽取实体和关系

假设原文是:

星河公司的项目 A 使用远航公司供应的电池。远航公司的生产依赖海岳公司的芯片。

LLM 可能抽取出三个组织或项目实体,以及两条关系:

项目 A → 使用电池 → 远航公司
远航公司 → 依赖芯片 → 海岳公司

不同文本块可以并发处理。抽取完成后,系统按名称和类型合并重复实体,按关系两端合并重复关系,同时保留来源文本块 ID。这样一条图关系仍能追溯到原文。

这里有一个无法绕开的质量边界:图谱是 LLM 从文本中生成的,不是天然正确的数据。 别名可能没有合并,同名对象可能被误合并,隐含关系也可能漏掉。提示词、实体类型、切块策略和原始数据质量都会影响结果。

第三步:发现社区并提前总结

有了实体和关系,系统会把图当成无向加权网络,使用分层 Leiden 算法寻找连接紧密的实体群。例如项目、供应商、零件和负责人频繁互相关联,就可能被归入一个社区;大的社区还可以继续拆成小社区。

随后,LLM 为每个社区生成标题、摘要、关键发现、完整报告和重要度评分。可以把它理解成:

系统先为整个资料库整理出多级目录,再为每个章节写好内容提要。

这一步使 Global Search 不必在提问时从随机找到的几段原文推测全局,而可以基于覆盖全库不同区域的报告进行归纳。代价是建索引时会产生较多模型调用,而且摘要一定会压缩和丢失细节。

索引数据由谁生成,存在哪里

这些表和向量不是用户手工创建的。运行 graphrag index 后,Microsoft GraphRAG 的索引流水线会自动切块、抽取、汇总并写入存储;用户只需要准备输入文档、配置模型和存储位置。GraphRAG 默认不要求 Neo4j,图结构可以直接表示成实体表和关系表:实体是一行节点,关系是一行边。

表 可以把它理解成 查询时的作用
documents 原始文档档案 保存整份内容和来源
text_units 带编号的原文摘录 给答案提供原始证据
entities 人物、公司、项目等对象名单 作为图节点和 Local Search 的入口
relationships 对象之间的关系清单 描述谁依赖谁、谁影响谁,是图的边
communities 联系紧密的对象分组 建立多层主题结构
community_reports 每个分组的预生成报告 为 Global 和 DRIFT 提供全局背景
covariates 可选的声明或主张 保存主体、客体、状态和来源等扩展信息

例如,entities 中可能有“项目 A”和“远航公司”;relationships 中则有一行 项目 A → 远航公司,描述项目使用其电池,并记录这条关系来自哪些 text_units。这些 ID 关联的意义,是让系统既能沿图找相关内容,也能返回原始证据。

在 graphrag init 生成的默认配置中,结构化结果写入项目根目录的 output/,例如 output/entities.parquet、output/relationships.parquet 和 output/community_reports.parquet;向量数据库 URI 默认是 output/lancedb。这些目录和文件由 GraphRAG 自动创建,不需要用户先建表。

GraphRAG 3.1.2 默认会生成三类向量:

向量索引 向量化的内容 主要使用者
text_unit_text 原文文本块 Basic Search
entity_description 实体名称与描述 Local Search 的实体定位
community_full_content 社区报告全文 Global / DRIFT 的社区检索

关系没有单独的关系向量索引。 Local Search 先用实体向量找到入口,再从关系表中按图连接、关系数量、权重或其他排名字段筛选关系;它不是把每条关系单独做 Embedding 后再相似度检索。

默认情况下,结构化表写成 Parquet 文件,向量写入 LanceDB。它们都是本地持久化数据,不需要另起数据库服务。GraphRAG 3.1.2 内置的存储包括:

数据类型 内置选择
结构化表 Parquet、CSV、Cosmos DB
向量索引 LanceDB、Azure AI Search、Cosmos DB

SQLite、MySQL 和 PostgreSQL 没有现成适配器,但并非不能接。项目提供注册自定义 TableProvider 和 VectorStore 的工厂接口。只想把结果用于自己的业务系统,也可以先生成 Parquet,再导入关系数据库;数组和对象字段需要映射为 JSON 或拆成关联表。

如果只用 Basic,可以把 embed_text.names 改成只包含 text_unit_text,避免为实体和社区报告生成不必要的向量;但索引工作流也要相应裁剪,否则仍可能生成不用于 Basic 的结构化结果。用户账号、文档权限、租户隔离和聊天记录不属于 GraphRAG 索引,需要应用层另外保存。

查询时有四条路,不只有 GraphRAG

graphrag 包同时提供 Basic、Local、Global 和 DRIFT。它们不是默认一起执行的四路召回,而是四种可选择的查询模式。

模式 使用的数据 适合的问题
Basic 文本块向量 某个具体事实或原文内容
Local 实体、关系、原文、社区报告 某个对象及其关联关系
Global 某一层级的社区报告 全库主题、趋势和共同风险
DRIFT 社区报告 + 多轮 Local Search 需要从概览逐步深入的复杂问题

Basic:GraphRAG 包里确实有普通 RAG

Basic Search 就是通用的向量 RAG:把用户问题转成向量,从文本块向量库取 Top K,将能放进 token 预算的原文拼入提示词,再让 LLM 回答。

默认 k 为 10,上下文预算为 12000 token。它只有一条文本向量召回路径。GraphRAG 3.1.2 的 Basic Search 没有默认加入:

  • BM25 或全文关键词召回;
  • LLM 查询改写、纠错和同义词扩展;
  • 把问题生成多个问法后分别检索;
  • 专用 Cross-Encoder / Reranker 二次排序;
  • 将关键词、向量和图谱结果统一融合。

因此,Basic 是可用的传统 RAG 基线,不是一套已经做满查询优化的企业级混合检索管线。 在 3.1.2 的 BasicSearchContext 实现中,向量结果先被转换成 ID 集合,再按原始文本块顺序取回;当上下文预算不足时,不应假设最后保留的片段严格维持相似度排名。

Local:从相关实体向外收集证据

Local Search 先用问题向量匹配实体描述,把最相关的实体作为图入口,再围绕这些实体收集多种上下文:

问题 → 匹配相关实体
              ├─ 相邻实体与关系
              ├─ 包含这些实体的原文片段
              ├─ 所属社区的报告
              └─ 可选声明数据
                    ↓
              排序、裁剪、回答

它确实会汇集多种资料,也会根据匹配实体数量、关系属性、社区排名等规则排序。不过,这更准确地说是围绕实体的多来源上下文构建,不是多个独立检索器召回后做统一融合。

Local 对“项目 A 为什么延期”“远航公司影响了哪些项目”更合适。它还可以把有限轮次的历史用户问题拼进当前检索文本,帮助多轮对话,但这仍不是一次独立的 LLM 查询改写。

Global:把社区报告 Map-Reduce

Global Search 面向“整个资料库在讲什么”这类问题。它选择指定层级的社区报告,分批交给 LLM:每批生成带重要度的中间要点,再过滤、汇总成最终回答。

社区报告分批 → 各批生成并评价要点 → 筛选重要要点 → 最终回答

社区层级会直接影响结果。更细的层级包含更多细节,也意味着更多报告、更高延迟和更多模型消耗。这里的评分与筛选发生在中间答案层,不等于对原始文本块使用专门的 Reranker。

DRIFT:先看全局,再自动追问局部

DRIFT 是 Dynamic Reasoning and Inference with Flexible Traversal。它先匹配相关社区报告,生成一个初步回答和若干后续问题,然后通过 Local Search 逐步探索,例如:

“项目延期由什么造成?”
        ↓
初步判断 + 后续问题
        ├─ 项目依赖哪些供应商?
        ├─ 供应商遇到了什么问题?
        └─ 哪些原文能够证明影响?
        ↓
执行局部检索并继续展开
        ↓
整合最终答案

四种模式中,DRIFT 最接近“查询扩展 + 多次检索”。它通常比单次 Local 更全面,也会带来更多模型调用和等待时间。

失败重试、缓存和增量更新

模型请求默认支持重试。初始化生成的配置使用指数退避策略,可以分别给聊天模型和 Embedding 模型指定次数、基础等待时间、最大等待时间和随机抖动:

completion_models:
  default_completion_model:
    retry:
      type: exponential_backoff
      max_retries: 5
      base_delay: 2.0
      max_delay: 30.0
      jitter: true

这里重试的是失败的单次模型请求,不是整个索引任务自动从断点继续。认证错误、参数错误、上下文超长等异常会直接失败;3.1.2 的跳过列表还包含一部分连接和服务异常,所以不能把它理解成“任何网络故障都必然重试”。

GraphRAG 还会缓存成功的模型结果。任务失败后重新运行,如果请求内容和配置一致、缓存仍然存在,之前成功的调用可能被复用,从而减少重复费用。它也提供增量索引流程,用于将新增或变化的数据与旧索引合并;这仍需要应用层决定何时触发,并处理数据版本和发布策略。

实际怎么用

Microsoft GraphRAG 最直接的交付物,是可安装的 graphrag Python 包和同名 CLI。最快的方式是先使用 CLI 初始化配置、建立索引和验证查询:

python -m pip install graphrag
mkdir graphrag-demo
cd graphrag-demo
graphrag init

初始化会生成 settings.yaml、.env、input 和提示词目录。配置模型与密钥,把文本资料放进 input 后执行:

graphrag index
graphrag query "资料中的主要风险是什么?" --method global
graphrag query "项目 A 为什么延期?" --method local
graphrag query "项目 A 为什么延期?" --method basic

集成到自己的后端时,可以调用 graphrag.api 暴露的 build_index、basic_search、local_search、global_search 和 drift_search。查询 API 接收配置及对应的索引表,返回回答与本次使用的上下文数据。

应用与 GraphRAG 的职责边界大致是:

GraphRAG 负责 业务应用负责
文档切块与索引流水线 文件上传、格式清洗和任务调度
实体关系抽取、社区发现与报告 用户、租户和文档权限
向量、局部、全局及 DRIFT 查询 聊天界面、会话保存和审计
索引缓存和增量更新能力 数据版本、发布、监控和成本控制

Microsoft GraphRAG 仓库中的 unified-search-app 是 Streamlit 演示程序,可以加载多个已经生成的 GraphRAG 索引、对比不同查询结果并查看社区报告。它适合观察效果,不是 GraphRAG 的核心存储或生产服务层。

什么时候值得用,什么时候普通 RAG 就够了

GraphRAG 的价值取决于问题,而不只取决于文档数量。

场景 更合适的起点
找一条明确事实、一个配置项 Basic 或其他普通 RAG
查询人物、公司、项目之间的联系 Local
概括全库主题、趋势、共同风险 Global
复杂问题需要先概览再逐步查证 DRIFT
资料很少、关系简单、预算有限 先用普通 RAG

它的主要成本发生在索引阶段:逐块抽取、实体描述汇总、社区报告和向量都会消耗模型资源。更新频繁的数据还需要持续维护索引。图谱抽取与摘要也可能出错,因此重要结论仍应回到原文引用核验。

所以,Microsoft GraphRAG 最准确的定位是:一个以图谱关联和全局归纳为核心、同时附带普通向量 RAG 的知识库索引与查询工具包。 graphrag 包已经实现了这套方法最主要的算法流程,可以直接拿来做原型或嵌入后端;但关键词混合检索、专用重排序、细粒度权限、业务数据库和产品界面,仍然属于接入方要补上的工程层。

没有 GraphRAG,知识库问答照样能做。区别在于,当问题开始要求跨文档串联关系、总结整个资料库时,普通 RAG 往往需要自己继续补查询改写、多路召回、图遍历和分层总结;GraphRAG 把其中“图与社区”这一条路线,做成了一套现成流程。

视频介绍

如果想先通过视频快速了解 GraphRAG,也可以参考这个 B 站介绍:

观看视频:GraphRAG 介绍