《大模型上下文工程实践》- 第 5 章:检索增强生成
深入理解RAG技术的原理和实现方法
5.1 RAG 基础与原理
5.1.1 RAG 基础概念
检索增强生成(RAG,Retrieval-Augmented Generation)是由 Facebook(现 Meta) AI Research 在 2020 年的一篇论文中出的一个技术,提出的原因是大语言模型(LLM)虽然在各种任务上表现优异,但由于知识存储在参数中,无法及时更新且易出现幻觉(Hallucination);因此引入外部可检索的非参数化记忆,并将检索结果与模型结合,从而提升知识密集型任务的准确性与可追溯性。
简单的人话表述就是,大模型需要外部的信息来帮助决策,提前将文档通过一些手段(分块、向量化等)存起来后,查询的时候可以在这些内容中搜索辅助大模型进行最终的回答,整个流程下来就是 RAG 要做的一个事情。
RAG 能流行是因为其解决了这么几个问题:
- 解决推理使用的是过时的训练语料库:尤其针对一些对时间较为敏感的数据,以及一些个人/企业知识库需要最新的
- 缓解幻觉(Hallucination):RAG 可以极强的缓解幻觉,这个核心还是因为模型基于上下文进行推理的过程可以产生更加可靠的结果
- 通用模型专业化:尤其针对垂直领域时,通用模型权重过于分散,在搭配该领域的知识库后,可以有效提升专业化,提高结果的可靠性
我们采用 Langchain 官方这个教程里的图演示 RAG 是怎么运作的:

文档通过这个流程进行分块、向量化和存储。然后到查询环节:

召回 Top K 的结果,结合提示词给到大模型做最后的输出。下面是一个简单的 Demo:
1 | import logging |
这是一个很简单的例子,我随便虚构了一些大模型不可能“知道”的内容,这样可以避免大模型作弊,然后写死了,运行后输出如下:
1 | 2025-09-21 22:25:21,127 [INFO] 准备文档完成,共 4 条 |
这边我做了一个 Top K 搜索的模拟,实际上是不会打印的,这个简单的 Demo 让我们对 RAG 有一个初步的概念。总体而言,RAG 是为了提高效果的技术,其结合文档检索,提供了合适模型的上下文,成为上下文工程中的核心技术之一。接下去我们来看一下 RAG 的基础架构和流程
5.1.2 架构与工作流程
接下去我们来看看 RAG 相关的架构和流程,这边我画了一张 RAG 架构图:

