
检索增强生成(Retrieval-Augmented Generation, RAG)已成为当前大语言模型(LLM)落地的核心技术范式,它通过外部知识检索有效缓解了模型幻觉、知识滞后和私有数据不可见等问题。本文将从架构设计、关键组件、实现细节到性能优化,全面剖析RAG系统的构建过程。我们将使用Python生态中的LangChain、Chroma向量数据库、OpenAI API(可替换为本地模型),手把手实现一个可扩展的企业级问答系统,并深入探讨检索质量、上下文融合、多路召回等进阶优化策略。无论你是AI应用开发工程师还是架构师,本文都将为你提供从0到1的实践指南。
大语言模型虽具备强大的生成能力,但其内在知识受限于训练数据的截止时间,且无法访问企业私域文档。简单的“微调”方案成本高、迭代慢,而RAG则提供了一种轻量级、可解释的知识增强方式。RAG系统的工作原理可以概括为:
相比单纯的生成式问答,RAG能够实时引用最新信息、提供溯源依据,且易于更新知识库,因此在客服、医疗、法律、金融等领域迅速得到应用。
一个生产级RAG系统通常包含离线索引(Indexing)和在线查询(Query)两条流水线:
本文将以语义检索 + 生成的基础架构为起点,逐步引入优化策略。
组件 | 选型 | 理由 |
|---|---|---|
编程语言 | Python 3.10+ | 生态丰富,AI库支持最全面 |
LLM | OpenAI GPT-4 / Azure OpenAI / 本地Qwen-14B | 本文以OpenAI为例,可无缝切换至本地模型 |
Embedding模型 | text-embedding-ada-002 或 BAAI/bge-large-zh | 中文场景推荐BGE系列,本文使用OpenAI Embedding |
向量数据库 | Chroma(本地持久化) | 轻量、易用、支持相似度检索,生产可换Qdrant/Pinecone |
文档加载器 | LangChain Document Loaders | 支持PDF、Word、Markdown、网页等 |
文本分割器 | RecursiveCharacterTextSplitter | 语义感知的递归分块,保留段落边界 |
检索器 | 向量检索 + 最大边际相关性(MMR) | 提升检索多样性 |
重排序(可选) | Cohere Rerank / 交叉编码器 | 进一步提升精准度 |
框架 | LangChain + FAISS(可选) | 快速原型,模块化设计 |
pip install langchain langchain-community chromadb openai tiktoken pypdf faiss-cpu设置环境变量(或使用.env文件)
import os
os.environ["OPENAI_API_KEY"] = "your-api-key"以PDF为例,加载并切分:
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
loader = PyPDFLoader("knowledge_base.pdf")
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"文档切分为 {len(chunks)} 个片段")分块策略要点:
chunk_size 过小会导致上下文不完整,过大则检索颗粒度过粗。推荐500~1000 token。chunk_overlap 保持片段间语义连续性。使用OpenAI Embedding将文本片段转为向量,存入Chroma:
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
vectorstore.persist()若使用本地Embedding模型(如BGE),可替换为:
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
embeddings = HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-large-zh",
model_kwargs={"device": "cuda"},
encode_kwargs={"normalize_embeddings": True}
)基础向量检索:
retriever = vectorstore.as_retriever(
search_type="similarity", # 或 "mmr"
search_kwargs={"k": 4}
)MMR(最大边际相关性) 可在相关性与多样性间平衡,减少冗余
retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 4, "fetch_k": 20}
)生成器使用ChatOpenAI,并设计一个包含上下文和问题的提示模板:
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
template = """
你是一个智能助手,请基于以下上下文信息回答用户的问题。如果上下文不足以回答问题,请明确说明“根据现有资料无法回答”。请保持回答简洁且有条理,必要时可以分点列举。
上下文:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model="gpt-4", temperature=0)使用LangChain的create_retrieval_chain将检索器和生成器串联:
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
document_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, document_chain)也可以自定义链以实现更复杂的控制流。现在可以测试:
response = rag_chain.invoke({"input": "什么是RAG?"})
print(response["answer"])以下是一个完整的RAG类,方便封装使用:
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
class RAGSystem:
def __init__(self, persist_dir="./chroma_db", model_name="gpt-4"):
self.persist_dir = persist_dir
self.embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
self.llm = ChatOpenAI(model=model_name, temperature=0)
self.vectorstore = None
self.retriever = None
self.chain = None
def index_documents(self, file_paths):
"""加载并索引文档"""
all_docs = []
for path in file_paths:
loader = PyPDFLoader(path)
all_docs.extend(loader.load())
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)
chunks = splitter.split_documents(all_docs)
self.vectorstore = Chroma.from_documents(
documents=chunks,
embedding=self.embeddings,
persist_directory=self.persist_dir
)
self.vectorstore.persist()
self._build_retriever()
def _build_retriever(self, search_type="mmr", k=4):
if self.vectorstore is None:
self.vectorstore = Chroma(
persist_directory=self.persist_dir,
embedding_function=self.embeddings
)
self.retriever = self.vectorstore.as_retriever(
search_type=search_type,
search_kwargs={"k": k}
)
self._build_chain()
def _build_chain(self):
template = """
你是一个智能助手,请基于以下上下文信息回答用户的问题。如果上下文不足以回答问题,请明确说明“根据现有资料无法回答”。请保持回答简洁且有条理,必要时可以分点列举。
上下文:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
document_chain = create_stuff_documents_chain(self.llm, prompt)
self.chain = create_retrieval_chain(self.retriever, document_chain)
def query(self, question):
if self.chain is None:
raise ValueError("请先索引文档或加载已有向量库")
response = self.chain.invoke({"input": question})
return response["answer"]
# 使用示例
if __name__ == "__main__":
rag = RAGSystem()
rag.index_documents(["doc1.pdf", "doc2.pdf"])
answer = rag.query("介绍一下RAG系统的优缺点")
print(answer)上述基础实现虽能工作,但在生产环境中会遇到检索不准确、回答冗余、上下文过载等问题。以下是几种实战级优化手段:
单纯向量检索可能忽略关键词精确匹配。引入BM25或TF-IDF关键词检索,并与向量检索结果融合(如加权合并或RRF算法)。
LangChain支持EnsembleRetriever:
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 4
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vectorstore.as_retriever()],
weights=[0.4, 0.6]
)初步检索返回候选片段(如10个),使用交叉编码器或专用Rerank模型(如Cohere Rerank、BGE-reranker)对候选重新评分,只取前N个送入生成器。这能显著提升top结果的相关性。
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
compressor = CohereRerank(model="rerank-english-v2.0", top_n=3)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 10})
)用户问题可能口语化或模糊,可先用LLM改写为多个检索友好型查询(HyDE技术),再分别检索并合并结果。
HyDE(Hypothetical Document Embeddings):让LLM生成一个假设性答案,然后用该答案进行检索,能有效弥合问题与文档之间的语义鸿沟。
def hyde_retrieve(question):
hyde_prompt = "请生成一段关于以下问题的假设性回答,回答应尽可能详细:\n" + question
hypothetical_answer = llm.invoke(hyde_prompt).content
# 用假设答案的向量进行检索
docs = vectorstore.similarity_search(hypothetical_answer, k=4)
return docs当检索到的文档过长时,可对每篇文档进行摘要或提取关键句,以节省LLM上下文窗口。LangChain的LLMChainExtractor可以实现:
from langchain.retrievers.document_compressors import LLMChainExtractor
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever()
)RAG不仅支持单轮问答,还需结合对话历史进行上下文理解。可使用ConversationBufferMemory或RAGConversationChain:
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
qa_chain = ConversationalRetrievalChain.from_llm(
llm, retriever, memory=memory
)生产级系统需要持续评估检索质量和生成质量:
可利用RAGAS(RAG Assessment)框架自动化评估:
pip install ragasfrom ragas.metrics import faithfulness, answer_relevancy
from ragas import evaluate
# 准备测试数据集
dataset = {
"question": [...],
"answer": [...],
"contexts": [...],
"ground_truth": [...]
}
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy])本文从零开始构建了一个基于Python和LLM的RAG智能问答系统,覆盖了从文档索引到检索增强生成的全链路,并深入讨论了多路召回、重排序、查询改写等进阶优化技术。RAG作为连接大模型与私有数据的桥梁,已在实际业务中展现出巨大价值。
未来趋势包括:
希望本文能为你的RAG实践提供扎实的起点。欢迎在思否社区交流讨论,共同推动AI应用落地。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。