GraphRAG 到底基于什么假设,适用范围又窄在哪——彻底搞懂GraphRAG总结篇

50 阅读21分钟

30 秒判断:本篇基于 graphrag 3.2.0,是前面八篇的收束。一句话:GraphRAG 把"压缩"这件本质上取决于目标的事,提前到索引期做完了——这既是它省钱的原因,也是它适用范围比看上去窄得多的原因。

一句话结论:它的前提是三条假设——目标在索引期已知且之后不变、提问方向只有一个、语料够大够松。这三条在我们自己的实测里各自被撞过:换一个领域框定,同一份语料的结构就变(实体 −20%、社区 −38%);"域"这一步本身不可复现;而语料不够大够松时,根层报告会比原文还长。所以它作为方法验证是成功的,作为产品基座还不合格——缺的不是功能,是控制面与方法。

适合谁读:只想读一篇就了解 GraphRAG 能与不能的人;以及正在评估"要不要把它搬进生产"的人。

结构速览:0 节先交代它一般被当成什么;1 节讲我们是怎么核的;2 节讲它的机制骨架与三条假设;3 节讲三条假设各自怎么被撞破;4 节讲三个结构性缺口;5 节把四种检索模式摆在一起算账;6 节讲当成产品还缺什么(质量属性);7 节给适用范围;8 节是结论与我们自己的用法。

证据标记:📄 源码(graphrag 3.2.0)· 📎 官方文档 · ✅ 实测(本系列)· 🤔 推断(会明确标出)。


0. 先讲清楚它一般被当成什么

一句话:GraphRAG 是"用 LLM 把一份语料预先压成一张图,再加一批层次摘要"的那类 RAG——从文本里抽出实体和关系连成图,把图切成层次社区,再给每个社区各写一份 LLM 摘要报告;问答时不再从原文里捞,而是从这批"预压缩"的材料里取。

它一般被这么理解,而这三条也确实是它最常被引用的卖点:

  • 它是对向量 RAG 的补充:向量检索擅长"点"问题(某个人、某件事在哪儿说过),但对"整份语料在讲什么"这类全局问题不行——全局问题没有判别性关键词,按相似度捞不准。GraphRAG 主打的就是这一类。
  • 它是微软那条线:论文《From Local to Global》+ 开源实现 microsoft/graphrag,也是"graph RAG"这一波里最常被当作代表的那个。
  • 最强的两个卖点是成本与覆盖面:论文里根层摘要只占原文 2.3–2.6% (省 token);而在"全面性"与"多样性"两项指标上,它对向量 RAG 大幅领先。

但这两件事都有前置条件。 本篇要做的事,就是把这些条件一条条摊开:那个 2.3–2.6% 是在什么前提下量出来的、"覆盖面"是拿什么当判据的,以及换一份语料、换一个提问方向之后,它们还成不成立。


1. 我们是怎么核的

这不是一篇论文复述。前面八篇的做法都是同一套:读代码(graphrag 3.2.0)+ 少量实测(几份不同的语料、几次真跑),每一条结论都标出处。