这是一个比较完整的 RAG 架构图,包含了流程中的一些关键节点,我们不需要马上理解每个环节,后面我们会陆续提到每个环节里的内容。
RAG 的基础架构相对简单,主要分为三个阶段:
- 查询(Query):输入,通常为用户的查询或者问题等
- 检索(Retriever):从相关知识库中获得与用户问题相关性最高的文档(Top K)
- 生成(Generation):根据 Query 和检索得到的文档,生成高质量的回答
下面是一个 RAG 实施的全过程:
- 数据通过合理的分块(chunking),每块分别做向量化(embedding)后存到向量数据库
- 查询进来后,将查询问题也通过同样的方式向量化后,去到向量数据库内做相似性搜索
- 将搜索得到的 top-k 文档块的原始数据拼接后放在上下文中一起发送给大语言模型
- 大语言模型基于响应的数据做最后的结果生成
这样有了原始数据的参考,大模型就有了参照物,最终给出的答案也会更加稳定,避免自由发挥情况下容易产生幻觉或产生过时数据的情况发生。在开始深入 RAG 之前,我们可以先来了解一下检索方式,这有助于我们理解 RAG 里一个很核心的概念,检索。
5.1.3 检索方式
在自然语言处理中有文本检索技术,分为:
- 稀疏文本检索(Sparse Retrieval)
- 稠密文本检索(Dense Retrieval)
在现行的 RAG 语境下,更多是使用了向量化搜索,也就是稠密文本检索的方式。但是随着 RAG 应用的推广和普及,目前越来越多应用中会将两个检索方式结合起来使用,这个在下一节中也会了解到。现在我们先来了解一下这两种检索方式的原理和差异。
稀疏文本检索(Sparse Retrieval)
原理是基于词频(Term Frequency)等显式词项统计信息,使用稀疏向量(Sparse Vector)表示文本,使用向量相似度进行匹配,返回最相关的文档。那么什么是稀疏向量呢?简单说就是大部分维度为 0 的向量。简单举个例子来理解,假设有个词表(vocabulary):
1 | ["apple", "banana", "car", "dog", "elephant"] |
这个词表有 5 个词,对应一个 5 维的向量空间。现在有个文档:
1 | I like banana |
我们用稀疏向量来表示这个文档时,会得到:
1 | [0, 1, 0, 0, 0] |
很直观的可以看到,这是一个 5 维的向量,但是其中大部分的维度都是 0(没出现),只有极少数是非 0(有出现的词)。理论上我们会在这里持续增加词出现的频次,比如
1 | banana banana banana! |
可以得到
1 | [0, 3, 0, 0, 0] |
看着没什么问题,但是这种极致简单的词频统计,会在某些情况下有问题,比如像“the”、“is”、“you”这些词在所有文本中都很多,但它们没啥实际意义。所以出现次数多的词,并不一定重要。为了解决这个问题,我们就需要引入一些方法。常见的方法有:
- TF-IDF(Term Frequency - Inverse Document Frequency):在词频的基础上加入“逆文档频率”因素,降低常见词的权重,提高稀有词的权重。
- BM25:一种改进的 TF-IDF 加权方案,同时考虑了词频饱和、文档长度归一化等因素,广泛应用于现代搜索引擎。
这些方法都基于**倒排索引(Inverted Index)**结构实现高效检索。它们不再简单依赖“词频越高越重要”的假设,而是引入更多统计规律,使得检索系统能更准确地评估“哪些词更关键”。这个也是传统的搜索引擎的基础,像 Google 这类搜索引擎在早期就应用了这类技术去做搜索。另外全文检索里可以经常看到这两个技术,比如 ES 的全文检索就是利用了 BM25 来做的。
可以看出稀疏文本检索的优点就是高效快速,消耗资源少,因此被广泛使用。其缺点就是无法理解一些语义相近但是词不重叠的文本,比如 car 和 automobile 这种,因此也就有了稠密文本检索来解决这个问题
稠密文本检索(Dense Retrieval)
原理是通过神经网络(如 Word2Vec、BERT)将查询和文档分别编码成低维稠密向量(Dense Vector),使用向量相似度(如内积或余弦相似度)进行匹配,返回最相关的文档。那么什么是稠密向量呢?和稀疏向量刚好反过来了:稠密向量是所有维度基本都有值的向量。每一维都用浮点数表示,通常没有“0”或者很少有“0”。
这边的低维是相对于前面稀疏文本里的稀疏向量通常是极高维度的,因为那边的向量维度=词表大小,通常可以词表可以达到几十万甚至百万维,但是在稠密向量里,通常几十维到几千维的程度,所以是低维稠密向量。
举个例子,还是前面这句话:
1 | I love bananas |
我们将其送进一个神经网络模型(如 BERT、DPR 编码器),可以输出得到一个向量,如:
1 | [0.12, -0.08, 0.91, 0.33, ..., 0.04] // 共768维 |
像现在流行的 Embedding 本质上就是这个原理,通过预训练语言模型后,可以通过模型将内容编码为向量,每个向量都是一个语义表示(Semantic Representation),这些向量不是手动构造的,而是模型通过大量文本学习出来的。
我们可以找到很多这种向量可视化的网站或者开源项目,比如 tensorflow 这个展示了 word2vec 的向量在三维空间的表示,可以看两个词的可视化距离(相似度计算其实算的就是在对应维度空间下的两点之间的距离,只不过维度高到人类大脑无法轻易想象,也就是超越人类的认知,没办法像在二维和三维空间下可以轻松计算距离)

另外 vectosphere 这个,也可以同样可视化展示:

