图解 RAG 高级算法和技术
RAG 概念
RAG,全称 Retrieval Augmented Generation,说白了就是给大语言模型配一个“外设知识库”。当模型需要回答问题时,先从某个数据源中检索出相关的信息片段,再把这些信息拼进提示词里,最后让模型基于这个上下文生成答案。本质上,这就是搜索 + 提示工程的组合——先把用户问题喂给搜索算法,找出最相关的上下文,然后把查询和上下文一起注入 Prompt,最后让 LLM 开口回答。
RAG 是 2023 年基于 LLM 的系统中最火热的架构,几乎成了标配。下面就把 RAG 的各种算法和技术从头到尾捋一遍。
Naive RAG
先从最基础的 Naive RAG 说起。流程很简单:把文档切成小块,用 Transformer 编码器模型(比如 BERT 家族的 Sentence Transformer)把这些块转成向量(Embedding),然后把所有向量塞进一个索引里。最后构造一个提示词,告诉模型:“根据搜索到的上下文回答用户的查询,如果找不到相关信息,就自己试着答,但得告诉用户你没找到依据。”
在运行时,用同样的编码器把用户查询也向量化,然后在索引里搜出最相似的 k 个结果,从数据库里捞回对应的文本块,作为上下文和查询一起送给大模型。下面是一个典型的 RAG 提示词示例:
prompt = f"""
Give the answer to the user query delimited by triple backticks ```{query}```
using the information given in context delimited by triple backticks ```{context}```.
If there is no relevant information in the provided context, try to answer yourself,
but tell user that you did not ha ve any relevant context to base your answer on.
Be concise and output the answer of size less than 80 tokens.
"""
Advanced RAG
1、分块和向量化
第一步是创建一个向量索引,用来表示文档内容。运行时,搜索所有向量与查询向量之间的最小余弦距离——距离越小,语义越接近。
1.1、分块
Transformer 模型有固定的输入长度,即便上下文窗口很大,一个句子或几个句子的向量也比在几页文本上取平均值的向量更能代表语义。所以必须把文档拆成一定大小的块,同时不能丢失含义——通常按句子或段落拆分,不能把一个句子切成两半。市面上有很多文本拆分器能搞定这事儿。
块的大小是个需要权衡的参数。它取决于嵌入模型的 token 容量:标准 BERT 类模型最多 512 个 token,OpenAI ada-002 能处理 8191 个 token。这里的关键是:既要给 LLM 足够的上下文去推理,又要让文本嵌入足够具体,以便搜索有效。
1.2、向量化
选择哪个模型来做 Embedding?选项很多,通常优先选搜索优化的模型,比如 bge-large 或 E5 系列。可以直接瞄一眼 MTEB 排行榜,上面有最新的性能对比。
2、搜索索引
2.1、向量存储索引
RAG Pipeline 的核心是搜索索引,用来存上一步得到的向量。最简单的实现是平面索引——暴力计算查询向量和所有块向量的距离。但数据量一上来(10000+ 元素),就得用专门的向量索引了,比如 faiss、nmslib 或 annoy,它们用近似最近邻算法(聚类、树、HNSW)来大幅提升效率。
根据索引选择、数据特性和搜索需求,还可以把元数据跟向量一起存,这样就能用元数据过滤器来限定搜索范围,比如只搜某个日期或来源的信息。
2.2、分层索引
文档多了怎么办?得能在海量文档里快速找到相关信息,然后综合成一个答案,还得引出处来源。一个高效的做法是建两个索引:一个存摘要,另一个存文档块。搜两步走:先用摘要索引过滤出相关文档,再在这部分文档的块索引里精确搜索。
2.3、假设问题和 HyDE
另一种思路:让大模型为每个文档块生成一个假设问题,然后把这些问题向量化存入索引。运行时,用查询去搜索这些“问题向量”(而不是块向量),检索到对应的问题后,再拿到原始文本块作为上下文送给 LLM。这种方法能提升搜索质量,因为查询和假设问题之间的语义相似度往往比查询和块更高(即 Q2Q 匹配)。
还有种反向逻辑的方法叫 HyDE——让大模型为给定的查询生成一个假设答案,然后把假设答案的向量和查询向量一起用,来提升搜索质量。
2.4、上下文扩充
概念很直白:为了搜索质量,检索更小的块,但为了推理质量,把周围的上下文也一并送给 LLM。有两种常用实现:一种是用检索到的句子周围 k 个句子来扩展上下文;另一种是递归地把文档拆成大的父块和小的子块。
2.4.1、句子窗口检索
方案中,文档里的每个句子都单独做向量化,这样搜索时能以极细的粒度找到最相关的句子。拿到最相关的句子后,再把它前后 k 个句子一起扩展成上下文,送给 LLM。绿色部分是在索引中搜到的句子嵌入,整个黑色+绿色段落都喂给 LLM,让它有更充足的上下文来推理。
2.4.2、父文档检索器
思路跟句子窗口检索类似——搜索更细粒度的信息,然后扩展上下文窗口。具体做法是:文档被拆成子块(小)和父块(大),检索时只搜索子块索引。如果前 k 个检索到的子块里有 n 个以上指向同一个父块,那就把这个父块替换为上下文送给 LLM——相当于自动合并了几个小子块。名字就是这么来的。
2.5、融合检索或混合搜索
一个相对传统的想法:把基于关键词的老式搜索(如 TF-IDF、BM25)和现代语义/向量搜索结合起来,取两者之长。唯一的难点是不同搜索结果的分数怎么归一化——通常用 Reciprocal Rank Fusion(倒数排序融合)对结果重新排序,得到最终输出。混合搜索往往能拿到更好的检索结果,因为它同时考虑了语义相似性和关键词匹配。
3、重新排名和过滤
检索结果拿到了,接下来得精加工:根据相似度分数、关键词、元数据过滤掉不合适的,或者用另外的模型重新排序。这一步是在把上下文送给 LLM 生成答案之前的最后一道关卡。
4、查询转换
查询转换是一组用 LLM 来修改用户输入的技术,目的是提升检索质量。有多种玩法:
- 如果查询很复杂,LLM 可以把它分解成多个子查询。比如问“GitHub 上 Langchain 和 LlamaIndex 哪个星多?”,显然在语料里很难找到直接的对比,那就拆成两个子查询:“Langchain 在 GitHub 上有多少星?”和“LlamaIndex 在 GitHub 上有多少星?”,并行执行后再把上下文合到一起,让 LLM 合成最终答案。Langchain 和 LlamaIndex 都实现了这个功能——前者叫多查询检索器,后者叫子问题查询引擎。
- :让 LLM 生成一个更一般的查询,检索到更通用或更高级的上下文,然后和原始查询的检索结果一起送进 LLM 生成答案。
Step-back prompting
- :直接用 LLM 把原始查询重新表述一番,以改进检索效果。
查询重写
5、聊天引擎
构建一个好用的 RAG 系统,光处理单次查询不够,还得支持多轮对话,能理解后续问题、照应或任意用户命令。这要通过查询压缩来处理,把聊天上下文和用户查询一起考虑进来。
一种流行且相对简单的方法是 ContextChatEngine:先检索跟用户查询相关的上下文,再和聊天记录(存在内存缓冲区里)一起送给 LLM,这样 LLM 就能基于历史上下文生成下一个答案。
更复杂的方案是 CondensePlusContextMode:每次交互时,聊天历史加上最后一条消息会被压缩成一条新查询,然后拿去搜索引,检索到的上下文连同原始用户消息一起用于生成答案。
6、查询路由
查询路由是由 LLM 驱动的决策步骤:根据用户查询来决定下一步干什么。选项通常包括:总结、针对某个数据索引搜索、或者尝试多个路由然后把结果合成一个答案。
查询路由器也用来选择索引或数据存储——比如你有多个数据源(经典向量存储、图数据库、关系数据库),或者有层次化索引(摘要索引和文档块索引)。路由的选择通过调用 LLM 来完成,返回预定义格式的结果,用于把查询送到给定的索引,或者更复杂的情况下,送到子链甚至其他 Agent。
7、RAG 中的 Agent
Agent 自第一个 LLM API 发布以来就存在了——核心想法是让 LLM 能推理、有工具、能完成任务。工具可以是确定性函数、外部 API,甚至其他 Agent。
来看一个多文档 Agent 方案:每个文档都初始化一个 Agent,它有两个工具——向量存储索引和摘要索引。顶层 Agent 负责把查询路由到对应的文档 Agent,并合成最终答案。每个文档 Agent 根据路由查询决定用哪个工具。这种架构展现了高级 RAG 的复杂路由决策。它的好处是能比较不同文档里的方案、实体,同时支持单文档的摘要和 QA——基本覆盖了最常见的“跟文档集合聊天”的用例。
但缺点也很明显:由于 LLM 内部来回多次调用,速度会比较慢。要知道,LLM 调用始终是 RAG Pipeline 里最耗时的操作,搜索本身是设计为快速执行的。所以对于大型多文档存储,建议对这个方案适当简化,保证可扩展性。
8、响应合成器
这是任何 RAG Pipeline 的最后一步——根据检索到的上下文和初始查询生成答案。最简单最直接的方法是把所有高于某个相似度阈值的上下文和查询拼接在一起,一次性喂给 LLM。
当然,还有更复杂的选项,涉及多次 LLM 调用来优化检索到的上下文并生成更好的答案。主要方法有:
- 逐块迭代细化:把检索到的上下文一块一块地送给 LLM,逐步完善答案。
- 总结上下文:先对检索到的上下文做总结再塞进提示词,以节省长度。
- 多答案合成:根据不同的上下文块生成多个答案,最后连接或总结成一个。
总结
这篇文章试图把 RAG 的核心算法方法梳理清楚,并举例说明了一些具体做法。生产环境中 RAG 系统最大的挑战是速度——毕竟 LLM 调用是瓶颈,搜索则是为速度而生的。希望这些内容能激发一些新的想法,让你在实际的 RAG Pipeline 里尝试、改进。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名