注:本文是Langchain框架的学习笔记;不是教程!不是教程!内容可能有所疏漏,欢迎交流指正。后续将持续更新学习笔记,分享我的学习心得和实践经验。
检索增强生成(Retrieval-Augmented Generation, RAG)是一种结合了检索系统和生成式AI的混合架构,旨在解决大语言模型(LLM)的知识时效性和幻觉问题。RAG通过从外部知识库检索相关信息,然后将这些信息作为上下文提供给LLM,从而生成更准确、更可靠的回答。
知识时效性:LLM的知识在预训练后就固定了,而RAG可以访问最新信息
减少幻觉:通过提供事实依据,降低模型编造信息的可能性
知识专业性:可以接入专业领域文档,增强模型在特定领域的表现
透明可解释:回答可以追溯到具体的信息来源
降低成本:相比模型微调,RAG实现更为经济高效
RAG系统通常由以下几个核心组件构成:
文档处理系统:负责加载、处理和分块文档
向量化模块:将文本转换为向量表示
向量数据库:存储和索引文本向量
检索器:根据查询检索相关文档
生成模块:结合检索结果和用户查询生成回答
索引阶段:
加载文档并分割成适当大小的块
使用嵌入模型将每个文本块转换为向量
将向量和原文本存储在向量数据库中
查询阶段:
将用户问题转换为向量表示
在向量数据库中检索与问题最相似的文本块
将检索到的文本块与原始问题一起构建提示
将完整提示发送给LLM生成最终回答
文档加载是RAG系统的第一步,Langchain提供了丰富的文档加载器(Document Loaders)来处理各种格式的文档。
from langchain_community.document_loaders import PyPDFLoader, CSVLoader, TextLoader
# 加载PDF文档
pdf_loader = PyPDFLoader("knowledge_base/document.pdf")
pdf_docs = pdf_loader.load()
# 加载CSV文件
csv_loader = CSVLoader("knowledge_base/data.csv")
csv_docs = csv_loader.load()
# 加载纯文本文件
text_loader = TextLoader("knowledge_base/article.txt")
text_docs = text_loader.load()
# 合并所有文档
all_docs = pdf_docs + csv_docs + text_docs
文档分块是将长文档切分成较小的文本片段,以便于向量化和检索。合理的分块策略对RAG系统的性能至关重要。
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 创建文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个块的目标大小(字符数)
chunk_overlap=200, # 相邻块之间的重叠部分,有助于保持上下文连贯性
length_function=len, # 计算文本长度的函数
separators=["\n\n", "\n", " ", ""] # 分割文本的分隔符优先级
)
# 分割文档
chunks = text_splitter.split_documents(all_docs)
print(f"文档被分割成 {len(chunks)} 个块")
分块策略的关键考量
块大小:太大会导致检索精度下降,太小会丢失上下文
块重叠:适当重叠可以保持上下文连贯性
分隔符选择:基于文档结构选择合适的分隔符
保留元数据:确保每个块都保留来源信息
向量化是将文本转换为数值向量的过程,这些向量捕捉了文本的语义信息
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import FAISS
# 初始化嵌入模型
embeddings = OllamaEmbeddings()
# 创建向量数据库
vectorstore = FAISS.from_documents(
documents=chunks,
embedding=embeddings
)
# 持久化存储
vectorstore.save_local("./faiss_index")
嵌入模型是RAG系统的关键组件,它决定了文本如何被转换为向量表示,进而影响检索的准确性。
模型 | 维度 | 优势 | 劣势 | 适用场景 |
---|---|---|---|---|
text-embedding-3-small | 1536 | 高质量、多语言支持 | 收费、API依赖 | 通用场景、多语言 |
bge-large-zh | 1024 | 中文表现优秀、开源 | 计算资源需求大 | 中文内容为主的应用 |
ollama/nomic-embed-text | 768 | 本地部署、无需联网 | 精度略低于商业模型 | 本地应用、隐私敏感场景 |
# OpenAI嵌入
from langchain_community.embeddings import OpenAIEmbeddings
openai_embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# HuggingFace嵌入(本地)
from langchain_community.embeddings import HuggingFaceEmbeddings
hf_embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh")
# Ollama嵌入
from langchain_ollama import OllamaEmbeddings
ollama_embeddings = OllamaEmbeddings(model="deepseek-r1:1.5b")
语义理解能力:更强的语义理解能力可以提高检索相关性
维度大小:更高维度通常能捕获更丰富的语义信息
领域适应性:某些模型在特定领域(如医疗、法律)表现更佳
多语言支持:处理多语言内容的能力
计算效率:生成嵌入的速度和资源消耗
检索策略决定了如何从向量数据库中找到与用户查询最相关的文档。
# similarity_search: 简单相似度检索, 参数k: 返回前n个最相似的文档
docs = vectorstore.similarity_search(
query="什么是检索增强生成?",
k=3
)
# similarity_search_with_score: 带相似度分数的检索
docs_with_scores = vectorstore.similarity_search_with_score(
query="什么是检索增强生成?",
k=3
)
# 基于元数据过滤的检索
filtered_docs = vectorstore.similarity_search(
query="什么是检索增强生成?",
k=3,
filter={"source": "textbook", "year": "2023"}
)
通过LLM生成多个不同角度的查询,然后合并检索结果,提高召回率。
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_community.chat_models import ChatZhipuAI
# 初始化LLM
llm = ChatZhipuAI(temperature=0)
# 创建多查询检索器
multi_query_retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(),
llm=llm
)
# 执行检索
unique_docs = multi_query_retriever.get_relevant_documents(
query="什么是检索增强生成?"
)
结合关键词搜索和语义搜索的优势。
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
# 创建BM25检索器(关键词搜索)
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 3
# 创建向量检索器(语义搜索)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 创建混合检索器
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.5, 0.5]
)
# 执行检索
hybrid_docs = ensemble_retriever.get_relevant_documents(
query="什么是检索增强生成?"
)
先检索较多文档,然后使用LLM筛选最相关部分,减少无关信息
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
# 创建文档压缩器
compressor = LLMChainExtractor.from_llm(llm)
# 创建上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 8}),
base_compressor=compressor
)
# 执行检索
compressed_docs = compression_retriever.get_relevant_documents(
query="什么是检索增强生成?"
)
余弦相似度:最常用的向量相似度计算方法
欧氏距离:直接计算向量间的距离
点积:简单但有效的相似度计算
杰卡德相似度:适用于关键词匹配
重排序(Reranking):使用更复杂模型对初步检索结果重新排序
RAG和模型微调是增强LLM能力的两种主要方法,各有优缺点。
实现成本低:无需大规模计算资源
知识更新容易:只需更新知识库,无需重新训练模型
透明可解释:可以追溯信息来源
适应性强:可以根据不同问题动态检索相关知识
知识内化:知识被编码到模型参数中
推理速度快:无需额外检索步骤
上下文窗口限制小:不受检索文档长度限制
特定任务表现优异:可以针对特定任务优化
场景特点 | 推荐方法 | 原因 |
---|---|---|
知识频繁更新 | RAG | 只需更新知识库 |
需要引用来源 | RAG | 可追溯信息来源 |
领域知识极专业 | RAG | 可直接接入专业文档 |
资源有限 | RAG | 计算需求低 |
特定格式输出 | 微调 | 可训练特定输出格式 |
推理速度要求高 | 微调 | 无需检索步骤 |
任务高度特定 | 微调 | 可针对性优化 |
需要深度理解 | 微调+RAG | 结合两者优势 |
解决思路:
优化分块策略,确保语义完整性
使用更高质量的嵌入模型
实施多查询检索策略
添加重排序(Reranking)步骤
结合关键词和语义搜索
# 使用重排序器提高相关性
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
# 初始化Cohere重排序器
compressor = CohereRerank(top_n=3)
# 创建重排序检索器
rerank_retriever = ContextualCompressionRetriever(
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 10}),
base_compressor=compressor
)
# 执行检索
reranked_docs = rerank_retriever.get_relevant_documents(
query="什么是检索增强生成?"
)
解决思路:
实施文档压缩技术
使用摘要生成减少文本长度
采用层次化检索方法
选择具有更大上下文窗口的模型
# 使用LLM生成检索文档的摘要
from langchain.chains.summarize import load_summarize_chain
# 初始化摘要链
summarize_chain = load_summarize_chain(
llm,
chain_type="map_reduce"
)
# 对检索文档进行摘要
summary = summarize_chain.run(docs)
# 使用摘要构建最终提示
final_prompt = f"基于以下信息回答问题:\n\n{summary}\n\n问题: 什么是检索增强生成?"
解决思路:
指导LLM仅基于检索内容回答
实施事实验证机制
添加不确定性表达
使用结构化输出格式
# 构建防幻觉提示模板
from langchain.prompts import ChatPromptTemplate
template = """你是一个诚实的AI助手。请基于以下检索到的信息回答用户问题。
如果检索的信息不足以回答问题,请直接说"我没有足够的信息来回答这个问题",不要编造答案。
检索到的信息:
{context}
用户问题: {question}
回答:"""
prompt = ChatPromptTemplate.from_template(template)
解决思路:
使用多语言嵌入模型
实施跨语言检索策略
添加翻译步骤
构建语言特定的知识库
# 使用多语言嵌入模型
from langchain_community.embeddings import HuggingFaceEmbeddings
# 初始化多语言嵌入模型
multilingual_embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
)
# 创建多语言向量数据库
multilingual_vectorstore = Chroma.from_documents(
documents=chunks,
embedding=multilingual_embeddings,
persist_directory="./multilingual_db"
)
下面我们将构建一个完整的RAG系统原型,包括文档处理、向量存储、检索和生成等全流程。
# 安装必要的包
# pip install langchain langchain_community langchain_core pypdf
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
# 设置ollama服务
OLLAMA_BASE_URL = "http://localhost:11434"
MODEL_NAME = "deepseek-r1:1.5b"
# 加载PDF文档
loader = PyPDFLoader("knowledge_base/ai_textbook.pdf")
documents = loader.load()
# 文本分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ".", " ", ""]
)
chunks = text_splitter.split_documents(documents)
# 初始化嵌入模型
embeddings = OllamaEmbeddings(
base_url=OLLAMA_BASE_URL,
model=MODEL_NAME
)
# 创建向量数据库
vectorstore = FAISS.from_documents(
documents=chunks,
embedding=embeddings,
distance_strategy="COSINE" # 指定使用余弦相似度
)
# 创建检索器
retriever = vectorstore.as_retriever()
# 测试检索效果
retrieved_docs = retriever.invoke("什么是检索增强生成?")
print(f"检索到 {len(retrieved_docs)} 个文档")
# 初始化聊天模型
chat_model = ChatOllama(
base_url=OLLAMA_BASE_URL,
model=MODEL_NAME,
temperature=0.1 # 降低温度以获得更确定性的回答
)
# 聊天提示模板(ChatPromptTemplate)
chat_prompt = ChatPromptTemplate.from_messages([
("system", """你是一个只能回答基于提供文档的问题的助手。
请遵循以下规则:
1. 只使用提供的文档中的信息回答问题
2. 如果文档中没有相关信息,请回答"文档中没有提供这个信息"
3. 不需要添加自己的知识、观点或分析
4. 保持回答简洁、准确, 不包含任何额外的信息或过程内容
以下是参考文档内容:
{context}"""),
("human", "{question}")
])
# 创建 RAG 链
qa_chain = RetrievalQA.from_chain_type(
llm=chat_model,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={
"prompt": chat_prompt
}
)
# 测试问题
questions = [
"什么是检索增强生成?",
"RAG系统的主要组成部分是什么?",
"RAG和模型微调有什么区别?"
]
# 执行测试
for question in questions:
answer = qa_chain.invoke(question)
print(f"回答: {answer}")
# 简单评估函数
def evaluate_rag(question, expected_keywords):
answer = qa_chain.invoke(question)
score = sum([1 for keyword in expected_keywords if keyword.lower() in answer.lower()])
return {
"question": question,
"answer": answer,
"score": score,
"max_score": len(expected_keywords)
}
# 评估测试集
eval_questions = [
{
"question": "RAG系统如何减少幻觉问题?",
"expected_keywords": ["检索", "外部知识", "事实依据", "验证"]
},
{
"question": "什么场景适合使用RAG而非微调?",
"expected_keywords": ["知识更新", "透明度", "资源限制", "专业领域"]
}
]
# 执行评估
results = [evaluate_rag(**q) for q in eval_questions]
# 输出评估结果
for result in results:
print(f"\n问题: {result['question']}")
print(f"得分: {result['score']}/{result['max_score']}")
print("-" * 50)
print(f"回答: {result['answer']}")
print("=" * 80)