回过头来,常见的稠密文本检索方法有下面这些,有兴趣的可以自己去了解一下:
| 方法 | 模型类型 | 核心思路 | 优势场景 | 主要限制 | 计算成本 |
|---|---|---|---|---|---|
| DPR | Bi-Encoder(稠密单向量) | 把 query 和文档各自编码成向量,用相似度匹配 | 快速大规模召回,OpenQA | 语义粒度粗糙,难处理复杂约束 | 低 |
| Contriever | Bi-Encoder(稠密单向量,无监督) | 不依赖标注,用对比学习学通用向量 | 跨领域、无标注场景 | 精度有限,仍是单向量 | 低 |
| Cross-Encoder | Joint Encoder(交叉) | 拼接 query+文档一起输入模型,输出相关性分数 | 精排,语义理解最强 | 不可扩展,每对都要算一次 | 高 |
| ColBERT | Multi-Vector(Late Interaction) | 文档保留 token 向量,query token 按词找匹配 | 精排,兼顾效率与细粒度 | 存储大,对 query 表述敏感 | 中 |
| SPLADE | Sparse+Neural | 输出稀疏向量,结合倒排索引 | 适合搜索引擎,能用现有基础设施 | 稀疏,语义能力有限 | 中 |
我们平时最常见的 RAG 应用就是使用了 Bi-Encoder,因为足够快,而 ReRank 时数量较少,可以利用 Cross-Encoder 来打分。
到这里我们已经知道了稠密文本检索到底是做什么了,在提前向量化资料后,在后续问题来了之后可以将问题也进行向量化,然后通过向量相似度进行搜索,得到最相关的资料,这就是稠密文本检索的过程,能够检索语义相近但词不匹配的文档,并且适合复杂查询、开放域问答、RAG 等应用。
其缺点也相对明显:需要大规模训练,消耗资源大,部署成本高,另外召回的结果可解释性低
融合方法(Hybrid Retrieval)
两者各有优缺点,因此很多系统或者应用场景会将两者进行结合,比如用稀疏检索(如 BM25)结合稠密检索先召回 Top K 文档,再用重排模型(Dense Reranker,如 Cross-Encoder)对结果进行重新排序,重新排序
引用一张我之前发的关于 Bi-Encoder 和 Cross-Encoder:

我们在实际应用中不会因为技术而技术,一定要记住这句话!否则很容易陷入拿着锤子找钉子的尴尬境地(现在其实有不少人就是拿着 AI 找钉子敲)。就比如前面提到的这些,有可能在实际的应用中只是简单的应用向量化去做检索就足够了,也可能复杂到需要结合关系型数据库做常规的数据检索 +ES 做全文检索 + 向量化检索 + 重排技术得到最匹配的结果去做方案。所以应用 AI(Applied AI)的背后就是我们需要去了解每个技术背后的原理,是基于什么背景之下提出来的,以及这个技术目前发展到什么程度了,可以解决什么问题,在某个应用场景下是否合适,这样我们才可以真正做到将 AI 应用在有价值的地方,赋能业务产生真正的商业价值,而不是陷入技术自嗨中。
了解完这个我们对于 RAG 的底层依托的技术已经有了比较清晰的认知了,接下去我们会进一步深入去了解 RAG 相关的技术以及衍生的一些应用方式。
5.2 RAG 进阶
常规的 RAG 相对简单,在实际应用中,我们会在原本的架构之上,去运用一些技术和方法来提高,比如:
- 标量 + 向量:通常 RAG 是将文档分块(Chunk)后向量化(Embedding)入库,然后查询也向量化后到向量数据库进行相似性搜索。如前面提到,实际上还可以结合传统的数据库或者 ES 进行标量数据的匹配检索,最后可以得到标量 + 向量数据。
- 重排(Reranking):不管是单向量还是结合了标量,在送到模型前可以用一些手段对文档进行重新排序,通常我们会使用重排模型对文档再进行评分排序,这样可以选择实际送到模型的文档
- 多跳 RAG:当单跳查询无法满足复杂的查询时,结合多跳是可以达到更好的效果的。
- 图增强 RAG(Graph-RAG):结合图的能力来扩展 RAG 的能力,尤其是在文档处理阶段,可以利用图 + 大模型来细化一些实体和关系,甚至进一步形成社区或领域的形态。
上面只是一部分技术或方法。在技术普及过程,开始会陆续出现体系化的知识,也是为了方便应用以及后来者学习,现在业界也有很多划分方式,比如 Daily Dose of Data Science 这张图:

另外这篇论文里也提供了相应的划分方式:
| 范式(Paradigm) | 关键特性(Key Features) | 优势(Strengths) |
|---|---|---|
| 基础RAG(Naïve RAG) | - 基于关键词的检索(如 TF-IDF、BM25) | - 实现简单易用 - 适合处理基于事实的查询 |
我们引用这篇论文里的一张示意图:

