编者按: 检索增强生成(RAG)系统最近备受关注,ChatGPT的火爆更让这类系统成为广泛讨论的热点。我们今天为大家带来的这篇文章,作者Matt Ambrogi的核心观点是:构建一个基本可用的RAG系统非常简单,但要使其达到实际生产可用的程度则异常困难,需要我们投入大量精力。
为此,作者详细介绍了10种策略,包括清洗数据、尝试不同索引类型、优化分块策略、使用 Base Prompt、使用元数据过滤、使用查询路由、研究重排序、使用查询转换、微调嵌入模型、使用 LLM 生态相关工具等,这些策略都可能不同程度地提高RAG系统的性能。
总体而言,本文对于RAG系统开发者极具参考价值,值得仔细阅读和实践。
作者 | Matt Ambrogi
编译 | 岳扬
🚢🚢🚢欢迎小伙伴们加入AI技术软件及技术交流群,追踪前沿热点,共探技术难题~
01 快速入门指南,但只阅读本节是不够的
检索增强生成(Retrieval Augmented Generation)是将用户输入的信息补充到 ChatGPT 等大语言模型(LLM)中的过程,这些信息是用户(或系统)从其他地方检索到的。然后,LLM 可以使用这些信息来增强其回复内容的生成。— Cory Zue
“Retrieval augmented generation is the process of supplementing a user’s input to a large language model (LLM) like ChatGPT with additional information that you (the system) have retrieved from somewhere else. The LLM can then use that information to augment the response that it generates.” — Cory Zue
LLM(大语言模型) 是一项了不起的发明,但容易出现一个比较严重的问题——它们会凭空捏造信息。RAG 在对用户的问题或 query 进行回答的过程中提供问题背景,从而使得 LLM 更加实用。
通过 LangChain 或 LlamaIndex 等框架的快速入门指南,任何人都可以构建一个简单的 RAG 系统,比如仅用大约五行代码就能构建一个用于文档的chatbot。
但是,用这五行代码构建的 chatbot 效果不会很好。RAG 很容易设计系统原型,但要实际投入生产却非常困难——也就是说,很难达到用户满意的程度。这些基础的使用方式能够让 RAG 的效果达到 80%,但接下来那 20%的效果,往往需要进一步实验。 RAG 的最佳实践还有待完善,而且会因使用情况而异。但找出 RAG 的最佳实践绝对值得我们付出时间,因为 RAG 可能是使用 LLM 的最有效方式之一了。
本文将探讨一些有关改进 RAG 系统质量的策略,专为那些希望缩小基本配置(能用)与可投入生产环境(可用、好用)之间性能差距的 RAG 系统开发者量身定制。 原文标题所说的“ Improve(改进)”意思是增加 RAG 系统能够:(1)找到相关的上下文,(2)并生成相关回复的用户查询(query)比例。本文假设读者已经了解了 RAG 的工作原理。如若没有,建议阅读 Cory Zue 的这篇文章 ( 《 RAG (检索增强生成)技术详解:揭秘基于垂直领域专有数据的Chatbots是如何实现的 》 ) 。本文还会假设读者对用于构建这些 RAG 工具的常见框架——LangChain[1] 和 LlamaIndex[2] 有一些基本了解。然而,本文讨论的相关内容基本上是与这些框架无关的。
我不会深入探讨如何准确实施我所介绍的每种策略的细节,而是尝试让读者了解何时以及为什么这可能有用。考虑到该领域的发展速度之快,我不可能提供详尽无遗或完全最新的最佳 RAG 实践大全。相反,本文的目标是概述一些开发者在尝试和改进 RAG APP 时可能会考虑和尝试的事情。
02 提高检索增强生成系统性能的 10 种方法
2.1 清洗数据
RAG 将 LLM 的功能与我们的数据连接起来。如果数据在内容或布局上混乱,那么 RAG 系统就会受到影响。如果使用的数据存在冲突的或冗余的信息,那么检索系统将很难找到正确的上下文。而若比较幸运,找到了正确的上下文,LLM 所执行的生成步骤可能会不理想。比如说,假设你正在为公司的帮助文档构建一个 chatbot,但发现它的效果不佳。这时应该首先检查的是输入系统的数据。topics 是否被合理地划分?topics 是集中在一处还是分散在多处?如果你作为人类都不能轻松地判断出需要查阅哪个文档才能来回答常见的 queries,那么检索系统也无法做到。(译者注:"topics" 在这里是指文档中的主要内容或讨论的重点。"queries" 在这里是指用户可能会提出的问题或查询。)
这个过程可以很简单,只需手动合并同一主题的文档即可,但还可以更进一步。我见过的一种更有创意的方法,是使用 LLM 来为所有文档创建摘要。然后检索系统可以首先在这些摘要上进行检索,只有在必要时才深入文档细节。一些框架甚至内置了这种使用LLM创建摘要并进行搜索的功能。
2.2 探索各种不同的索引类型
索引是 LlamaIndex 和 LangChain 的核心支柱,它也是支撑检索系统的“擎天柱”。RAG 的通用标准方法涉及嵌入(embeddings)和相似性搜索(similarity search)。将上下文数据分块,然后将所有内容转换为嵌入向量,当接收到查询(query)时,从上下文中找到相似的片段。这种方法效果非常好,但并非适用于每种情况。如若我们遇到查询特定内容(比如网店中的商品)的需求,对此种情况,我们可能会希望探索基于关键词的搜索。但这些技术不一定是非此即彼的,许多应用程序都使用混合搜索方式。 例如,可以对涉及特定内容的 query 使用基于关键词的索引,但对一般客户的支持则依赖嵌入。
2.3 尝试不同的文档分块方法
将上下文数据进行分块是构建 RAG 系统的核心步骤。这些 RAG 框架将分块过程抽象化了,让我们不必理解该过程就可直接使用。但我们应该理解该过程,分块(chunking)大小的确定很重要。我们应该探索对于我们的 RAG APP 来说最有效的方法。一般来说,较小的分块大小通常能够提高检索效果,但可能会导致生成的内容缺乏上下文关联。 分块的方法有很多,但盲目地进行分块是行不通的。来自 PineCone 的这篇文章[3]介绍了一些可供参考的策略。我这里有一组测试问题样例,可通过实验[4]来处理这个问题。用小、中、大三种分块大小对每组问题循环进行了一次测试,结果发现小的分块大小是最好的。
2.4 尝试使用 Base Prompt
LlamaIndex 中使用Base Prompt(译者注:是指在使用大语言模型时所提供的初始 prompt 或指令,用于引导模型生成相应的文本或回答)的一个经典案例是:
“下面是上下文信息。请根据上下文信息,在不考虑已有知识的情况下,回答相关查询"。”
‘Context information is below. Given the context information and not prior knowledge, answer the query.’
我们可以使用其他 prompt 替换上面这个 prompt,甚至还可以修改 RAG,以便在上下文(context)中找不到优质的相关信息时,允许 LLM 依赖自己的知识。可以通过修改 prompt 的方式,影响模型对于不同类型问题的接受程度和回答方式,例如,指示它以某种方式回答主观性问题。至少,修改提示语,让 LLM 知道它在做什么工作,是十分有帮助的。例如:
“你是一名 customer support agent 。你的目标是,回答问题时要尽量提供准确的信息,并且尽可能地帮助提问者解决问题。你应当保持友善,但不要过于啰嗦。下面是提供的上下文信息。请根据上下文信息,在不考虑已有知识的情况下,回答相关查询。”
‘You are a customer support agent. You are designed to be as helpful as possible while providing only factual information. You should be friendly, but not overly chatty. Context information is below. Given the context information and not prior knowledge, answer the query.’
2.5 尝试使用元数据过滤
提高检索效率的一个非常有效的策略是在数据块中添加元数据,然后利用它们来帮助处理结果。日期是一种常见的元数据标签,因为它可以让我们根据时间的先后顺序进行筛选[5]。想象一下,假如你正在开发一款允许用户查询其电子邮件历史记录的应用程序。这种情况下,日期最近的电子邮件很可能更相关,但从嵌入的角度来看,我们并不知道这些邮件是否与用户的 query 最相似。这就引出了在构建 RAG 时要牢记的一个通用概念:相似 ≠ 相关。你可以将每封电子邮件的日期附加到其元数据中,然后在检索过程中优先考虑日期最近的上下文。LlamaIndex 内置了一款名为 Node Post-Processors 的工具,恰好可以帮助实现这一点。
2.6 使用查询路由
通常情况下,拥有多个索引(index)是非常有用的。当 query 进来时,就可以将其路由到相应的索引。例如,可以有一个索引处理摘要问题(summarization questions),另一个处理那些直接寻求明确答案的问题(pointed questions),还有一个索引适用于需要考虑时间因素才能得到准确答案的问题。如果试图针对所有这些行为优化一个索引,最终会影响索引在所有这些行为中的表现。 相反,你可以将 query 路由到适当的索引。另一个使用情况是将一些 query 定向到key-word based index(译者注:可能为使用关键词或关键短语构建的索引),正如第2.2节所讨论的那样。
构建好索引后,只需在文本中定义每个索引的用途。然后在查询时,LLM 将选择适当的索引。
2.7 研究重排序
重排序(Reranking)是解决相似性和相关性不一致问题的一种方法。通过重排序,检索系统会像往常一样获取上下文的顶级节点,然后根据相关性重新排序。Cohere Rereanker[6]通常用于此目的。我经常看到一些 RAG 专家推荐使用这种策略。无论在何种情况之下,如果你正在使用 RAG 构建系统,都应该尝试使用 Rereanking,看看这种方法是否能改善系统。
2.8 考虑使用查询转换
前文已经介绍将用户的 query 放入 base prompt 中来对 RAG 系统进行优化,但仍可以进一步修改:
- 重新表述(Rephrasing) :如果 RAG 系统找不到 query 的相关上下文,可以让 LLM 重新表述 query 并重新提交。在嵌入空间中,对人类来说看似相同的两个问题并不一定很相似。
- HyDE:HyDE[7]这种策略,可以接收 query后,先生成假设的回复,然后将两者都同时用于嵌入向量(embedding)的查找。研究显示这种方法可以显著提高RAG系统的性能。
- 子查询(Sub-queries) :对复杂的 query 进行分解,往往能让 LLM 的效果更好。可以将这一点纳入 RAG 系统中,将 query 分解为多个问题。
LLamaIndex 文档涵盖了这些类型的查询转换[8]。
2.9 微调嵌入模型
RAG 的标准检索机制是基于嵌入向量的相似性,数据被分解并嵌入到索引中。当接收到 query 时,它也转换为嵌入向量以便与索引中的嵌入向量进行比较。但是,是什么将文本转换为嵌入向量呢?通常是一个预训练模型,比如OpenAI的 text-embedding-ada-002。
问题在于,预训练模型可能无法很好地捕捉到特定领域中的相似性概念。想象一下,如果你现在需要处理法律文件,那么你很可能希望嵌入(embedding)能更多地根据 "知识产权 "或 "违约 "等特定领域的术语来判断相似性,而不是根据 "特此 "和 "协议 "等一般术语。
可以对嵌入模型进行微调来解决这个问题。这样做可以将检索性能指标提升5-10%。 微调需要做一些额外的工作,但确实可以显著提高RAG系统的检索性能。这个过程应该比您想象的要简单,因为LlamaIndex可以帮助您生成一个训练集。如需了解更多信息,您可以查看 Jerry Liu 撰写的这篇关于 LlamaIndex 如何微调嵌入模型的文章[9],或者查看这篇介绍微调过程的文章[10]。
2.10 开始使用 LLM 生态相关开发工具
你可能已经在使用LlamaIndex或LangChain来构建RAG系统了。这两个框架都拥有比较好用的 debugging 工具,可以让我们定义回调函数,查看使用了哪些上下文,检查检索结果来自哪个文档,等等。
如果您发现这些框架内置的工具不够用,也还有一个日益壮大的工具生态系统可以帮助我们深入了解 RAG 系统的内部运行情况。Arize AI有一个内置的工具[11],可以帮助我们探索检索到的上下文是如何以及为什么被检索到的。Rivet[12]是一款提供可视化界面的工具,可以帮助我们构建复杂的 Agent,法律科技公司 Ironclad 刚刚将其开源。新工具源源不断地被发布,都可以值得一试,看看哪些对你的工作流程有帮助。(译者注:白海科技的大模型平台IDP LM也可以帮助您零代码微调构建领域大模型)
03 Conclusion
构建 RAG 系统的过程可能会令人沮丧,因为让其运行起来非常容易,但要让其运行效果良好却非常困难。我希望上述这些策略可以启发一些读者,帮助读者尽可能弥合这一差距。这些想法并非一劳永逸的,实践是一个不断试验、尝试和遇到错误的过程。 我在本文中没有深入探讨如何评估系统的性能,评估过程目前更像是一门艺术而非科学,但建立一种可以被持续评估的系统非常重要。这是唯一的方法来判断正在实施的策略是否产生了作用。我之前写过一篇关于如何评估 RAG 系统的文章[13]。如需了解更多信息,您可以了解 LlamaIndex Evals[14]、LangChain Evals[15] 和一个非常有前景的新框架 RAGAS[15]。
Congratulations, you've made it!
Thanks for reading!
END
参考资料
[2]https://gpt-index.readthedocs.io/en/stable/getting_started/starter_example.html
[3]https://www.pinecone.io/learn/chunking-strategies/
[4]https://www.mattambrogi.com/posts/chunk-size-matters/
[5]https://www.mattambrogi.com/posts/recency-for-chatbots/
[6]https://docs.cohere.com/docs/reranking
[7]http://boston.lti.cs.cmu.edu/luyug/HyDE/HyDE.pdf
[8]https://gpt-index.readthedocs.io/en/v0.6.9/how_to/query/query_transformations.html
[9]https://medium.com/llamaindex-blog/fine-tuning-embeddings-for-rag-with-synthetic-data-e534409a3971
[11]https://arize.com/resource/arizeobserve-introducing-phoenix-ml-observability-in-your-notebook/
[12]https://news.ycombinator.com/item?id=37433218
[13]https://www.mattambrogi.com/posts/evaluating-chatbots/
[14]https://gpt-index.readthedocs.io/en/v0.7.2/how_to/evaluation/evaluation.html
[15]https://blog.langchain.dev/evaluating-rag-pipelines-with-ragas-langsmith/
本文经原作者授权,由Baihai IDP编译。如需转载译文,请联系获取授权。
原文链接: