首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >LightRAG

LightRAG

原创
作者头像
小龙0-0
发布2026-07-26 22:05:05
发布2026-07-26 22:05:05
610
举报

为什么你的 RAG 答不出「跨文档」问题:GraphRAG 双层检索原理拆解

本文以一张 LightRAG 查询流程图为线索,把 GraphRAG 这一类方案的检索链路逐环拆开,重点解释三件事:图谱在检索中到底承担什么角色、为什么必须把问题拆成两个层次、以及为什么找到实体之后还必须回头去拿原文。


LightRAG
LightRAG

一、先把问题说清楚:朴素 RAG 卡在哪

1.1 朴素 RAG 的形式化

设知识库被切分为文本块(chunk)集合:

C = \{c_1, c_2, \dots, c_n\}

给定用户问题 q ,嵌入模型 E(\cdot) 把文本映射到 \mathbb{R}^d 。朴素 RAG 的检索就是一次 Top-k 近邻搜索:

\mathcal{R}(q) = \underset{c \in C}{\text{Top-}k} \ \ \text{sim}\big(E(q),\, E(c)\big)

其中相似度通常取余弦相似度:

\text{sim}(\mathbf{u}, \mathbf{v}) = \frac{\mathbf{u} \cdot \mathbf{v}}{\| \mathbf{u} \| \, \| \mathbf{v} \|}

然后把 \mathcal{R}(q) 拼进提示词交给大模型生成。整条链路只有一次相似度计算,简单、快、便宜。

1.2 三类它注定答不好的问题

第一类:需要跨文档聚合。

"我们所有微服务里,哪些依赖了那个即将下线的旧版认证组件?"

答案分散在十几份文档里,每份只提到一个服务。任何单个 chunk 与问题的相似度都不高,Top-k 只能捞到最"像"的那几个,剩下的全部丢失。

第二类:需要关系推理。

"支付超时和上个季度那次数据库迁移有关系吗?"

知识库里有一段讲支付服务的告警,有一段讲迁移方案,但没有任何一段话同时提到这两件事。它们之间的联系是"支付服务依赖订单库、订单库是迁移对象"这样一条两跳路径——这条路径不存在于任何一个 chunk 里,它存在于文档之间。

第三类:主题级归纳。

"这批事故报告整体反映出什么系统性问题?"

这需要对全库做归纳,而不是找到某几段。Top-k 天然做不到。

1.3 根因:一个容易被忽略的区别

朴素 RAG 的失效可以归结为一句话:

\text{语义邻近}\ \ne\ \text{逻辑相关}

\text{sim}(E(q), E(c)) 度量的是"这段文字读起来像不像在回答这个问题",而不是"这段文字是否是推出答案所必需的一环"。当答案需要 c_ic_j 联合成立、而两者各自的相似度都排不进 Top-k 时,检索就在第一步失败了——后面的模型再强也救不回来,因为它根本没拿到料

切分策略、重排模型、更大的 k ,都只能在"语义邻近"这个维度上做优化,动不了根因。要动根因,得换一种索引结构。


二、GraphRAG 的核心思路:把隐式关系变成显式结构

GraphRAG 这一类方案的共同做法是:在索引期就用大模型把文本里的实体和关系抽出来,显式地存成一张图。原本散落在文档之间、只存在于读者脑中的关联,被物化成了可以遍历的边。

2.1 图的形式化定义

G = (V, E), \qquad E \subseteq V \times R \times V

V 是实体集合,R 是关系类型集合,每条边是一个三元组 e = (v_i, r, v_j)

关键在于,图里每个元素都挂着两样东西:

元素

描述文本

来源集合

实体 v

d_v:该实体的聚合描述

S_v ⊆ C

关系 e

d_e:该关系的聚合描述

S_e ⊆ C

S_vS_e 就是流程图里反复出现的 source_id:记录"这个实体/这条关系是从哪几个原文块里抽出来的"。

请记住这个 S 它是整套方案能不能落地的关键,第四节会专门讲。

2.2 两条技术路线的分水岭

