在之前的文章《RAG 初探》里,我们浅聊了 RAG 的基本原理,vanilla RAG 的工作方式概述下来说是:将文本切块,然后使用 transformer encoder 向量化,将向量放入索引。在用户发起一个 query 时,我们把 query 用同一个 encoder 向量化,然后跟索引中的向量做相似度计算,得到 top-K 个文档,最后将这些文档及 query 送入 LLM 里,让 LLM 生成答案。
整个 pipeline 的结构如下图所示:

流程可以分为切片+向量化、索引、检索、生成几个部分。
切片+向量化
切片切片需要考虑适当的块间重叠、多粒度文档块切分、基于语义的文档切分以及文档块摘要。
向量化需要使用合适的 encoder,底层 encoder 可以从 MTEB leaderboard 里挑选,上层 encoder 可以使用 SBERT。
索引
创建索引
索引有很多成熟的工具:faiss,nmslib,annoy。LlamaIndex 也支持许多向量存储索引。
索引优化
层次索引:如果文档比较多,考虑创建两个索引:一个摘要索引,一个文档块索引。搜索时两步走,先通过摘要筛选出相关文档,然后仅在这个相关组内进行搜索。

另一个思路是为每一个 chunk 都用 LLM 生成一个相关的问题,然后向量化并存储这些问题,搜索时先将 query 与问题做匹配,然后将匹配到的问题对应的 chunk 送入 LLM 生成答案。
搜索
混合/融合搜索
把基于关键字(比如 TFIDF、BM25)的搜索结果与基于向量的搜索结果组合起来。 这种方法的关键一环是把具有不同相似度分数的检索结果正确融合起来,毕竟 BM25 的分数跟向量的相似度分数具备不同的物理意义。 一个办法是借助倒排融合算法(reciprocal rank fusion,RRF)来解决,对检索结果进行重新排序以获得最终输出。
RRF 的思路是,对于每一个文档 ,计算它在每一个检索结果中的排名 ,然后将这些排名加权求和,权重是 ,其中 是一个超参数,用来扼制异常高排名的影响。这个方案的一个优点是,它不需要知道检索结果的具体分数,只需要知道排名就可以了。
Query 改造
有些 query 比较复杂,比如 “LangChain, LlamaIndex 在 Github 上哪个 star 更多”,这时候就需要对 query 进行拆分,变成两个简单的问题 “LangChain 在 Github 上的 star 数是多少” 以及 “LlamaIndex 在 Github 上的 star 数是多少”,然后将两个问题的答案相减,得到最终的答案。这个过程叫做 query 变换(query transformation)。
响应合成
RAG pipeline 的最后一步,是将检索到的文档及 query 送入 LLM 生成答案。简单粗暴的做法是所有检索召回的上文都拼接到一起,然后跟 query 一起丢给 LLM。精细一点的做法也有,比如:
- 先 summarize 检索到的文档,然后跟 query 一起丢给 LLM。
- 根据不同的 context chunks 生成多个不同的答案,在答案的基础上做 summary。
- 一次只给 LLM 一个 context chunk,迭代的优化答案。
参考
- Ivan Ilin, Advanced RAG Techniques: an Illustrated Overview, torwardsai blog, 2023.12.17