RAG 检索增强生成:概念、原理与实战
引言
针对大型语言模型(LLM)在处理专有数据或快速更新信息时效果不佳的问题,传统方法如再训练、微调或 Prompt 增强往往存在成本高、灵活性差等局限。检索增强生成(Retrieval-Augmented Generation, RAG)的出现,有效弥合了 LLM 的通用常识与特定领域专有数据之间的差距。
本文将深入介绍 RAG 的概念理论,并演示如何利用 LangChain 框架,结合 OpenAI 语言模型和 Weaviate 矢量数据库,构建一个简单的 RAG 管道(Pipeline)。
什么是 RAG?
RAG 的全称是 Retrieval-Augmented Generation,中文翻译为检索增强生成。它是一个为大模型提供外部知识源的概念架构,使模型能够生成准确且符合上下文的答案,同时显著减少模型幻觉。
知识更新问题
最先进的 LLM 会接受大量的训练数据,将广泛的常识知识存储在神经网络的权重中。然而,当用户询问训练数据之外的知识(例如最新新闻、特定私有文档)时,LLM 的输出可能会导致事实不准确,这就是我们常说的'模型幻觉'。
因此,弥合大模型的常识与其他背景知识之间的差距非常重要,以帮助 LLM 生成更准确的结果。
解决方法
传统的解决方法是通过微调神经网络模型来适应特定领域的专有信息。尽管这种技术很有效,但它属于计算密集型任务,需要专业技术知识,难以灵活地适应不断变化的信息。
2020 年,Lewis 等人在知识密集型 NLP 任务中提出了一种更灵活的技术,称为检索增强生成(RAG)。研究人员将生成模型与检索器模块相结合,以提供来自外部知识源的附加信息,并且这些信息可以很方便地进行更新维护。
简单来说,RAG 对于 LLM 来说就像学生的开卷考试。在开卷考试中,学生可以携带参考材料(如课本或笔记),用来查找相关信息回答问题。测试的重点是学生的推理能力,而不是记忆特定信息的能力。
同样,事实知识与 LLM 的推理能力分离,并存储在外部知识源中,可以轻松访问和更新:
- 参数知识:在训练期间学习到的知识,隐式存储在神经网络的权重中。
- 非参数知识:存储在外部知识源中,例如向量数据库。
一般的 RAG 工作流程如下:
- 检索 (Retrieve):根据用户请求从外部知识源检索相关上下文。使用嵌入模型将用户查询嵌入到与向量数据库中附加上下文相同的向量空间中,执行相似性搜索,返回最接近的前 k 个数据对象。
- 增强 (Augment):用户查询和检索到的附加上下文被填充到提示模板中。
- 生成 (Generate):最后,检索增强后的提示被馈送到 LLM 进行回答。
LangChain 实现 RAG
接下来展示如何使用 LangChain,结合 OpenAI LLM、Weaviate 矢量数据库在 Python 中实现 RAG Pipeline。
基础环境准备
- 安装依赖包:包括用于编排的 langchain、大模型接口 openai、矢量数据库客户端 weaviate-client。
pip install langchain openai weaviate-client python-dotenv
-
配置 API 密钥:申请 OpenAI 账户获取 API 密钥,并将其保存在环境变量中。
-
创建配置文件:在项目根目录创建 .env 文件存放敏感配置。
-
加载配置:在代码中使用 python-dotenv 加载环境变量。
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
向量数据库构建
矢量数据库作为保存所有附加信息的外部知识源。该过程包含三个步骤:加载数据、数据分块、数据块存储。
1. 加载数据
选择一篇文本文件(如小说)作为文档输入。使用 LangChain 的 TextLoader 加载文本。
from langchain.document_loaders import TextLoader
loader = TextLoader('./斗破苍穹.txt')
documents = loader.load()
2. 数据分块
由于文档原始状态过长(可能超过数万行),无法直接放入大模型的上下文窗口,需要将其分割成更小的部分。LangChain 内置了多种文本分割器。这里使用 CharacterTextSplitter,设置 chunk_size 约为 1024,chunk_overlap 为 128,以保持块之间的文本连续性。
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1024, chunk_overlap=128)
chunks = text_splitter.split_documents(documents)
3. 数据块存储
为了启用跨文本块的语义搜索,需要为每个块生成向量嵌入,并将它们与嵌入一起存储。可以使用 OpenAI 嵌入模型生成向量,并使用 Weaviate 向量数据库进行存储。
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Weaviate
import weaviate
from weaviate.embedded import EmbeddedOptions
client = weaviate.Client(
embedded_options=EmbeddedOptions()
)
vectorstore = Weaviate.from_documents(
client=client,
documents=chunks,
embedding=OpenAIEmbeddings(),
by_text=False
)
RAG 流程实现
第一步:数据检索
将数据存入矢量数据库后,定义检索器组件。该组件根据用户查询和嵌入块之间的语义相似性获取相关上下文。
retriever = vectorstore.as_retriever()
第二步:提示增强
完成数据检索后,使用相关上下文来增强提示。需要准备一个提示模板,以便自定义提示内容。
from langchain.prompts import ChatPromptTemplate
template = """你是一个问答机器人助手,请使用以下检索到的上下文来回答问题,如果你不知道答案,就说你不知道。问题是:{question}, 上下文:{context}, 答案是:"""
prompt = ChatPromptTemplate.from_template(template)
第三步:答案生成
利用 RAG 管道构建一条链,将检索器、提示模板和 LLM 链接在一起。
from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
query = "萧炎的表妹是谁?"
res = rag_chain.invoke(query)
print(f'答案:{res}')
RAG 优化与最佳实践
在实际生产环境中,简单的 RAG 流程可能面临检索精度不足或响应延迟等问题。以下是几个关键的优化方向:
1. 检索策略优化
- 混合检索:结合关键词检索(BM25)和向量检索,提高召回率。
- 重排序 (Re-ranking):在初步检索后,使用交叉编码器对候选文档进行重新排序,选取最相关的片段。
2. 分块策略调整
- 按语义分块:除了按字符数分块,可以尝试按段落或句子分块,保持语义完整性。
- 父子索引 (Parent-Child Indexing):检索小片段,但向 LLM 发送包含更多上下文的父文档。
3. 提示工程改进
- 动态 Few-Shot:在提示中加入示例,引导模型更好地理解任务。
- 思维链 (CoT):要求模型在给出最终答案前先进行推理步骤。
4. 评估体系
建立评估指标至关重要,常用的包括:
- 检索准确率 (Recall@K):衡量检索到的文档是否包含正确答案。
- 答案相关性:通过人工或自动评分判断生成答案的质量。
总结
本文介绍了 RAG 的核心概念及其工作原理,并通过 Python 和 LangChain 实现了具体的 RAG 管道。在此过程中,使用了 OpenAI 的 ChatGPT 接口、Weaviate 矢量数据库以及 OpenAI 嵌入模型。通过引入外部知识库,RAG 有效解决了大模型知识滞后和幻觉问题,是构建企业级 AI 应用的关键技术之一。未来随着检索技术的进步,RAG 将在更多垂直领域发挥重要作用。