“一代更比一代强”:现代 RAG 架构的演进之路

基于 RAG(检索增强生成)的 AI 系统,过去是,现在仍然是企业利用大语言模型(LLM)的最有价值的应用之一。我记得差不多两年前我写了第一篇关于 RAG 的文章,那时候这个术语还未被广泛采用。

我当时描述的是一个以最基础方式实现的 RAG 系统。自那以后,这个行业不断发展,在此过程中引入了各种先进技术。

在这篇文章,我们将探讨 RAG 的演进历程 —— 从基础版本(Naive)到 Agentic。阅读本文后,您将理解 RAG 系统演进过程中的每一步都攻克了哪些挑战。

01 Naive RAG 的出现

2022 年底 ChatGPT 的推出让 LLMs 成为了主流,几乎与此同时,Naive RAG 也应运而生。检索增强生成(RAG)技术的出现旨在解决原生 LLM 所面临的问题。简而言之就是:

  • 幻觉问题。
  • 有限的上下文窗口大小。
  • 无法访问非公开数据。
  • 无法自动获取训练截止日期后的新信息,且更新这些知识需要重新训练模型。

RAG 的最简单实现形式如下:

![[v2-bee350c9ee8f6e5c5b2de4a09c5c81b4_1440w.jpg]]

Naive RAG.

预处理过程(Preprocessing):

1) 将整个知识库的文本语料分割成文本块(chunk) —— 每个文本块代表一段可供查询的上下文片段。目标数据可来自多种来源,例如以 Confluence 文档为主、PDF 报告为辅的混合资料库。

2) 使用嵌入模型(Embedding Model)将每个文本块转换为向量嵌入(vector embedding)。

3) 将所有向量嵌入存储到向量数据库(Vector Database)中。 同时分别保存代表每个嵌入的文本及其指向嵌入的指针。

检索过程(Retrieval):

4)在向量数据库或知识检索系统中,为了确保查询和存储的知识能够准确匹配,需要用同一个嵌入模型(Embedding Model)来处理存储到知识库中的文档内容和用户提出的问题或查询。

5) 使用生成的向量嵌入在向量数据库的索引上运行查询。 选择要从向量数据库中检索的向量数量 —— 等同于你将检索并最终用于回答查询的上下文数量。

6) 向量数据库针对提供的向量嵌入在索引上执行近似最近邻(ANN)搜索,并返回之前选定数量的上下文向量。 该过程会返回在给定嵌入空间(Embedding/Latent space)中最相似的向量,需将这些返回的向量嵌入映射到其对应的原始文本块。

7) 通过提示词(prompt)将问题连同检索到的上下文文本块传递给 LLM。 指示 LLM 仅使用提供的上下文来回答给定问题。这并不意味着不需要提示词工程(Prompt Engineering)——你仍需确保 LLM 返回的答案符合预期范围,例如,如果检索到的上下文中没有可用数据,则应确保不提供编造的答案。

02 Naive RAG 系统的动态组件

即便不采用任何高级技术,在构建生产级 RAG 系统时也需要考虑许多动态组件。

![[v2-1543c5d958e7a940e6c42cdc70e8f7f6_1440w.jpg]]

RAG - 动态组件

检索环节:

F) 分块策略 - 如何对用于外部上下文的数据进行分块

  • 小文本块与大文本块的选择
  • 滑动窗口或固定窗口的文本分块方法
  • 检索时是否关联父块/链接块,或仅使用原始的检索数据

C) 选择嵌入模型,将外部上下文嵌入到 latent space 或从 latent space 中查询。 需要考虑上下文嵌入(Contextual embeddings)。

D) 向量数据库

  • 选择哪种数据库
  • 选择部署位置
  • 应与向量嵌入一并存储哪些元数据?这些数据将用于检索前的预筛选和检索后的结果过滤。
  • 索引构建策略

E) 向量搜索

  • 选择相似度度量标准
  • 选择查询路径:元数据优先,还是近似最近邻(ANN)优先
  • 混合搜索方案

