Skip to content
Published on

Graph RAG 是什么 — 向量 RAG 卡壳的地方,以及它值回成本的那一刻

分享
Authors

引言 — 复习向量 RAG,看看它在哪儿卡壳

RAG(检索增强生成)的标准配方,我已经在生产环境 RAG 模式一文里整理过了。概括起来是这样 — 把文档切成分块,做嵌入放进向量数据库,取出与问题语义最接近的前 k 个分块贴进提示词里。再加上混合检索和重排序,「这份文档里 X 写在哪儿」这类问题,大多用它就能充分解决。

然而这条流水线有一处结构性的盲区。向量检索归根到底是在找「与问题语义相近的片段」,它在答案完整地落在某一个片段里时工作得最好。问题出在答案散落于多个片段、或者压根就没在任何一个片段里写明的时候。

向量 RAG 解不了的两类问题

第一,多跳问题。 「在参与了那些最大规模故障的团队所拥有的其他服务当中,哪些依赖 Postgres?」这个答案,得顺着好几层连接才走得出来 — 故障 → 团队 → 服务 → 依赖。没有哪个分块装得下这整条链。前 k 个取得再好,把散落在不同文档里的环节一个个接起来,这件事是推理,不是检索。

第二,全局「意义建构」(sensemaking)问题。 「贯穿我们全部复盘的反复主题是什么?」GraphRAG 论文举的正是这个例子 — "What are the main themes in the dataset?"。这不是检索问题,而是摘要问题。用论文的说法,这是以查询为中心的摘要(query-focused summarization),答案不在某个特定分块里,而是散布在整个语料库中。你一旦去取前 k 个,这局就已经输了 — 因为 k 个并不等于全体。可要说「把全部内容都塞进上下文」,在 100 万 token 的语料库上又做不到,即便塞得下,也会因为 lost-in-the-middle 而崩掉。

GraphRAG 是什么 — 构建图索引的过程

Microsoft 的 GraphRAG(Edge 等,arXiv:2404.16130)正面瞄准了这两处盲区。核心思路是「在检索之前,先用 LLM 从语料库里把知识图谱预先抽出来」。用论文的说法,LLM 分两个阶段构建图索引 — 先导出实体知识图谱,再为每一组紧密相连的实体预先生成社区摘要。

索引流水线的流程是这样的:

  1. 分块。 把语料库切成称为 TextUnit 的分析单元。(到这一步为止,都和普通 RAG 一样。)
  2. 抽取。 LLM 从每个 TextUnit 里抽出实体、关系,以及关键主张(claim)。比如会不断累积出「支付服务 —(拥有)→ 爱丽丝团队」「支付服务 —(依赖)→ Postgres」这样的三元组。
  3. 构建图。 把抽出的实体作为节点、关系作为边连起来,将整个语料库做成一张知识图谱。
  4. 社区检测。 用 Leiden 算法对图做分层聚类。紧密相连的节点被归入同一个社区,而社区又向上组成更高层的社区,形成层级。(Leiden 是一种改进自 Louvain、能保证「良好连接的社区」的社区检测算法 — Traag 等,2019。)
  5. 社区摘要。 对每个社区,由 LLM 自下而上(bottom-up)生成摘要。「这个社区是关于支付基础设施及其故障历史的」这样的摘要,会在每一层被生成出来。

这里关键的转变在于,把昂贵的理解工作,不放在查询时刻,而是 在索引时预先做完。相当于把理解语料库的成本预付掉。

局部检索 vs 全局检索

这样预先造好的两种结构(图 + 社区摘要),会按问题的类型区别使用。

全局检索(Global Search) 用于前面那种意义建构问题。方式是映射-归约(map-reduce) — 在映射阶段,每份社区摘要各自针对问题生成一个部分回答(并各带一个有用性评分),在归约阶段再把这些部分回答汇总,合成最终回答。照搬论文的原话,就是先用每份社区摘要生成部分响应,再把所有部分响应重新摘要,得出最终响应。对于需要通览整个语料库的问题,通览的不是原始分块,而是预先造好的摘要。

局部检索(Local Search) 用于关于特定实体的问题。它从问题里找出实体,在图上朝其邻居呈扇形展开(fan-out),把相连的关系、文本片段、社区摘要收集起来构成上下文。多跳问题就在这里得解 — 因为沿着图走,散落的环节就接上了。(在此基础上再加入社区语境的 DRIFT 检索,是它的一个变体。)

一句话概括:全局看的是「森林」,局部看的是「树木与它的邻居」。

一个具体例子 — 事故语料库

假设我们把过去一年的数百份故障复盘做了索引。

