用Python构建RAG检索增强生成系统:从原理到生产部署

引言:为什么需要RAG
大语言模型虽然强大,但有两个根本性限制:知识截止日期和幻觉问题。模型的知识停留在训练数据的时间点,对于训练后发生的事件一无所知。同时,模型有时会"编造"听起来合理但实际错误的信息。
检索增强生成(Retrieval-Augmented Generation,RAG)通过将外部知识库与LLM结合,优雅地解决了这两个问题。当用户提问时,系统先从知识库中检索相关文档,然后将检索结果与问题一起送给LLM,让它基于真实的信息来源生成回答。
一、RAG系统的核心架构
一个完整的RAG系统包含以下关键组件:
1.1 文档处理管道
文档处理的目的是将各种格式的原始文档转化为可供检索的标准化文本块。这个过程包括:
文档解析:从PDF、Word、HTML、Markdown等格式中提取纯文本。对于PDF,常用PyMuPDF或Unstructured库;对于网页内容,可以使用BeautifulSoup或Trafilatura。
文本清洗:去除页眉页脚、特殊字符、多余空白,统一编码格式。
智能分块:将长文档切分为适当大小的文本块。分块策略直接影响检索质量——块太大则检索不精确,块太小则丢失上下文。
1.2 向量嵌入与存储
将文本块转化为向量表示是RAG的核心步骤。现代嵌入模型(如text-embedding-3-large或bge-large-zh)能将语义相似的文本映射到向量空间中相近的位置。
向量数据库的选择至关重要。Chroma适合原型开发,Milvus适合大规模生产部署,Pinecone提供全托管服务,而Weaviate在混合搜索方面表现出色。
1.3 检索与重排序
基础检索使用余弦相似度或欧氏距离来找到最相关的文档块。但简单的向量相似度可能不够精确,因此生产系统通常会加入重排序步骤——使用更强大(也更慢)的模型对候选文档块进行精细排序,然后只保留最相关的前几条送给LLM。
二、手把手构建RAG系统
2.1 环境准备
# 安装依赖
# pip install langchain chromadb sentence-transformers openai pypdf
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.llms import ChatOpenAI
2.2 文档加载与分块
def load_and_split_documents(pdf_dir: str):
documents = []
for pdf_file in Path(pdf_dir).glob("*.pdf"):
loader = PyPDFLoader(str(pdf_file))
documents.extend(loader.load())
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=100,
separators=["\n\n", "\n", "。", ".", " ", ""],
)
chunks = text_splitter.split_documents(documents)
return chunks
2.3 构建向量索引
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5",
model_kwargs={"device": "cuda"},
)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db",
)
retriever = vectorstore.as_retriever(
search_type="mmr", # 最大边际相关性,增加结果多样性
search_kwargs={"k": 5, "fetch_k": 20},
)
2.4 构建问答链
from langchain.prompts import PromptTemplate
template = """基于以下上下文回答问题。如果无法从上下文中找到答案,请如实说明。
上下文:
{context}
问题:{question}
回答:"""
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4", temperature=0),
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={
"prompt": PromptTemplate.from_template(template)
},
)
三、高级RAG技术
3.1 父子文档检索
基础的文本分块面临一个困境:小块检索精确但丢失上下文,大块保留上下文但检索精度下降。父子文档检索巧妙地解决了这个问题——构建索引时使用小块以提高搜索精度,但在生成答案时将小块所属的完整文档段落一并提供给LLM。
3.2 多跳检索
复杂问题往往需要从多个文档中拼接信息。多跳检索通过多轮检索——第一轮检索的结果用于生成新的检索查询——逐步收集回答问题所需的全部信息。
3.3 自查询检索
自查询检索器允许用户在语义搜索的同时指定元数据过滤条件。比如"查找2025年发布的关于大模型微调的技术文档"——系统会将"大模型微调"转化为向量搜索,同时将"2025年"转化为元数据过滤。
四、生产环境优化
4.1 检索质量评估
使用RAGAS等评估框架定期评估检索质量,关注以下指标:
- 上下文召回率:检索结果是否包含了回答问题所需的信息
- 答案忠实度:生成的回答是否完全基于提供的上下文
- 答案相关性:回答是否直接回应了用户的问题
4.2 缓存策略
对高频查询实现语义缓存。当新问题与已缓存问题的语义相似度超过阈值时,直接返回缓存结果,大幅降低延迟和成本。
4.3 流式输出
使用Server-Sent Events或WebSocket实现流式响应,让用户逐步看到生成内容,提升交互体验。
五、RAG的局限性
尽管RAG效果显著,但仍有一些局限需要正视:
- **检索失败**:如果知识库中没有相关信息,RAG也无能为力
- **上下文窗口限制**:即使检索到了相关文档,LLM的上下文窗口也可能装不下
- **噪声干扰**:检索回的不相关文档可能误导模型
结语
RAG是目前将LLM落地到企业场景中最实用的技术路线之一。它不仅解决了幻觉问题,还使得模型能够基于最新、最精准的私有知识回答问题。
对于大多数企业来说,微调一个自有模型的门槛远高于构建一个RAG系统。从今天开始,用RAG让AI真正理解你的业务。
---
封面图来源:Unsplash 本文为Ai探索笔记原创


钱哆哆♥官方正规流量卡♥1 个月前
生死门虽繁星灿烂,但活着的人才是最重要。
钱哆哆♥官方正规流量卡♥1 个月前
《技术博客图文文章怎么做得不单一:封面、结构图与场景插图的组合方法》已更新:技术博客图文文章怎么做得不单一:封面、结构图与场景插图的组合方法 很多技术博客的正文其实不差,问题常常出在视觉层太单一。首页列表里大家都只有一张封面,点进去以后又是一大段连续文字,读者很难在几秒钟内判断这篇文章到底值不值得继续看。内容本身也许很扎实,但呈现方式没有把价值推出来。…
钱哆哆♥官方正规流量卡♥1 个月前
《技术博客图文文章怎么做得不单一:封面、结构图与场景插图的组合方法》已更新:技术博客图文文章怎么做得不单一:封面、结构图与场景插图的组合方法 很多技术博客的正文其实不差,问题常常出在视觉层太单一。首页列表里大家都只有一张封面,点进去以后又是一大段连续文字,读者很难在几秒钟内判断这篇文章到底值不值得继续看。内容本身也许很扎实,但呈现方式没有把价值推出来。…
钱哆哆♥官方正规流量卡♥1 个月前
《技术博客图文文章怎么做得不单一:封面、结构图与场景插图的组合方法》已更新:技术博客图文文章怎么做得不单一:封面、结构图与场景插图的组合方法 很多技术博客的正文其实不差,问题常常出在视觉层太单一。首页列表里大家都只有一张封面,点进去以后又是一大段连续文字,读者很难在几秒钟内判断这篇文章到底值不值得继续看。内容本身也许很扎实,但呈现方式没有把价值推出来。…
钱哆哆♥官方正规流量卡♥1 个月前
你和学霸的区别就是,你所有的灵光一闪,都是他的基本题型。