G) 启发式规则 - 应用于检索流程的业务规则

  • 根据文档的时间相关性调整权重
  • 对上下文进行去重(根据多样性进行排序)
  • 检索时附带内容的原始来源信息
  • 根据特定条件(如用户查询意图、文档类型)对原始文本进行差异化预处理

生成环节:

A) 大语言模型 - 为你的应用程序选择合适的 LLM

B) 提示词工程 - 即使能在提示词(prompt)中调用上下文信息,也仍需精心设计提示词 —— 你依然需要调整系统(译者注:包括设定角色(Role)、规则(Rules)、输出格式(Format)等对齐手段。),才能生成符合预期的输出,并防范越狱攻击。

完成上述所有工作后,我们才得以构建可运行的 RAG 系统。

但残酷的事实是,此类系统往往难以真正解决业务问题。由于各种原因,这种系统的准确性可能很低。

03 改进 Naive RAG 系统的高级技术

为不断提高 Naive RAG 系统的准确性,我们采用了以下一些较为成功的技术:

  • 查询改写(Query Alteration) - 可采用以下几种技巧:
  • 查询重写(Query rewriting) :让大语言模型(LLM)重写原始查询,以更好地适应检索过程。重写的方式有多种,例如,修改语法错误,或将查询简化为更简短精炼的语句。
    • 查询扩展(Query Expansion):让 LLM 对原始查询进行多次改写,创建多个变体版本(variations)。接着,多次运行检索过程,以检索更多可能相关的上下文。
  • 重排序(Reranking) - 对初次检索出的文档,用比常规上下文搜索更复杂的方法进行重排序。通常,这需要使用更大的模型,并且在检索阶段有意获取远超实际所需数量的文档。重排序(Reranking)与前文提到的查询扩展(Query Expansion)配合使用效果尤佳,因为后者通常能返回比平时更多的数据。整个过程类似于我们在推荐系统中常见的做法。
  • 嵌入模型的微调(Fine-Tuning of the embedding model) - 某些领域(如医疗)在使用基础嵌入模型进行数据检索效果不佳。此时,你就需要对自己的嵌入模型进行定制化微调。

接下来,我们再看些其它高级的 RAG 技术与架构。

04 上下文检索(Contextual Retrieval)

上下文检索(Contextual Retrieval)的概念由 Anthropic 团队在去年年底提出。其目标在于提升基于检索增强生成(RAG)的 AI 系统中检索到的数据的准确性和相关性。

我非常喜欢上下文检索的直观性和简洁性。而且它的确能带来不错的效果。

以下是上下文检索的实现步骤:

![[v2-ec64942805835f0773d1aad0518c9776_1440w.jpg]]

上下文检索(Contextual Retrieval)

预处理阶段(Preprocessing):

1)使用选定的分块策略将每份文档分割成若干个文本块。

2)将每个文本块单独与完整文档一起加入提示词中。

3)在提示词中添加指令,要求 LLM 定位该文本块在文档中的位置,并为其生成简短的上下文。随后将此提示词输入选定的 LLM。

4)将上一步生成的上下文,与其对应的原始文本块合并。

5)将组合后的数据输入一个 TF-IDF 嵌入器(embedder)。

6)再将数据输入一个基于 LLM 的嵌入模型(embedding model)。

7)将步骤 5 和步骤 6 生成的数据存入支持高效搜索的数据库中。

检索阶段(Retrieval):

8)使用用户查询(user query)检索相关上下文。使用近似最近邻(ANN)搜索实现语义匹配,同时使用 TF-IDF 索引进行精确搜索。

9)使用排序融合(Rank Fusion)技术对检索结果进行合并、去重,并选出排名前 N 的候选项。

10)对前一步的结果进行重排序(Rerank),将范围缩小至前 K 个候选项。

11)将步骤 10 的结果与用户查询一起输入 LLM,生成最终答案。

一些思考:

  • 步骤 3. 听起来(并且实际也)耗费惊人,但通过应用提示词缓存技术(Prompt Caching),成本可以显著降低。
  • 提示词缓存技术既可在专有(闭源)模型场景下使用,也可在开源模型场景下使用(请参阅下一段内容)。

05 缓存增强生成(Cache Augmented Generation)的昙花一现