同样是"建图再检索",业界主要分成两派:

微软 GraphRAG 的路线:建完图之后跑社区检测(Leiden 算法),把图划分成层次化社区,再让大模型为每个社区生成摘要。全局查询时遍历这些社区摘要做 map-reduce 汇总。

  • 优点:主题级归纳能力强,能回答"整体反映什么问题"这类问题
  • 代价:索引期要为每个社区生成摘要,大模型调用量大;每次全局查询都要扫社区摘要,token 开销高;文档增量更新时社区结构变化,往往需要重算

LightRAG 的路线:不做社区检测,改为给实体和关系分别建立向量索引,查询时用双层关键词分别命中,再做图扩展。

  • 优点:查询期大模型调用次数固定(关键词抽取一次 + 生成一次);新增文档只需局部更新图和向量索引
  • 代价:放弃了显式的主题层抽象,纯粹的全库归纳类问题弱于前者

本文这张流程图属于后者。理解它的价值在于:它是成本可控、增量友好的那一派,对个人和中小团队更现实。


三、双层检索:为什么要把一个问题拆成两半

这是整套设计里最巧妙的一步,也是最容易被误解的一步。

3.1 一个侦探的类比

把知识库想象成一堆卷宗,你是接手案子的侦探。你会同时做两件不同性质的事:

  • 查具体的人和物:某个嫌疑人叫什么、他名下有哪些资产、案发当晚在哪。这是的查询,落在具体实体上。
  • 查关系网:谁和谁有资金往来、哪两个组织有隐蔽合作、这类案子通常靠什么手法。这是的查询,落在关系和模式上。

只做第一件事,你会得到一堆孤立事实,拼不出案情;只做第二件事,你有了大方向却拿不出证据。必须两条线同时走,最后合并。

3.2 形式化

用一次大模型调用,把问题拆成两组关键词:

\text{LLM}_{\text{kw}}(q) \longrightarrow \big(K_{\text{low}},\ K_{\text{high}}\big)
  • K_{\text{low}} (低层关键词):具体实体、专有名词、明确的事物。对应"查人查物"。
  • K_{\text{high}} (高层关键词):主题、概念、关系模式。对应"查关系网"。

以前面那个问题为例:

"支付超时和上个季度那次数据库迁移有关系吗?"

抽取结果

K_{\text{low}}

支付服务、超时告警、数据库迁移、订单库

K_high(高层关键词)

故障归因、系统依赖关系、变更影响范围

注意 K_{\text{high}} 里那些词在原文里可能一次都没出现过。它们不是用来做字面匹配的,而是用来在关系向量库里找"语义上属于这类关联"的边。这正是它能发现"支付服务→订单库→迁移项目"这条路径的原因。


四、逐环拆解查询流程

📌 此处插入流程图

下面对着图从上到下走一遍。

4.1 入口:关键词抽取

用户问题关键词抽取提示词 + 系统提示词关键词抽取 LLM低层关键词 K_low + 高层关键词 K_high

这一步是整条链路的唯一分叉点,也是最脆弱的一环:如果关键词抽错,后面所有检索都在错误的方向上做精确计算。

工程上有两个要点:其一,输出必须是结构化的(强制 JSON),否则解析失败会直接击穿整条链路;其二,提示词里应当给出低层与高层的判别标准和示例,否则模型很容易把两类词混在一起——实践中最常见的错误是把所有词都塞进低层。

4.2 本地检索路径(偏实体)

对应图的左侧分支,由 K_{\text{low}} 驱动。

第一步,实体库匹配。 把低层关键词向量化,在实体向量库里取 Top-k

V_q = \underset{v \in V}{\text{Top-}k} \ \ \text{sim}\big(E(K_{\text{low}}),\, E(d_v)\big)

第二步,图谱扩展。 只拿到命中的实体是不够的——答案往往在邻居身上。所以沿边向外走一跳:

\tilde{V}_q = V_q \cup \bigcup_{v \in V_q} N(v)
E_q = \big\{ (v_i, r, v_j) \in E \ :\ v_i \in V_q \ \lor \ v_j \in V_q \big\}

N(v)v 的邻居集合。这一步是 GraphRAG 相对朴素 RAG 的本质增益所在:朴素 RAG 只能看到"像"的东西,而这里能看到"连着"的东西。

⚠️ 但它同时是成本失控的源头。扩展后的规模上界为:

| \tilde{V}_q | \ \le\ k \cdot \big(1 + \bar{d}\big)

\bar{d} 是平均度数。真实知识图谱的度数分布是长尾的——总有几个 hub 节点(比如"用户""系统""公司"这类被反复提及的泛化实体)度数高达成百上千。一旦命中一个 hub,\tilde{V}_q 直接爆炸。必须做度数剪枝或按边权截断,这不是优化项而是必需项。

4.3 全局检索路径(偏关系)

对应图的右侧分支,由 K_{\text{high}} 驱动。结构对称,但起点是而不是点:

E_q' = \underset{e \in E}{\text{Top-}k} \ \ \text{sim}\big(E(K_{\text{high}}),\, E(d_e)\big)

命中关系之后,反向把两端的实体及其邻居拉进来:

\tilde{V}_q' = \bigcup_{(v_i, r, v_j) \in E_q'} \Big( \{v_i, v_j\} \cup N(v_i) \cup N(v_j) \Big)

为什么要单独为关系建一套向量索引? 因为"关系的语义"和"实体的语义"不是一回事。"A 依赖 B"这条边的描述文本,讲的是一种依赖模式;而实体 A 的描述讲的是 A 是什么。当用户问的是"有没有关系""是什么导致了"这类问题,他真正要匹配的是前者。把两者混在一个索引里,关系语义会被实体描述淹没。

4.4 ⭐ 共用逻辑:为什么找到图谱之后还要回头拿原文

这是图中央那个灰色虚线框,也是全文最重要的一节

到这里,两条路径都拿到了一批实体和关系。一个自然的想法是:直接把这些实体描述和关系描述拼成上下文交给模型不就行了?

不行。 原因是:d_vd_e 是索引期由大模型对原文压缩得到的产物。压缩必然丢信息——具体的数字、时间、配置项、人物原话、条件限定,全都可能在归纳过程中被抹掉。如果只给模型看图谱描述,它会基于一份"二手摘要"作答,而且无法察觉自己丢了什么

所以必须走这条回溯链路:

第一步,按 source_id 收集候选池。 把命中的实体和关系各自的来源合并:

\hat{C} = \bigcup_{v \in \tilde{V}_q} S_v \ \cup \ \bigcup_{e \in E_q} S_e

第二步,合并去重。 同一个 chunk 常被多个实体引用,必须去重,否则重复内容会挤占上下文预算。

第三步,用原问题向量做二次精排。 这里有一个极容易写错的细节:

C^* = \underset{c \in \hat{C}}{\text{Top-}m} \ \ \text{sim}\big(E(q),\, E(c)\big), \qquad m \approx 3 \sim 5

注意这一步用的是完整原问题 q 的向量,而不是关键词的向量。

为什么?因为关键词是抽象和裁剪后的产物,用它去筛原文会持续放大第一步的抽取偏差;而此时候选池 \hat{C} 已经被图谱大幅收窄了——从全库几万个 chunk 降到几十个——在这么小的集合上,用信息最完整的原问题做精排,精度最高。

这个设计可以概括为一句话:用图谱做召回,用原问题做精排。 图谱负责"该看哪几页",原问题负责"这几页里哪段最贴题"。两者分工不同,用错向量就失去了各自的优势。

4.5 上下文组装与生成

每条路径最终产出一份结构化上下文,由两层构成:

\text{Ctx} = \underbrace{\text{Desc}\big(\tilde{V}_q,\ E_q\big)}_{\text{图谱层:提供结构与关联}} \ \Big\|\ \underbrace{C^*}_{\text{原文层:提供可信证据}}

这个双层结构不是冗余,而是刻意的分工:

  • 图谱层告诉模型"这些东西之间是什么关系"——这是原文里没有明说的部分
  • 原文层告诉模型"确切的表述是什么"——这是图谱压缩掉的部分