这套做法下来,最先被撞掉的不是某个结论,而是一个印象—— "论文都写清楚了,读论文就够" 。实际情况正相反:没写进论文的、文档与实现对不上的、声明与行为不一致的地方,比想象的多。 下面三条就是例子:

  • 论文全文里 "local search" 出现 0 次、"drift" 出现 0 次,而这两个模式是产品里的两条主要检索路径(详见拆解篇);
  • Global 模式那个"取最深层"的机制,自 v2.0.0 起就是空操作,文档却一直写着"选第 N 层"(已提上游 #2573,详见层级参数篇);
  • 官方推荐的 prompt-tune,它的 auto 采样下标是错配的,"用嵌入挑代表性样本"实际退化成"在语料的一个区间里随机挑几块"(详见prompt-tune 篇)。

换句话说:对它做判断,只能立在一手材料上。 下面每一条都能回到代码或实测。


2. 它做了什么:把压缩提前到索引期

先把机制压成一句话:输入文本先被切成块,块喂给 LLM 抽出实体和关系,实体和关系连成一张图,图被切成层次社区,每个社区各写一份 LLM 报告;查询时不再读原文,而是从这批报告(以及图、块)里取材料回答。

这一步的意义在于:它把"压缩"从查询期搬到了索引期。 之后的每一次提问,都只面对"已经被压过一遍的语料"——这就是它能省 token 的原因。

而这次压缩不是中立的,它被一次"领域框定"决定。框定的载体是五件套:

环节它决定了什么
域(domain)把这份语料归到哪个领域
persona这套索引的专家视角——不止"报告作者":它同时被写进实体类型、评分口径、抽取示例、实体描述与报告(见下文)
实体类型表抽图抽什么(抽取范围)
评分口径什么算重要
报告角色报告从谁的视角写

(这五件套由 prompt-tune 一次生成,也可以手写。生成流程与它的坑见 prompt-tune 篇,索引十步流水线与 22 个可配环节见索引篇。)

顺带纠一个常见的简化:persona 不是"报告作者"一个角色。在 prompt-tune 的产物里,这段三四句话的专家描述会被同时写进六个环节——评分口径、实体类型表、抽取示例(它直接当示例生成的 system 提示词)、实体描述摘要、报告角色(以它为输入生成)、以及报告提示词本身。而且它只吃"域"这一个输入、完全不读语料(模板里那句 "and sample text" 是残留)。另有一个同名两义要当心:论文里的 persona 是"提问者角色" (先定"谁会问",再由它生成问题),而 prompt-tune 的 persona 是"撰写者视角"——方向相反,这也正是"拿后者去顶前者"会踩坑的根源。

于是它的三条假设就出来了:① 目标在索引期已知、之后不再变;② 提问者的方向只有一个;③ 语料够大够松,压缩才压得动。下面三节,分别看这三条在实测里长什么样。


3. 三条假设各自的实测结果

3.1 假设一:目标在索引期已知——但压缩比本来就是"目标的函数"

(这里拿"域框定"当目标的代理:目标是"这份语料给谁用、什么算重要",域框定是它在提示词层的落地,后面四件套全由它派生。)

最直接的证据是我们做过的一组对照:同一份语料(51 篇英文新闻、约 12.4 万 token)、同一套模型,只换领域框定,结果:

手写六域工具自动推断的域变化
实体数3,0992,482−20%
社区数735456−38%
根层报告 ÷ 原文37.4%28.7%低 9 个点
全层报告 ÷ 原文457%308%−33%

注意中间那两个换算量:每条实体摊到的原文从 40 个 token 变成 50 个(实体少了,摊薄了),每根社区覆盖从 3,360 涨到 4,605(根社区从 37 个变 27 个)——结构跟着框定变,这才是压缩比变化的来源(详见目标函数篇)。

而论文里那个"根层摘要只占原文 2.3–2.6%"的账,分母其实是 TS(把原文直接做 map-reduce 摘要) ——TS 的成本≈把原文整体扫一遍,所以它也可以读成"÷ 原文"(本系列的报告长度篇就是按后者换算的)。关键是论文没有公布这两份数据集实际用的实体类型表、few-shot 示例、语料描述——也就是说,那个数字按论文本身复现不出来。它的完整限定语应该是: "在目标对齐、且目标与索引期预设一致"的条件下。

3.2 假设二:提问方向只有一个——而"域"这一步本身不可复现

五件套是一条单向派生链:后四件全部由"域"派生(域 → persona → 评分口径 → 类型表 → 报告角色)。所以"一套索引只能服务一个方向"不是设计选择,是结构决定的——换方向就得换整套索引。

更麻烦的是头一环就不稳定。同一份输入、同样的参数:

  • 第一次跑,它答 Sports(下游是一套体育类型表、体育记者 persona);
  • 再跑一次,答 News and Current Events(下游换成六个通用类型);
  • 换个采样范围,答 Technology and Business News。

prompt-tune 那边同样跑五遍,得到五个互不相容的域,类型表两两相似度中位约 0.18,25 个类型里 19 个只出现过一次(详见 prompt-tune 篇)。

这条假设的问题不是"不成立",而是"没人负责" :它把"这份语料属于哪个领域"交给了一个不可复现的推断,而这个推断决定了后面全部结构。

3.3 假设三:语料够大够松——这是一条可以算的账

论文的 2.3–2.6%,放到我们自己的语料上:

语料源 token根层报告 ÷ 原文
14 篇技术笔记10.9 万126%
51 篇英文新闻12.4 万37.4%
论文 · 播客101 万2.6%
论文 · 新闻171 万2.3%

差了一个数量级。往下拆,膨胀来自三个乘数:同一片原文被很多社区各写一遍 / 每份报告有一条长度地板 / 社区切得比材料还碎(详见报告长度篇)。

那个"长度地板"的来源很具体:报告提示词要求写 5–10 条要点、每条多段、还要"全面" 。于是:

  • 上限被撞:根层 47 份报告里 19 份(40.4%)正好写到 10 条,还有一份写到 11 条;
  • 下限被撑:最碎的 L2 层,743 份里 172 份(23.1%)恰好只写 5 条,还有 20 份连 5 条都没凑够;
  • 材料涨 6 倍,报告只涨 2 倍——报告长度和材料量是解耦的(相关系数 0.68–0.90)。

于是可以给出一条能直接用的判据:

根层报告总量 < 原文 ⟺ 根社区数 × 平均报告长度 < 源 token。

而平均报告长度有个约 1.4k token 的地板,所以真正的主控量是"每根社区能分到多少源材料" 。这也回答了"什么语料适合默认配置":长、松散、叙事型(新闻、播客、会议记录)适合;精炼、概念密度高(技术笔记、论文、规格)不适合——后者要么调小要点下限,要么干脆别走这条路。


4. 三个结构性缺口(不是 bug)

前面那些数字是"账"。这一节是"形"——三个不是靠修 bug 能解决的问题。

① 它的"层级"不是抽象层级。 剖开看,社区是按实体数硬拆出来的:L0 中位 87 个实体、L1 中位 10、L2 100% ≤10 个实体;而且父报告从不读子报告(1,185 个社区的"上下文超限"计数全是 0)。所谓"越上层越粗",靠的不是递归抽象,而是材料更多 + 输出带宽固定(那份 5–10 条要点)。也就是说,它是分块树,不是抽象树:父节点不是子节点的概括,只是"更大的框"。

② 它答不了"都有哪些"。 社区是拓扑聚出来的簇,边界取决于你选哪一层(论文自己做过"哪层最适合全局问题"的实验,没有通用答案)。所以它能给出"看起来完整"的清单,给不出"这就是全部"——而"看起来齐全的遗漏"恰恰是最危险的那一类错。

③ 增量索引会把它的成本杠杆拆掉。 它的增量是"追加式"的:新内容独立成图再拼起来,社区与度数都不重算。实测下来,概念层的根节点会一批批地长(9 → +6 → 15),社区会退化成"单文档话题簇",跨批拿到新边的实体里还有 3 处度数停在旧值。

后果很直接:根层社区变多 ⇒ 根层报告总量变大 ⇒ 压缩比变差。而"预汇总"这件事存在的理由,本来就是摊薄全局问答的 token——所以这是一个很典型的交换:用"每次更新更便宜",换掉了"全局成本的那个杠杆" (详见增量篇)。


5. 四种检索模式各自的账

把四种模式摆在一起(机制细节见 Local 与 DRIFT 拆解篇与层级参数篇):

模式取什么材料入口成本结构缺什么
Global≤N 层社区报告的全文,map-reduce由用户填层号(无默认、无自适应)批数随报告总量增长选层没人管;"取最深"早已失效
Local社区报告全文 + 实体关系表 + 原文块(12k 三分:0.5/0.15/0.35)实体描述向量取 20 个,无阈值查询期 1 次嵌入 + 1 次生成阈值与重排;预算不互让;裁剪不记录
DRIFT先 20 份报告播种,再拿生成的问题反复跑 Local播种(HyDE + 5 折并行)上限约 67 次调用没有中途判停;条数写死;去重靠巧合
Basic原文块向量 top-k(k=10)纯相似度1 次生成它就是对照组:无图参与(这不是缺陷,是它的定义)

(顺带把一个容易说错的点讲清:社区报告里确实有 summary(摘要)与 full_content(全文)两栏,但三种检索模式默认喂给模型的都是全文——factory 里 Global 显式设了 use_community_summary=False,Local 的默认也是 False。摘要那一栏只在直接调用、不传这个参数时才会用上(类签名里默认是 True)——又一处"哪一层生效"的坑。第二、三节说"每个社区各写一份报告",指的都是这份全文。)

(还有一个同名的坑:论文里的 "community summary" 指的是"整份社区报告",不是报告里的 summary 字段——本系列前面几篇沿用论文的说法叫"社区摘要",说的也是报告整体。)

三点值得单独说:

  • Global 的"层号"在实现里是空转的:文档让你选一层,代码把 ≤N 的全层都给了你;那个"每层只留最深社区"的机制自 v2.0.0 起失效,且没有任何断言或日志提示。
  • Local 的三源装配是好骨架:社区报告给全局视角、实体关系表给中观结构、原文块给细节证据——这比纯 top-k 向量强。但它把三类材料的配额写死成固定比例,而"该给谁多少"本来取决于问题形状。
  • DRIFT 把"全局起点 → 局部证据 → 汇总"串起来了,方向对;但它没有任何判停(跑满固定轮数),问题条数写死,去重靠"问题字符串哈希"这种巧合。

另外还有一条隐藏路径:Dynamic Community Selection。 它只换第 0 步(选材料)——从"用户填层号拿全层"改成"按问题逐社区打分、逐步下探"。方向是对的(把隐式判断显式化:层号一直是那个判断唯一的出口),但实测未必更省:它多了一个"逐社区打分"的环节,而系统不做"值不值得"的预判;打分是 5 档,实测只有边界那一档可重复,解析失败时的默认值又恰好等于阈值(详见 Dynamic 篇)。


6. 当成产品基座,它还缺什么

把前面八篇里那些"不对的地方"粗分一下,是三类:

类别典型实例
功能缺口(该有的能力没有)DRIFT 没有中途判停;Local 入口没有阈值、没有重排;多轮会话不做查询改写
质量属性(可观测/可配置/可维护/可复现/可标定)失败静默、指标打错层、成本漏记;同一份代码两层默认值;死参数(data_max_tokens/concurrency 在查询侧无人读取);参数全是经验值
语义契约(声明 ≠ 实现,而且没人被通知)"取最深一层"早成空操作;报告长度上限被静默丢掉;官方 notebook 打印出的异常值被当作正常输出

由此可以给一条比"代码糙不糙"更硬的判据,三问:

  1. 声明能不能被机器核对? (文档/提示词说的话,有没有一条可执行的检查在盯着它)
  2. 失败与丢弃能不能被看见? (失败静默、裁剪不记录、成本漏记——这些都出现过)
  3. 参数能不能由部署约束导出? (预算、阈值、条数,是从"模型窗口 − 输出预留 − 输入长度"推出来的,还是拍的)

这不是"某个团队粗心",而是激励结构:论文奖励方法有效,不奖励可观测、可配置、可维护;研究阶段的预算自然全押在功能与指标上。所以"验证假设"和"交付组件"是两件事——一旦把它当产品基座,被推迟的质量属性就变成债务。


7. 所以适用范围在哪

维度在舒适区不在舒适区
提问方向单一、稳定(一个方向一套索引)多类角色共用一套索引;问题形状随会话变化
语料大且松(每根社区材料远大于报告地板)小语料、高密度精炼语料(技术笔记、论文、规格)
问题类型全局摘要型("整体在讲什么")——这是它唯一有优势的类型分两档:答得了但不划算——局部事实题(它有 Local 模式能答,但纯向量检索更便宜、效果通常相当,建索引与装配的成本摊不平);答不了——枚举型("都有哪些",社区是拓扑簇、边界取决于选哪层)与多跳关系查询
报告形态预编译划算:目标预知 + 全局问题反复被问目标未定 + 全局问题只问一次(押错视角、又摊不薄成本)

再往上一层看(🤔 下面这段和那张表都是我们的推断,不是 GraphRAG 的实测结论):公域信息已经被大模型内化了,要补的是私域——而私域有两个特点:没那么大、常常自带组织结构。于是可以按两个变量分格: "结构是语料给的,还是要我们产的" × "问题要事实,还是要图景" 。

场景结构从哪来 / 问题形状该上什么机制
组织级问答语料自带 / 多为局部事实型结构导航 + 内容检索兜底 + 忠实引用;不必预编译,但权限/版本/审计要单做
个人知识库要我们造 / 检索型、局部为多打标(可多值、可重叠)+ 原文回锚 + 人可修正;别追求一次分好
聚焦领域洞察要我们造 / 跨源全局型预编译或"查询期首做再缓存"+ 跨源连接;这才是 GraphRAG 的格

于是全篇收在一句:GraphRAG 的预编译不是错的,它是被当成了通用解法。


8. 结论,以及我们自己的用法

三个结论:

① 作为方法验证,它是成功的:它证明了"把语料预编译成层次摘要,再对摘要做全局问答"这条路走得通。在语料偏小时它依然能答、覆盖也还在,但成本不再有优势——我们那份 14 篇技术笔记的语料上,光根层报告就已经是原文的 126%。

② 作为产品基座,它还不合格:缺的不是功能,是控制面与方法——参数没人能导出、失败看不见、声明没人校验。

③ 它真正的适用域=语料扁平且够大够松 + 目标预知 + 全局问题会被反复问。三条各管一件事:

条件它保证什么不满足会怎样
语料扁平且够大够松两件事:抽取"造结构"不是白干;压缩比压得动语料本来就有组织(文档树、条款编号、工单分类、数据库),那它做的抽取与聚类相当于把按用途写的结构换成按连接密度聚出来的结构,还多花一笔钱;语料精炼、信息密度高(技术笔记、论文、规格)则报告比原文还长——同一量级下,新闻 37.4% vs 技术笔记 126%
目标预知(不是"先建着,以后再说")押注押得对,而且不用重建五件套就是对目标的押注;押错了,压出来的东西对你没用,而且不可逆——改目标要重建索引
全局问题会被反复问(不是只问一次)索引那笔大钱摊得平预编译是一次性大开销、之后每次问答才便宜;全局问题只问一次,这笔账基本是亏的——而一次性的全局问题,用"把原文现做一遍 map-reduce"就够(论文里两者在全面性上无显著差异)

三条可以这么记: "扁平+够大够松"管这份语料适不适合用这种方式造结构;"目标预知"管押注押得对不对;"全局问题反复被问"管押注能不能摊平。

顺带说清"目标不预知"长什么样:结构、报告都会正常生成,但那不是你要的——失败形态是"压出来的东西不对题",而不是报错;修复只能等,先别上预编译(走查询期现算),等目标稳定了再固化。

还有一条更锋利的界线: "目标"这件事没法从语料里推出来。 语料能告诉你它是什么内容,不能告诉你谁会来问、想要什么——这也正是 prompt-tune 那个根本性错位的来源:它试图从语料猜出 persona,而论文评测里的 persona 是提问者角色(先定"谁会问",再由它生成问题),工具产出的 persona 却是撰写者视角。

顺带把一个反直觉的点说透:语料"杂"不是缺点,"密度太高"才是。 类型表抽的是"目标需要的那类东西",多带几个无关主题不妨碍抽取;反而语料越松散,每根社区能分到的材料越多、报告又被上限封住,压缩比会更好看(3.3 节那四行就是这个规律的样本)。要多留一分心的是"无关内容占多少"——它们会让报告里混进对目标没用的部分,只是不会让压缩比变差。

我们自己的用法(也是给要落地的人的顺序):

  • 落地顺序照这个走:角色 → 实体类型 → 语料适配性 → 参数标定 → 验收;先问"谁问、问什么",再定抽什么,最后才谈参数;
  • 动手前先跑一次 "只跑抽取"的预检:抽一小批样本,估出实体数与预计社区数,用 根社区数 × 约 1.4k token vs 源 token 判断默认配置能不能用——别等全量跑完才发现报告比原文还长;
  • 借法一句话:骨架留下,控制面重写——三类来源的划分、层次的思路、map-reduce 的形态都可以借;阈值、预算、判停、对账这些要自己补。

附:核对方式与系列导航

如果你正在用 GraphRAG,欢迎把你那份语料的根社区数 × 平均报告长度 vs 源 token贴到评论区——这张账一算,就知道默认配置在你的语料上成不成立。