问题 A (局部 / 多跳): 「参与了支付服务故障的团队所拥有的其他服务有哪些?」
  向量 RAG:       返回几个支付服务的分块 -> 团队 -> 其他服务的连接丢失
  GraphRAG(局部): 支付服务节点 -(参与)-> 团队节点 -(拥有)-> 服务节点
                  沿图走几跳,把答案拼出来

问题 B (全局 / 意义建构): 「去年全部复盘中反复出现的主题是?」
  向量 RAG:       只返回前 k 份复盘 -> 「整体主题」原理上得不到
  GraphRAG(全局): 对社区摘要做映射-归约
                  社区1: 数据库故障转移 / 社区2: 部署回滚 / 社区3: 认证过期
                  -> 归约为「三个反复主题」

问题 C (简单的局部查询): 「ERR_2043 是什么意思?」
  向量 RAG:       返回对应的一个分块 -> 足够(GraphRAG 属于过度)

问题 C 很重要。并不是所有问题都需要图 — 大多数实务问题,依然更接近 C。

诚实的权衡 — 它并不便宜

现在是该诚实的部分了。GraphRAG 不是免费的。

索引成本。 抽取阶段会对语料库的每一个分块都跑一次 LLM(为了拾回漏掉的实体,GraphRAG 会跑好几轮 gleaning pass)。而社区摘要阶段,又会为层级里的每一个社区各跑一次 LLM。也就是说,索引成本会随语料库规模成比例增加,而其中大部分是 LLM 调用。Microsoft 公开的资料没有给出具体金额,所以这里我不会凭空编造数字,但有一点是明确的:这和「只需把文档嵌入一下」的向量 RAG 索引,是数量级不同的工作。

延迟与更新。 索引是批处理作业,在大语料库上要花上几分钟到几小时。更让人头疼的是更新 — 每当文档变化,就得重新构建图和摘要(增量索引是有的,但会增加复杂度),因此它和实时不断变化的语料库并不搭。

查询成本。 全局检索本身也不便宜 — 因为映射-归约要跨越多份社区摘要、多次调用 LLM。局部检索则便宜得多。

对抽取质量的依赖。 图有多好,取决于抽取抽出了多少。实体抽得一团糟,图就一团糟,所以每个领域都得调优抽取提示词。把什么看作实体、哪些关系才有意义,归根结底是领域建模的问题 — 这个话题,我会在把领域知识建模为本体动手构建知识图谱两篇里另行展开。

那么,你该不该用

判断标准可以这样归纳:

用了值回成本的情况

  • 问题是全局型、主题型的 — 「整体主题」、「总体趋势」、「X 与 Y 在整个语料库中如何交织」。
  • 需要跨越多份文档的多跳推理。
  • 语料库相对稳定,且被查询得足够频繁、足够长久,能够摊销掉索引成本。

属于过度的情况

  • 问题大多是局部查询 — 「X 写在哪里」、「这个报错什么意思」。
  • 语料库无时无刻不在变。
  • 预算和延迟的余地都很紧,而混合检索 + 重排序已经通过了你的评估标准。

现实中的生产环境,大多不会二选一,而是混着用。局部查询交给便宜的向量、混合检索去处理,只有真正的意义建构问题才动用图。GraphRAG 自己就带着局部检索模式这一事实,已经道出了它的混合本性 — 这不是图对向量的二分法,而是一条按问题类型来挑工具的光谱。至于实际的实现工具 — 从 microsoft/graphrag 库,到 Neo4j、LlamaIndex 的图索引 — 之间的比较,我会在运行 Graph RAG 的工具一篇里另行展开。

而且生产环境 RAG 模式一文里的原则,在这里同样适用 — 别拿基准测试评估,要拿你自己的查询来评估。论文报告的改进(在全局意义建构问题上,回答的 comprehensiveness 与 diversity 有所提升),是用 LLM 评判(LLM-as-judge)打分、由作者报告的数值,并且是基于 100 万 token 级的数据集。至于在你的语料库、你的问题上能不能拿到同样的收益,那是你得用一个小的评估集亲自去确认的问题。

结语

归纳起来是这样。向量 RAG 擅长答案落在某一个片段里的问题,而在多跳和全局意义建构上会结构性地卡住。GraphRAG 瞄准的正是这两处盲区,把昂贵的理解工作挪到索引时刻 — 用 LLM 抽取知识图谱,用 Leiden 找出社区,预先造好社区摘要,然后全局问题用摘要的映射-归约来回答,局部问题用以实体为中心的图探索来回答。

代价是,索引会大量吃掉 LLM 成本,更新也很麻烦。所以 GraphRAG 不是「更好的 RAG」,而是「解决另一类问题的 RAG」。先从朴素、混合方案起步,等到全局意义建构真的暴露成失败点时 — 那时候再把图搬出来。先有问题,才有工具,不是反过来。

参考资料