RAG(Retrieval-Augmented Generation,检索增强生成)是近年来大语言模型应用中最流行的架构之一。许多产品几乎完全建立在 RAG 之上,涵盖了结合网络搜索引擎和 LLM 的问答服务,到成千上万个私有数据聊天的应用程序。很多人将 RAG 和 Agent 作为大模型应用的两种主流架构,但什么是 RAG?它涉及了哪些具体的技术细节?本文将深入解读 RAG 的核心机制、高级优化策略及评估方法。
1. 什么是 RAG
RAG 即检索增强生成,它为 LLM 提供了从特定数据源检索到的信息,并基于此修正生成的答案。RAG 本质上是 Search + LLM Prompt 的结合体。系统通过大模型回答用户查询,并将搜索算法找到的相关信息作为大模型的上下文注入到提示语中。查询文本和检索到的上下文都会被发送给 LLM,使其能够利用外部知识生成更准确、更具时效性的回答。
在实现层面,嵌入式搜索引擎可以通过 Faiss 等库来实现,向量搜索领域成为了 RAG 的重要助力。像 Pinecone 这样的服务可以构建开源搜索索引,为输入文本增加了额外的存储空间和管理工具。关于向量表示和嵌入技术的更多细节,可以参考相关的深度学习文档。
面向 RAG 的开发框架主要有两个最著名的开源工具——LangChain 和 LlamaIndex。它们分别创建于 2022 年 10 月和 11 月,随着 ChatGPT 的爆发,在 2023 年获得了大量采用。这两个项目都是令人惊叹的开源工程,发展速度极快,极大地降低了构建 RAG 应用的门槛。
2. 基础的 RAG 技术
RAG 系统的起点通常是一个文本文档的语料库。基础流程看起来是这样的:把文本分割成块,把这些分块嵌入到向量与 Transformer 编码器模型中,把所有这些向量建立索引,最后创建一个 LLM 提示语,告诉模型回答用户的查询,并给出在搜索步骤中找到的上下文。
在运行时,我们用相同的编码器模型完成用户查询的向量化,然后执行这个查询向量的索引搜索,找到 top-k 的结果,从数据库中检索到相应的文本块,并提供给 LLM 提示语(Prompt)作为上下文。
在 OpenAI 平台上,提示词 Prompt 的设计非常关键。例如,可以使用如下 Python 代码结构来构建问答逻辑:
def question_answering(context, query):
prompt = f"""
基于以下上下文回答问题:{context}
问题:{query}
"""
response = get_completion(instruction=prompt, model="gpt-3.5-turbo")
answer = response.choices[0].message["content"]
return answer
关于提示词和提示词工程的更多介绍,可以参考 OpenAI 的官方手册。显然,尽管 OpenAI 在 LLM 市场上处于领先地位,但还是有很多替代方案,比如 Anthropic 的 Claude,还有最近流行的更小但功能强大的模型,比如 Mistral,微软的 Phi-2 以及许多开源选项,比如 Llama2,OpenLLaMA,Falcon 等都可以用来开发面向 RAG 的大模型产品。
3. RAG 中的高级技术
尽管并不是所有 RAG 系统中的高级技术都可以轻松地在一张图中可视化,但描述核心步骤和算法的方案对于理解其复杂性非常有意义。
3.1. 分块和矢量化
首先,要创建一个向量索引来表示我们的文档内容,然后在运行时搜索所有这些向量和查询向量之间最小距离对应的最接近语义。
由于 Transformer 模型有固定的输入序列长度,即使输入上下文的窗口很大,一个或几个句子的向量也比一个在几页文本上取平均值的向量更能代表它们的语义意义。因此,数据分块是一个有意义的技术。把初始文档分成一定大小的块,同时又不失去它们的意义,也就是把文本分成句子或段落,而不是把一个句子分成两部分。而且,已经有了各种能够执行此任务的文本分割器实现。例如,在 LlamaIndex 中,NodeParser 就提供了一些高级选项,如定义自己的文本分割器、元数据、节点/块关系等。
数据块的大小是一个需要考虑的参数,它取决于使用的嵌入模型及其 token 容量。标准的 Transformer 编码模型,如 BERT 的句子转换器,最多只能使用 512 个 token。OpenAI ada-002 能够处理更长的序列,如 8191 个 token,但这里的折衷是足够的上下文让 LLM 能够推理,以及特定的足够文本嵌入以便有效地执行搜索。
下一步是选择一个模型来生产所选块的嵌入。同样有很多方法,例如搜索优化的模型(bge-large 或者 E5 系列),MTEB 排行榜可以得到最新的一些方法信息。关于文档分块和向量化步骤的端到端实现,可以具体地参考 LlamaIndex 的官方文档。