可以较为清楚的看出差别,分类是人为划分的,本质上就是针对基础的 RAG 在各个环节进行优化提升,目的都是为了提高最后输出的效果。
**进阶 RAG(Advanced RAG)就是加入了前处理阶段(Pre-Retrieval)来优化查询,比如查询重写或运用一些策略进行处理。并且加入了后处理阶段(Post-Retrieval)**来优化检索后的文档块,比如重排、压缩或融合等手段,这样在最终给到大模型可以得到更好的结果提升。
**模块化 RAG(Modular RAG)**则是将各种阶段或者功能单独成模块,每个模块是最小单元,可以自由的组合,形成一个类似 workflow 的流程,有点像是玩乐高积木,可以针对不同的业务场景自由组合。本质上里面的技术和方法没有变化,只不过是在工程化上进行了优化,方便不断复用和自由编排。
**图 RAG(Graph RAG)**就是利用了图来辅助处理,万物皆可图,图的能力应用在 RAG 里,使得 RAG 得到了极大的提升,后面我们会在图 RAG 章节里会详细分析加入图能力,RAG 得到的好处和提升。
**智能体 RAG(Agentic RAG)**则是将 RAG 从简单的检索生成扩展成自主的 Agent,可以基于一定的策略动态决策并进行多轮次检索,这个其实是对多跳 RAG 的一种提升,将 AI Agent 的思想融入 RAG。
到这里我们再回过头来看看我们前面的那张架构图:

这里面其实已经体现了很多的东西,我们可以把 RAG 分为:
- 输入:可能有不同的输入方式,主流常见的是从 Chat 进来的问题
- 前处理:检索前作一些前置处理动作,目的是增加召回效果
- 检索:执行检索
- 后处理:对检索的结果进行特定的处理,目的也是增加召回效果
- 生成:给大模型输出最后的结果
- 输出:将结果返回
这个其实就是一个进阶 RAG 的流程了,至于模块化 RAG,其实是将里面的功能模块都单独抽出来形成独立的单元,这样可以重复自由组织编排,而图 RAG 和智能体 RAG 则会在里面多个环节参与。下面我们会针对一些关键的节点和方式展开。
5.2.1 查询重写
在传统的 RAG 里,通常就是将查询通过向量化的手段转成嵌入(embedding),做相似性搜索后给到大模型。这种情况下有明显可见的问题:输入查询无法顺利匹配到文档块。
在实际场景下,用户输入的问题有可能因为过于简化或者表述不当而无法通过相似度搜索匹配到合适的文档块,使得最终的效果不符合预期。面对这个问题,可以应用查询重写来进一步缓解并提升效果。
正如前面提到的,重写策略其实有挺多的,目前主流的有这么几种(更多还是一些类别的划分,实际上在不同的业务场景下还会有不同的策略浮现的,比如一些行业词汇重写、黑白词等等,这边就不过度展开):
- 规范化重写(Canonicalization):将随意、模糊、口语化表达转成标准清晰的问题
- 同义改写(Paraphrasing):增强表达覆盖、抗 embedding 漏召
- 泛化重写(Step-Back Query):提升复杂问题检索效果
- 多查询生成(Multi-query Generation):多视角覆盖、提升召回率
- 问题分解策略(Question Decomposition):将复杂查询拆分为多个子问题,分步检索和推理
规范化重写(Canonicalization)
规范化重写其实就是针对查询问题让大语言模型帮忙进行重写,使得问题更加规范化,这其中有一些不同的手法。我们先来看一个基础的示例:
1 | 周杰伦第一张专辑是什么? |
可以改写成
1 | 周杰伦第一张音乐专辑名称是什么? |
类似这样的规范化重写,可以将一个较为随意的问题转变成更加正式的问题。以便在向量化检索的过程中,可以更好的召回预期的文档块用于最终的结果生成。
同义改写(Paraphrasing)
同义改写的原理也是差不多的,对于不合适的表述,可以进行同义替换改写,使得输入的内容可以更容易匹配到合适的文档块。比如:
1 | # 历史聊天记录 |
历史信息已经出现过相应的人物名,但是在最新的 Query 中却没有重复表述,此时是可以通过重写将用户最新的问题重写成:
1 | 马斯克现在个人财富估值是多少? |
甚至是可以进一步结合前面规范化重写:
1 | 截止2025年7月,马斯克(Elon Musk)的个人净资产估值是多少? |
这样等于是把时间具体化,并且名词也更加规范化表述了。
泛化重写(Step-Back Query)
泛化重写是把具体的问题抽象,将问题覆盖范围扩大了,这样可以扩大检索范围和获取更完整的上下文信息,比如:
1 | 马斯克的出生地是哪里? |
可以改写成:
1 | 马斯克的个人背景和早年经历是什么? |
这种好处不是明显可见的,为什么这么说呢?因为问题被泛化之后,有可能会导致答案也进一步被泛化,当然最终的递送给大语言模型的 Prompt 是可以保留原始的问题的。泛化重写其实是应该结合多跳 RAG 这些技术来发挥更大的作用,这个在后续我们也会涉及到,简单说就是通过泛化先在一个方向上探索,再一步步细化定位到实际想要的结果中。
多查询生成(Multi-query Generation)
这个方式也是应对用户问题表述不清晰或含糊的情况,通过将单一问题生成多个问题的方式,对一个问题提供多个角度,这样可以提高覆盖度,达到更好的检索和结果生成效果。
我们来看下例子:
1 | 周杰伦的第一张专辑是什么? |
多查询重写:
1 | 周杰伦最早发行的专辑是哪一张? |
这样就将一个问题扩展出基于不同角度的多个问题组合,这样可以以较为全面的角度去召回文档块了。
问题分解策略(Question Decomposition)
将一个复杂问题拆解成多个原子问题,使得可以基于多个问题去分别召回文档块,比如:
1 | 周杰伦从出道到现在有哪些重要的音乐成就? |
可以拆解成:
1 | 周杰伦是哪一年出道的? |
这样可以基于不同的问题去做处理了。这里其实还可以结合前面的一些重写策略进一步完善子问题。
另外这种方式通常会结合一些 MapReduce 的思维去做时间,也就是基于不同的原子问题去做文档块的召回,并做不同的结果生成,最终再把所有的结果再进行汇总生成一个最终的结果。后续我们也会提到这块应用,尤其在 Graph RAG 里有很完备的应用示例可以学习。
5.2.2 检索结果重排
重排是提升 RAG 检索效果里很重要的一步,也是目前实际应用中很广泛被采用的一种方式,主要有几种方式:
- 基于打分函数的传统重排方法:BM25,TF-IDF 余弦相似度
- 语义匹配类重排方法:双塔结构(Bi-Encoder),交叉编码器(Cross-Encoder)
- 生成式重排方法:通过 LLM 进行评分和排序
实际使用需要根据业务需求和所有的资源来决定,这边我们来看个例子,LangChain 官方有一个 FlashRank reranker 的例子,采用的是 FlashRank,主要支持 Pointwise(单文档打分),Pairwise(双文档比较,看谁相关度更好)和 Listwise(列表排序,一次对所有文档排序)两种方式
下面是一个基础的 RAG 流程,对文档切分后建立 embedding,然后在对问题做向量化后在里面检索出相似度最高的 20 条文档片段
1 | from langchain_community.document_loaders import TextLoader |
现在来应用一下 FlashRank 做重排,从前面读取 retriever,构建 ContextualCompressionRetriever,里面会使用 FlashrankRerank
1 | from langchain.retrievers import ContextualCompressionRetriever |
对比一下前后的效果:

- Document 1 -> Document 1
- Document 4 -> Document 2
- Document 6 -> Document 3
经过重排后,获取到的 Top 3 文档不一样了
5.2.3 Graph RAG

Graph RAG 是微软在 2024 年推出的一种结构化、分层的检索增强生成(RAG)方法,相较于仅使用纯文本片段进行语义搜索的朴素方法,它更加系统和智能。GraphRAG 的处理流程包括:从原始文本中提取知识图谱、构建社区层级结构、为这些社区生成摘要,并在执行基于 RAG 的任务时充分利用这些结构化信息。下面我们会做一个比较详细的分析
索引阶段
看看架构图可以有个全局的认知

我们来看看标准处理流程:
- 文本处理 (Text Processing)
- 文档处理(Document Processing)
- 图提取(Graph Extraction)
- 图增强(Graph Augmentation)
- 声明提取(Claims Extraction)
- 社区创建(Community Creation)
- 文本单元最终化((Final Text Units)
- 社区报告生成(Community Reports)
- 文本嵌入(Text Embeddings)
文本处理 (Text Processing)
主要接收多种数据输入,然后对输入的数据进行切分(支持按句子或者 token 进行切分),分块得到文本单元 TextUnits。
这步主要是为了方便后续的数据处理,因为后续的处理涉及多轮次的模型调用,以一个合理块大小的处理单元来处理,会更加方便且上下文不容超过,也适合并发调度处理。
文档处理(Document Processing)
将文本处理阶段处理出来的 TextUnits 与原始文档建立引用关系,形成一个结构化的数据表,用于后续一些操作:
- 跟踪每个文档包含哪些 chunk
- 后续社区摘要、图构建等流程中使用
- 统一文档展示和可视化索引
图提取(Graph Extraction)
会包含几个阶段:
- **实体(Entity)和关系(Relationship)**提取
- 图数据进行摘要简化(Graph Summrization)
首先会让大语言模型提取文本里的实体(Entity),以及不同实体间的关系(Relationship),还会附带关系强弱的评分用于计算实体间的关系权重。
这期间会在内存中做一定的合并和更新。比如实体和关系的描述,持续的更新会导致描述膨胀,这种情况下需要再进行一步图摘要,也就是让模型再次帮忙将实体和关系里的描述做总结为单一简介描述
图增强(Graph Augmentation)
图增强里主要是图最终化,也就是将初步提取出来的图数据(实体节点和关系边),经过清洗、加工、标准化并准备好用于下游使用的过程。因为这是图构建的最后阶段:
- 之前:只有基础的实体名称、描述、关系
- 之后:实体具备了向量表示、空间坐标、网络属性等完整特征
简单说就是:
初步提取的基础数据 -> 可用于可视化、推理、检索和分析的结构化图
在对实体最终化流程中,会有这么一些操作和步骤:
- 根据配置决定是否创建向量(embedding)
- 根据配置决定是否对图做 UMAP 或其他布局(layout)方法,生成 2D/3D 坐标用于可视化
- 计算每个实体节点的度数(degree),用于后续分析或排序
- 合并、移除重复、预填充缺失字段、生成唯一 id 等等
UMAP(Uniform Manifold Approximation and Projection)中文名为统一流形近似与投影算法,是一种非线性降维算法,可以用于把高维数据(比如向量嵌入 embedding)映射到二维或三维空间,用于方便可视化或聚类分析。简单说就是:
UMAP 是一种可以把高维“云雾向量”压缩成漂亮二维坐标点的方法,保留结构、方便展示和聚类
关于实体节点的度数(degree),其实是每个节点连接的边的数量,比如:
- Leo –写–> 书
- Leo –开发–> 应用
那么 Leo 这个节点就有两条边,它的 degree 就是 2。那为什么要算 degree 呢?因为在图分析/图机器学习中,degree 是一个很有用的特征值,比如:
- 找到重要节点:高度数可能表示实体在图中很核心
- 控制布局:在图布局中(比如 UMAP 或 Force-directed),高 degree 节点更可能在中心。
- 下游模型特征:在图神经网络中,degree 是常用的节点特征之一
- 图过滤:有时我们只保留 degree>=2 的节点,忽略孤立点(degree=0)。
声明提取(Claims Extraction)
Graph RAG 里面是叫做共变量(Covariates)提取任务,一个道理,就是从文本单元里提取声明(Claims)的过程,并将其转换为结构化数据,供后续图构建或社区摘要使用。
操作主要是让模型针对文本单元里的内容进行声明提取,Prompt 里会包括实体、想找的主张,需要分析的原始内容,最终模型会输出声明主体、涉及对象、声明类型、声明状态(对/错/存疑)、时间范围、描述说明、原始文本这些信息。
社区创建(Community Creation)
这里会借助 Leiden 算法将节点进行社区化,简单说就是把相似、相关的阶段放到统一个社区。社区是指内部连接多,外部连接少的一组节点,类比班级,一个班级内部的同学联系较为紧密,而不同的班级之间的联系相对就少一点,这里班级就是一个社区的概念。另外同一个班级之下还可以分兴趣小组,这样就出现了分层级的社区,也就是某个社区有可能归属于某个父社区。Leiden 算法整体就是在做这么一件事情,我们不展开算法的细节,有兴趣的可以自行了解。
通过构建,最终是可以得到一个这种结构的数据
1 | (level, cluster_id, parent_cluster_id, [node_ids]) |
示例数据
1 | [ |
最终再通过一定的操作来整理聚合社区,只保留每个社区里实体和社区内实体间关系信息,社区之间的关系被忽略,这样最终就得到一份社区数据了,会存放到数据库里,类似
1 | id,human_readable_id,community,parent,children,entity_ids,relationship_ids,text_unit_ids,level,title,period,size |
文本单元最终化((Final Text Units)
这一步主要是针对前面的几个步骤产生的中间数据做最终的聚合关联,也就是将文本单元(TextUnits)与实体(Entities)、关系(Relationships)和声明共变量(Covariates)。关联之后文本单元就拥有了实体 id 列表、关系列表、声明列表。
大概数据如下:
1 | { |
这步的目的是为每个文本单元添加结构化语义(实体、关系、属性),为后续图创建和问答系统打下基础。
社区报告生成(Community Reports)
这步核心目的是基于实体(Entities)、关系(Relationships)、社区(Communities)和声明(Claims),构建每个社区的摘要性报告。
核心的处理步骤有:
- 社区展开:将社区结构展开
- 数据准备:预处理实体、关系和声明数据
- 上下文创建:为每个社区构建上下文
- 摘要生成:生成社区报告
首先就是将原本的社区记录(一条记录是一个社区,包含多个实体和关系)展开,然后合并到实体里,这样实体里就包含了所属社区、层级这些信息了。
然后就是针对实体、关系和声明做相应的结构化数据准备,补充一些缺失的描述,为后续构建 Prompt 做准备。
接下去是针对每个社区构建一份本地上下文(Local Context)。首先会遍历社区的所有层级(从高到低,这边可以理解一层都有不同的社区,上层的社区下会继续划分子社区,所以是一个嵌套关系的),对每个社区聚合实体、边、声明,然后将结构化的社区上下文变成模型可读的 Prompt,再发送给模型进行摘要。
摘要生成主要是读取前一步产生的社区上下文信息,调用大语言模型去生成文字摘要。期间会有一些车略,比如处理上下超限的情况,会尝试用子社区报告替换本地上下文,如果无法替换则进行修剪本地上下文以适应限制。
样例数据:
1 | -----Reports----- |
文本嵌入(Text Embeddings)
这步是最后的环节了,用于为前面产生的各种文本内容生成对应的向量表示,用于后续检索阶段的语义搜索和向量检索。主要包括:
- 完整文档内容
- 实体标题和描述
- 关系描述
- 文本单元
- 社区标题和摘要
- 社区完整报告内容
检索阶段
Graph RAG 针对不同的使用场景,提供了 4 种查询方法:
- 全局搜索(Global Search):面向社区报告级别的全局搜索,适合高层知识查找
- 本地搜索(Local Search):走了图和文本搜索,同时融合实体、关系、文本等细粒度搜索
- 动态推理搜索(DRIFT Search):和本地搜索类似,但是引入了 embedding 对齐
- 基础搜索(Basic Search):走了文本级别的搜索,是最轻量的文本向量语义检索
全局搜索(Global Search)
主要基于社区(Community)和其报告(Reports)进行粗粒度搜索。走的是 Map Reduce 的方式,也就是将社区报告拆成多个文本块(chunks),每个文本块分别发送给大语言模型做分析,会生成类似下面格式的内容
1 | {{ |
这里包括的是对应社区报告的摘要,精炼的内容描述和对应的重要性得分,评分会决定该观点是否值得被纳入最终的 Reduce 阶段。Reduce 阶段只会过滤出 score 大于 0 的结果,并且对结果进行排序,使得较为重要的观点排在前面,最终会展现出类似这样的形式:
1 | ----Analyst 1---- |
表现出不同的“分析员”(Analyst)的分析情况,然后把这份汇总的结果再次发送到大语言模型,将多个“分析员”的观点汇总成一个连贯、有逻辑且可读性较强的最终答案。输入的 prompt 片段类似:
1 | ---Target response length and format--- |
最终输出的类似:
1 | ## Environmental Violations of Company A |
本地搜索(Local Search)
本地搜索会利用向量搜索去检索出合适的实体(Entities),然后给予这个实体去构建对应的上下文,其中涉及到了以下的数据:
- 实体
- 关系
- 文本单元
- 社区摘要
- 声明
其中实体是通过向量化搜索得到的,社区则是通过排序后选出 topK 个社区摘要,其他的则是通过对应实体去检索。最终会将上面的这些数据构建成单个上下文(不像全局搜索用 chunk 的形式)。然后将这个上下文结合预设的 Prompt 一起发送到大语言模型生成结果。
示例输入片段:
1 | ---Role--- |
输出示例:
1 | ## Key Individuals |
动态推理搜索(DRIFT Search)
动态推理搜索(DRIFT Search,Dynamic Reasoning and Inference with Flexible Traversal)是最复杂也最智能的一种检索方式,它结合了推理驱动的层次搜索、查询拆分(Primer)、多步骤搜索和最终答案的合并(Reduce)。
首先 DRIFT 会随机从社区报告里取一个全量文本出来,然后将输入的内容与随机取出的社区报告(作为模板)给到大语言模型去做相应的**虚拟答案(Hypothetical Answer)**生成,相应的 Prompt 是这样的:
1 | Create a hypothetical answer to the following query: {query} |
然后将虚拟的答案转成向量,通过计算余弦相似度(Sosine Similarity),可以得到虚拟答案和所有文档的相似度,取出 topK 社区报告。
然后基于 Primer 做将 topK 社区报告进行分片,并发调用 LLM 对每一份报告进行子问题生成(Query Decomposition)。我们来看看其 Prompt 模板:
1 | You are a helpful agent designed to reason over a knowledge graph in response to a user query. |
这里的 Prompt 要求 LLM 以类人类推理者而不是抽象逻辑机器来推理,其作用是结合用户 query 与社区总结(community reports),引导 LLM 推理出一个中间答案(intermediate answer)和一组后续子查询(follow-up queries)
输出示例:
1 | { |
最终这个环节得到的是以虚拟答案检索出来的 topK 社区报告为语境种子,去生成对应的中间答案和子查询列表以及对应的评分。最后就是将所有的中间答案拼接起来,评分取平均数,子查询问题合并。
接下去进入到循环执行动作(Action)的步骤了,会持续从当前状态中挑出尚未处理的动作(只保留 top-k 最重要的动作),每个动作进行搜索,这里的检索走的是本地搜索(Local Search),也就是针对 query 走图和文本搜索。这边还会控制最大深度,避免深度爆炸。
最后将所有的结果进行聚合(Reduce),会将前面所有 Action 最终的回答拼接让模型帮忙汇总出最终答案
基础搜索(Basic Search)
基础搜索的话只会将问题基于文本单元做向量检索,得到 topK 结果,然后到大语言模型进行生成。相对简单的一个检索。
示例输入:
1 | source_id|text |
示例输出:
1 | ---Target response length and format--- |
总结
我们花了很长的篇幅来深入 GraphRAG,是因为我觉得里面应用了很多相关技术实现,从最基础的向量化检索,到采用了图做结合,甚至里面也融合了多跳 RAG 或者说多跳推理的技术,还利用了 HyDE(Hypothetical Response)的思想。因此非常值得深入了解和学习。
总体而言 Graph RAG 通过将非结构化文本转化为图结构表示,突破了传统 RAG 仅依赖向量检索的局限性。它采用分阶段处理流程,从文本中提取实体与关系,构建社区结构与摘要信息,并融合图结构与向量嵌入,实现多种检索模式的协同支持。
在复杂上下文与多样应用场景中,GraphRAG 提供了一个强有力的实践范式。尽管本质上仍受限于语言模型的上下文窗口,但它通过算法、工程与架构手段最大化信息利用效率,将原本偏单跳的 RAG 推进到更具多跳推理能力的方向。其核心目标始终是:获取最相关、最有用的上下文以支持更好的生成结果。
RAG 这部分内容非常多,目前也只是走马观花式的覆盖了一部分内容,包括 AgenticRAG 在内的一些方式还没有展开篇幅去讲,但是我觉得整个篇幅的内容已经足够支撑每一位读者去开启 RAG 探索之路了。除了技术探索和学术研究以外,在 Applied AI 中,我们会更加关注实际的业务和需求,始终以此作为导向,利用技术去创造更多的商业价值,才是有意义的事情,因此技术不是目的而是手段,当我们遇到一个无法解决的问题时,或许应该再去看看业界有什么新的方法,如果刚好没有,就是创造这个新的方法的时候。
那么我们就继续往下走,来看看工具之于上下文工程的意义和用法