RAG 应用构建与优化:解决检索召回与上下文窗口问题
前面的章节,我们已经完成了可用的基于知识库回答的 AI 助手。尽管 RAG(Retrieval-Augmented Generation)容易上手,但是要真正掌握其精髓却颇有难度。实际上,建立一个有效的 RAG 系统不仅仅是将文档放入向量数据库并叠加一个 LLM 模型那么简单,这种方式仅时而有效而已。比如我们问些复杂点问题,LLM 的回答往往不尽如人意,提示词的内容并不全面。
问题分析:检索召回与上下文窗口的矛盾
在揭开解决方案的神秘面纱之前,我们先来探索一下这个问题的核心。想象一下,你有一个巨大的图书馆,里面有数十亿本书,而你的任务是在这个图书馆中找到与你的研究主题最相关的资料。这就是 RAG 模型的工作——在大规模的文本海洋中进行语义搜索。
为了在这个巨大的图书馆中快速找到答案,我们使用了一种叫做向量搜索的技术。这就像是在一个多维空间中,把每本书的内容压缩成一个小小的向量,然后通过计算这些向量与你的查询向量之间的距离(比如用余弦相似度),来找到最接近的那本书。但是,这里有个小问题。当我们把书的内容压缩成向量时,就像是把一个丰富多彩的故事变成了黑白照片,总会有一些细节丢失。所以,有时候即使是最接近的三本书,也可能遗漏了一些关键的线索。如果那些排名靠后的书里藏着宝藏般的信息,我们该怎么办呢?
一个直观的想法是,把更多的书带回家(增加 top_k 值),然后把它们一股脑儿地交给我们的'大语言模型'。但是,我们真正关心的是召回率,也就是'我们找到了多少真正相关的书'。召回率并不在乎我们带回了多少本书,它只关心我们是否找到了所有相关的书。理论上,如果我们把图书馆里的每一本书都带回家,我们就能达到完美的召回率。然而,现实是残酷的。我们的'大语言模型'就像是一个只能装下有限信息的背包,我们称之为上下文窗口。即使是最先进的模型,比如 Anthropic 的 Claude,它的背包可以装下 100K Token,我们还是不能把所有的书都塞进去。
图表达的意思是如果信息被放置在上下文窗口的中间位置,那么模型回忆或检索这些信息的能力会降低,其效果甚至不如这些信息从未被提供给模型。这里的'上下文窗口'指的是模型在处理语言时能够考虑的文本范围,通常是一个固定长度的序列。这种现象可能是因为在上下文窗口中间的信息相比于靠近窗口开始或结束位置的信息,更容易被后续输入的信息所覆盖或干扰,从而导致模型在需要时难以准确地回忆起这些信息。
示例说明
假设我们有一个大型语言模型(LLM),它的上下文窗口长度为 10 个句子。我们想要模型根据一段对话来回答问题。对话内容如下:
小明说:'我昨天去了图书馆。'
小华问:'你借了什么书?'
小明回答:'我借了一本关于历史的书。'
小华又问:'那本书是关于哪个时期的?'
小明说:'是关于古罗马的。'
小华说:'听起来很有趣。'
小明补充:'是的,书中有很多关于罗马帝国的细节。'
小华问:'你打算什么时候还书?'
小明回答:'下周三。'
小华说:'我可能也会去借那本书。'
现在,我们要求模型回答问题:'小明借的书是关于什么的?'
如果我们将这个问题放在上下文窗口的中间(例如,在第 5 句和第 6 句之间),模型可能会因为后续的对话内容(如小华对书的兴趣、还书日期等)而分散注意力,导致它回忆起小明借的书是关于古罗马的能力降低。相比之下,如果问题紧跟在第 3 句或第 5 句之后,模型可能更容易直接关联到小明借的书的内容,因为它还没有被后续的对话内容所干扰。
这个例子说明了在上下文窗口中间存储的信息可能会受到后续信息的干扰,从而影响模型回忆这些信息的能力。这也强调了在设计交互式或连续对话系统时,合理安排信息在上下文中的位置对于提高模型性能的重要性。
LLM 的回忆能力指的是它从其上下文窗口内的文本中检索信息的能力。研究表明,随着我们在上下文窗口中放置更多的令牌(tokens),LLM 的回忆能力会下降。当我们过度填充上下文窗口时,LLM 也更不可能遵循指令——因此,过度填充上下文窗口是一个糟糕的想法。
我们可以通过增加向量数据库返回的文档数量来提高检索回忆率,但我们不能在不损害 LLM 回忆能力的情况下将这些文档传递给 LLM。
解决方案:最大化检索与 LLM 效率
解决这个问题的方法是,通过检索大量文档来最大化检索回忆率,然后通过最小化传递给 LLM 的文档数量来最大化 LLM 的回忆能力。为了做到这一点,可以采用以下方案:
1. 使用合适的切分器
在前面的例子中,我使用了 RecursiveCharacterTextSplitter。此文本拆分器是推荐用于通用文本的拆分器。它通过一个字符列表参数化,并尝试按顺序在这些字符上拆分,直到块足够小。默认的字符列表是 [\n\n, \n, , ``]。这样做的效果是尽可能长时间地保持所有段落(然后是句子,然后是单词)在一起,因为这些通常看起来是最具有语义相关性的文本部分。
合理地分割文档需要考虑以下因素:
- 块大小:块的大小应该适中,既要足够小以适应模型的上下文长度限制,又要足够大以包含足够的信息。通常,块的大小在几百个单词左右。