2024 年底,一份白皮书在社交媒体上短暂刷屏。它介绍了一项有望彻底改变 RAG (检索增强生成) 的技术(真的能吗?)—— 缓存增强生成(Cache Augmented Generation, CAG)。我们已经了解了常规 RAG 的工作原理,下面简要介绍一下 CAG:

![[v2-f0aca8b384e5bc198845fe88f3526686_1440w.jpg]]

RAG vs. CAG

1)将所有外部上下文预计算至 LLM 的 KV 缓存中,并存入内存。该过程仅需执行一次,后续步骤可重复调用初始缓存而无需重新计算。

2)向 LLM 输入包含用户查询(user query)的系统提示词,同时提供如何使用缓存上下文的指令。

3)将 LLM 生成的答案返回用户。完成后清除缓存中的临时生成内容,仅保留最初缓存的上下文,使 LLM 准备好进行下一次生成。

CAG 承诺,通过将所有上下文存储在 KV 缓存中(而非每次生成时只检索部分数据),实现更精准的检索。现实如何?

  • CAG 无法解决因上下文极长而导致的不准确问题。
  • CAG 在数据安全方面存在诸多局限性。
  • 对大型组织而言,将整个内部知识库加载到缓存近乎不可能。
  • 缓存会失去动态更新能力,添加新数据非常困难。

事实上,自从多数 LLM 提供商引入提示词缓存(Prompt Caching)技术后,我们已在使用的正是 CAG 的一种变体。我们的做法可以说是 CAG 与 RAG 的融合,具体实施过程如下:

![[v2-a42b8b8f96db7fbaa6fd4dd4de321c85_1440w.jpg]]

RAG 和 CAG 的融合

数据预处理:

1)在缓存增强生成(CAG)中,我们仅使用极少变化的数据源。除了要求数据更新频率低外,我们还应该考虑哪些数据源最常被相关查询命中。确定这些信息后,我们才会将所有选定的数据预计算至 LLM 的 KV 缓存中,并将其缓存在内存中。此过程仅需执行一次,后续步骤可多次运行而无需重新计算初始缓存。

2)对于 RAG,如有必要,可将向量嵌入预计算并存入兼容的数据库中,供后续步骤 4 检索。有时对于 RAG 来说,只需更简单的数据类型,常规数据库即可满足需求。

查询路径:

3)构建一个提示词,需包含用户查询及系统提示词,明确指导大语言模型如何利用缓存的上下文(cached context)及外部检索到的上下文信息。

4)将用户查询转化为向量嵌入,用于通过向量数据库进行语义搜索,并从上下文存储中检索相关数据。若无需语义搜索,则查询其他来源(如实时数据库或互联网)。

5)将步骤 4 中获取的外部上下文信息整合至最终的提示词中,以增强回答质量。

6)向用户返回最终生成的答案。

接下来,我们将探讨最新的技术发展方向 —— Agentic RAG。

模型缓存增强代码步骤 python

将 LLM 的选定数据(通常是重复使用的前缀/Prompt)预计算并存储到 KV 缓存(Key-Value Cache)中,是提升多轮对话或长文本推理效率的关键。

在 Python 中实现这一过程通常分为三步:预热计算、导出/保存缓存、以及在推理时手动注入。

实现代码步骤

以下基于 transformers 库展示核心实现逻辑:

1. 初始预热:计算并获取选定数据的 KV 缓存

首先,将你需要预计算的“选定数据”(如系统提示词、长文档背景)输入模型,并开启 use_cache=True 以获取 past_key_values

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

# 1. 加载模型和分词器
model_id = "your-model-path"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float16).cuda()

# 2. 定义需要预计算的选定数据(例如长前缀)
prefix_text = "这是需要预计算并缓存的长文本或系统指令..."
inputs = tokenizer(prefix_text, return_tensors="pt").to("cuda")

# 3. 进行前向传播以获取 KV 缓存
with torch.no_grad():
    outputs = model(**inputs, use_cache=True)
    
# 选定的 KV 缓存存储在 past_key_values 中
precomputed_kv = outputs.past_key_values 

2. 推理阶段:将预计算的缓存注入模型