两路上下文合并后(图中的 hybrid 模式),送入回答生成模板,产出最终答案。

值得注意的是:整条链路上,大模型只被调用了两次——一次关键词抽取,一次答案生成。中间所有工作都是向量检索和图遍历,是廉价的确定性计算。这是这套方案成本可控的根本原因。


五、伪代码:把流程还原成实现

代码语言:python
复制
def graph_rag_query(q: str, mode: str = "hybrid") -> str:
    # ① 唯一的分叉点:双层关键词抽取(LLM 调用 1/2)
    kw = extract_keywords(q)          # -> {"low": [...], "high": [...]}

    contexts = []

    # ② 本地路径:偏实体
    if mode in ("local", "hybrid"):
        ents = vec_entities.search(kw["low"], top_k=K)
        ents, rels = expand_neighbors(ents, max_degree=DEGREE_CAP)  # 必须剪枝
        contexts.append(build_ctx(q, ents, rels))

    # ③ 全局路径:偏关系
    if mode in ("global", "hybrid"):
        rels = vec_relations.search(kw["high"], top_k=K)
        ents, rels = expand_from_edges(rels, max_degree=DEGREE_CAP)
        contexts.append(build_ctx(q, ents, rels))

    # ④ 合并两路上下文并生成(LLM 调用 2/2)
    return generate(q, merge(contexts))


def build_ctx(q: str, ents, rels) -> Context:
    """图中央那个灰框:召回用图谱,精排用原问题"""
    # 按 source_id 收集候选池
    pool = {sid for x in list(ents) + list(rels) for sid in x.source_ids}
    chunks = dedup(fetch_chunks(pool))

    # ⚠️ 关键:此处用原问题 q 的向量,不是关键词的向量
    top_chunks = rank_by_similarity(query=q, candidates=chunks, top_m=5)

    return Context(
        graph_layer=describe(ents, rels),   # 结构
        text_layer=top_chunks,              # 证据
    )

六、代价分析:这套方案贵在哪、省在哪

任何方案的取舍都写在成本结构里。

环节

朴素 RAG

社区摘要路线

双层检索路线(本文)

索引期 LLM 调用

实体关系抽取 + 每个社区生成摘要

实体关系抽取

查询期 LLM 调用

1 次(生成)

1 次生成 + 社区摘要 map-reduce

2 次(抽取 + 生成)

查询期检索开销

一次向量搜索

遍历社区摘要

两次向量搜索 + 图遍历

增量更新

只需新增向量

社区结构可能需重算

局部更新图与索引

主题级归纳能力

多跳关系推理

一个粗略的量级判断:设全库 n 个 chunk、| V | 个实体,双层检索的查询期复杂度约为

O\big(k \log | V |\big) \ +\ O\big(k \bar{d}\big) \ +\ O(1)\ \text{次 LLM 调用}

而社区摘要路线的全局查询需要触及社区摘要集合 \mathcal{S} ,token 消耗随 | \mathcal{S} | 线性增长。这是两者成本差异的结构性来源。

⚠️ 具体的 token 数与延迟数据请以各方案论文与官方基准为准,不同数据集差异很大,本文只做结构性对比。


七、什么时候该用,什么时候别用

值得上 GraphRAG 的场景:

  • 问题需要多跳推理("A 和 C 有关系吗",而关联要经过 B)
  • 需要跨文档聚合(答案分散在多份文档,每份只有一块拼图)
  • 领域实体密集且关系明确:故障归因、合规审查、供应链、医疗、法律、代码库理解

不值得的场景:

  • 单点事实查询("接口超时阈值配置是多少")——朴素 RAG 又快又便宜,上图谱纯属自找麻烦
  • 文档量小(几十份)——直接把全文塞进长上下文可能就够了
  • 实体边界模糊的领域(如纯叙事文本、主观评论)——抽取质量上不去,图就是噪声

一条实用判据: 先用朴素 RAG 跑一批真实问题,把失败案例分类。如果失败集中在"需要跨文档/多跳"这一类,上 GraphRAG 有明确收益;如果失败集中在"切分不当""检索到了但答错了",那问题不在索引结构上,建图救不了你。


八、工程落地的五个坑

1. 实体抽取质量决定整个系统的上限。 图建歪了,后面所有检索都在歪的结构上做精确计算。抽取环节的提示词、实体类型定义、消歧规则,值得投入的精力远超大多数人的预期。

2. Hub 节点会让图扩展爆炸。 前面公式里那个 \bar{d} 在长尾分布下毫无参考价值。必须设度数上限、按边权排序截断,或者在索引期就把"用户""系统"这类泛化实体过滤掉。

3. 同一实体的描述会不断累积。 同一个实体在几十份文档里被提到,描述会越拼越长直到塞不进上下文。需要定期做描述合并与摘要,而合并本身又是一次有损压缩——这是个需要主动管理的权衡,不是可以放任的细节。

4. 别忘了 source_id 是可信度的锚。 它不只用来回溯原文,还是做引用溯源的基础。答案里能标出"依据来自哪份文档哪一段",可信度会有质变。这个能力几乎是免费的,不要浪费。

5. 评估必须分类做,不能只看总分。 GraphRAG 的收益高度集中在特定问题类型上。如果只看一个笼统的准确率,多跳能力的提升会被单点查询上的性能退化抵消掉,你会得出"上了图谱没什么用"的错误结论。至少要把测试集按"单点事实 / 跨文档聚合 / 多跳关系 / 主题归纳"分成四类,分别看提升。这是最容易被跳过、也最容易导致错误决策的一步。


九、小结

回到开头那张流程图,整套设计可以压缩成三句话:

  1. 把隐式关系显式化——索引期用大模型把文档里的实体和关系抽成一张可遍历的图,让"文档之间"的信息有了载体。
  2. 把问题拆成两个层次——低层关键词查实体(点),高层关键词查关系(边),两路并行,各自解决对方解决不了的部分。
  3. 用图谱召回,用原问题精排——图谱负责把范围从全库收窄到几十个候选,原问题负责在候选里挑出最贴题的几段原文。图谱给结构,原文给证据。

而它最值得学的地方,恰恰是那个最不起眼的设计:明明已经有了实体描述,却仍然坚持回头去取原文。 这背后是一个很朴素的工程判断——任何经过大模型压缩的中间产物都不该被当作最终依据,因为你无法知道它丢了什么。


参考

  • Guo Z., Xia L., Yu Y., Ao T., Huang C. LightRAG: Simple and Fast Retrieval-Augmented Generation. arXiv:2410.05779
  • Edge D., et al. From Local to Global: A Graph RAG Approach to Query-Focused Summarization. Microsoft Research
  • Liu N. F., et al. Lost in the Middle: How Language Models Use Long Contexts. TACL 2024

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么你的 RAG 答不出「跨文档」问题:GraphRAG 双层检索原理拆解
    • 一、先把问题说清楚:朴素 RAG 卡在哪
      • 1.1 朴素 RAG 的形式化
      • 1.2 三类它注定答不好的问题
      • 1.3 根因:一个容易被忽略的区别
    • 二、GraphRAG 的核心思路:把隐式关系变成显式结构
      • 2.1 图的形式化定义
      • 2.2 两条技术路线的分水岭
    • 三、双层检索:为什么要把一个问题拆成两半
      • 3.1 一个侦探的类比
      • 3.2 形式化
    • 四、逐环拆解查询流程
      • 4.1 入口:关键词抽取
      • 4.2 本地检索路径(偏实体)
      • 4.3 全局检索路径(偏关系)
      • 4.4 ⭐ 共用逻辑:为什么找到图谱之后还要回头拿原文
      • 4.5 上下文组装与生成
    • 五、伪代码:把流程还原成实现
    • 六、代价分析:这套方案贵在哪、省在哪
    • 七、什么时候该用,什么时候别用
    • 八、工程落地的五个坑
    • 九、小结
    • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档