在后续推理新问题时,不再重复计算 prefix_text,而是直接将 precomputed_kv 作为 past_key_values 参数传入。 [1]

# 4. 定义新的用户问题
user_query = "基于上述内容,请回答:..."
new_inputs = tokenizer(user_query, return_tensors="pt").to("cuda")

# 5. 拼接位置编码(重要:需要告诉模型新输入是从哪里开始的)
# past_key_values 的长度即为已缓存 token 的数量
past_length = precomputed_kv[0][0].shape[2] 
# 注意:在某些版本中,transformers 会自动处理位置偏移,但手动生成时需留意 attention_mask

# 6. 使用预计算的缓存进行生成
output_ids = model.generate(
    **new_inputs,
    past_key_values=precomputed_kv,
    use_cache=True,
    max_new_tokens=50
)

print(tokenizer.decode(output_ids[0], skip_special_tokens=True))

关键点与优化建议

  • 内存管理:KV 缓存在显存中占用较大。若需缓存大量不同前缀,建议参考 vLLM 的 PagedAttention 技术,它通过类似操作系统的分页管理来减少显存碎片并提高利用率。
  • 前缀缓存(Prefix Caching):对于高并发场景,可以使用支持自动前缀缓存的模型推理引擎(如 vLLMMindIE),它们能自动识别重复前缀并复用 KV 块,无需手动编写复杂的注入逻辑。
  • 数据一致性:预计算 KV 缓存时使用的 tokenizer 参数和模型配置(如 torch_dtype)必须与推理时完全一致,否则会导致推理结果乱码。 [2, 3, 4]

06 Agentic RAG

Agentic RAG 新增了两个核心组件,试图降低应对复杂用户查询时的结果不一致性。

  • 数据源路由(Data Source Routing)。
  • 答案验证与修正(Reflection)。

现在,让我们来探究其工作机制。

![[v2-6cff163415e0977e47c89bd29b103422_1440w.jpg]]

Agentic RAG

1) 分析用户查询 :将原始用户查询传递给基于大语言模型的智能体进行分析。在此阶段:

a. 原始查询可能会被改写(有时需多次改写),最终生成单个或多个查询传递至后续流程。

b. 智能体判断是否需要额外数据源来回答查询。这是体现其自主决策能力的第一环节。

2) 如果需要其他数据,则触发检索步骤,此时进行数据源路由(Data Source Routing)。 系统中可预置一个或多个数据集,智能体被赋予自主权来选择适用于当前查询的具体数据源。举几个例子:

a. 实时用户数据(如用户当前位置等实时信息)。

b. 用户可能感兴趣的内部文档。

c. 网络公开数据。

d. …

3) 一旦从潜在的多个数据源检索到数据,我们就会像在常规 RAG 中一样对其进行重排序。 这也是一个关键步骤,因为利用不同存储技术的多个数据源都可整合至此 RAG 系统中。检索过程的复杂性可被封装在提供给智能体的工具背后。

4) 尝试直接通过大语言模型生成答案(或生成多个答案,或生成一组操作指令)。 此过程可在第一轮完成,或在答案验证与修正(Reflection)环节后进行。

5) 对生成的答案进行分析、总结,并评估其正确性和相关性

a. 若智能体判定答案已足够完善,则返回给用户。

b. 若智能体认为答案有待改进,则尝试改写用户查询并重复这个生成循环(generation loop)。此处体现了常规 RAG 与 Agentic RAG 的第二大差异。

近期 Anthropic 的开源项目 MCP,将为 Agentic RAG 的开发提供强劲助力。

07 总结 (Wrapping up)

至此,我们回顾了现代检索增强生成(RAG)架构的演进历程。 RAG 技术并未消亡,也不会在短期内消失。 我相信其架构在未来一段时间内仍将持续演进。学习这些架构并了解何时使用何种方案,将是一项有价值的投资。

一般来说,方案越简单越好,因为增加系统复杂性会带来新的挑战。 一些新出现的挑战包括:

  • 对端到端系统进行评估的困难。
  • 多次调用大语言模型导致的端到端延迟增加。
  • 运营成本的增加。
posted @ 2026-07-01 19:09  crabin88  阅读(8)  评论(0)    收藏  举报