首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Langchain 和 RAG 最佳实践

Langchain 和 RAG 最佳实践

原创
作者头像
timerring
修改2025-06-06 01:03:47
修改2025-06-06 01:03:47
24410
代码可运行
举报
运行总次数:0
代码可运行

这是一篇关于LangChain和RAG的快速入门文章,主要参考了由Harrison Chase和Andrew Ng讲授的Langchain chat with your data课程。你可以在rag101仓库中查看完整代码。本文翻译自我的英文博客,最新修订内容可随时参考:LangChain 与 RAG 最佳实践

LangChain与RAG最佳实践

简介

LangChain

LangChain是用于构建大语言模型(LLM)应用的开源开发框架,其组件如下:

提示(Prompt)
  • 提示模板(Prompt Templates):用于生成模型输入。
  • 输出解析器(Output Parsers):处理生成结果的实现。
  • 示例选择器(Example Selectors):选择合适的输入示例。
模型(Models)
  • 大语言模型(LLMs)
  • 聊天模型(Chat Models)
  • 文本嵌入模型(Text Embedding Models)
索引(Indexes)
  • 文档加载器(Document Loaders)
  • 文本分割器(Text Splitters)
  • 向量存储(Vector Stores)
  • 检索器(Retrievers)
链(Chains)
  • 可作为其他链的构建块。
  • 提供超过20种特定应用的链。
代理(Agents)
  • 支持5种代理帮助语言模型使用外部工具。
  • 代理工具包(Agent Toolkits):提供超过10种实现,代理通过特定工具执行任务。

RAG流程

整个RAG流程基于向量存储加载(Vector Store Loading)和检索增强生成(Retrieval-Augmented Generation)。

向量存储加载

从不同来源加载数据,拆分并将其转换为向量嵌入。

检索增强生成
  1. 用户输入查询(Query)后,系统将从向量存储中检索最相关的文档片段(Relevant Splits)。
  2. 检索到的相关片段将组合成一个提示(Prompt),该提示会与上下文一起传递给大语言模型(LLM)。
  3. 最后,语言模型将根据检索到的片段生成答案并返回给用户。

加载器(Loaders)

可以使用加载器处理不同种类和格式的数据。有些是公开的,有些是专有的;有些是结构化的,有些是非结构化的。

一些有用的库:

  • PDF:pypdf
  • YouTube音频:yt_dlp、pydub
  • 网页:beautifulsoup4

更多加载器可查看官方文档。完整代码可在这里查看。

PDF

现在进行实践:

首先安装库:

代码语言:bash
复制
pip install langchain-community 
pip install pypdf

可以在以下代码中查看演示:

代码语言:python
代码运行次数:0
运行
复制
from langchain.document_loaders import PyPDFLoader

# 实际上,langchain调用pypdf库来加载pdf文件
loader = PyPDFLoader("ProbRandProc_Notes2004_JWBerkeley.pdf")
pages = loader.load()

print(type(pages))
# <class 'list'>
print(len(pages))
# 打印总页数

# 以第一页为例
page = pages[0]
print(type(page))
# <class 'langchain_core.documents.base.Document'>

# 页面内容包括:
# 1. page_content:页面文本内容
# 2. metadata:页面元数据

print(page.page_content[0:500])
print(page.metadata)

网页加载器(Web Base Loader)

同样先安装库:

代码语言:bash
复制
pip install beautifulsoup4

WebBaseLoader基于beautifulsoup4库。

代码语言:python
代码运行次数:0
运行
复制
from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader("https://zh.d2l.ai/")
pages = loader.load()
print(pages[0].page_content[:500])

# 也可以使用json进行后处理
# import json
# convert_to_json = json.loads(pages[0].page_content)

分割器(Splitters)

将文档分割成更小的块,同时保留有意义的关系。

为何分割?

  • GPU限制:参数超过10亿的GPT模型,前向传播无法处理如此大的参数,因此分割是必要的。
  • 计算更高效。
  • 某些模型有固定的序列长度限制。
  • 更好的泛化能力。

然而,分割点可能会丢失一些信息,因此分割时应考虑语义。

分割器类型

  • 字符文本分割器(CharacterTextSplitter)
  • Markdown标题文本分割器(MarkdownHeaderTextSplitter)
  • Token文本分割器(TokenTextsplitter)
  • SentenceTransformersTokenTextSplitter
  • 递归字符文本分割器(RecursiveCharacterTextSplitter):递归尝试按不同字符分割,找到可行的分割方式。
  • 语言分割器(Language):适用于C++、Python、Ruby、Markdown等语言
  • NLTK文本分割器(NLTKTextSplitter):使用NLTK(自然语言工具包)分割句子
  • Spacy文本分割器(SpacyTextSplitter):使用Spacy分割句子

更多信息请查看文档

字符文本分割器与递归字符文本分割器示例

完整代码可在这里查看。

代码语言:python
代码运行次数:0
运行
复制
from langchain.text_splitter import RecursiveCharacterTextSplitter, CharacterTextSplitter

example_text = """撰写文档时,作者会使用文档结构对内容进行分组。这可以向读者传达哪些想法是相关的。例如,紧密相关的想法在句子中。相似的想法在段落中。段落构成文档。\n\n 段落通常用一个或两个回车符分隔。回车符就是你在这个字符串中看到的“反斜杠n”。句子以句号结尾,但同时也有空格。单词之间用空格分隔。"""

c_splitter = CharacterTextSplitter(
    chunk_size=450,  # 块大小
    chunk_overlap=0,  # 块重叠部分,可与前一个块共享
    separator=' '
)
r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=450,
    chunk_overlap=0, 
    separators=["\n\n", "\n", " ", ""]  # 分隔符优先级
)

print(c_splitter.split_text(example_text))
# 按450个字符分割
print(r_splitter.split_text(example_text))
# 先按\n\n分割

向量存储与嵌入(Vectorstores and Embeddings)

回顾RAG流程:

优势:

  1. 提高查询的准确性:查询相似块时,准确性更高。
  2. 提高查询效率:查询相似块时减少计算量。
  3. 提高查询的覆盖范围:块可以覆盖文档的每个点。
  4. 便于嵌入处理。

嵌入(Embeddings)

如果两个句子含义相似,那么它们在高维语义空间中会更接近。

向量存储(Vector Stores)

将每个块存储在向量存储中。当用户查询时,查询会被嵌入,然后找到最相似的向量,即这些块的索引,然后返回这些块。

实践

嵌入

完整代码可在这里查看。

首先安装库:

chromadb是一个轻量级向量数据库。

代码语言:bash
复制
pip install chromadb

我们需要一个好的嵌入模型,你可以选择你喜欢的。参考文档

这里我使用ZhipuAIEmbeddings,因此需要安装库:

代码语言:bash
复制
pip install zhipuai

测试代码如下:

代码语言:python
代码运行次数:0
运行
复制
from langchain_community.embeddings import ZhipuAIEmbeddings

embed = ZhipuAIEmbeddings(
    model="embedding-3",
    api_key="输入你自己的api key"
)

input_texts = ["这是一个测试查询1。", "这是一个测试查询2。"]
print(embed.embed_documents(input_texts))
向量存储

完整代码可在这里查看。

代码语言:bash
复制
pip install langchain-chroma

然后我们可以使用Chroma来存储嵌入。

代码语言:python
代码运行次数:0
运行
复制
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import ZhipuAIEmbeddings

# 加载网页
loader = WebBaseLoader("https://en.d2l.ai/")
docs = loader.load()

# 分割文本为块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1500,
    chunk_overlap=150
)
splits = text_splitter.split_documents(docs)
# print(len(splits))

# 设置嵌入模型
embeddings = ZhipuAIEmbeddings(
    model="embedding-3",
    api_key="你的api key"
)

# 设置持久化目录
persist_directory = r'.'

# 创建向量数据库
vectordb = Chroma.from_documents(
    documents=splits,
    embedding=embeddings,
    persist_directory=persist_directory
)
# print(vectordb._collection.count())

# 查询向量数据库
question = "Recurrent"
docs = vectordb.similarity_search(question, k=3)
# print(len(docs))
print(docs[0].page_content)

然后你可以在特定目录中找到chroma.sqlite3文件。

检索(Retrieval)

这部分是RAG的核心部分。

上一部分我们已经使用了similarity_search方法。除此之外,我们还有其他方法:

  • 基本语义相似性(Basic semantic similarity)
  • 最大边际相关(Maximum Marginal Relevance, MMR)
  • 元数据(Metadata)
  • LLM辅助检索(LLM Aided Retrieval)

相似性搜索(Similarity Search)

相似性搜索计算查询向量与数据库中所有文档向量的相似性,以找到最相关的文档。相似性度量方法包括余弦相似度欧氏距离,它们可以有效度量两个向量在高维空间中的接近程度。

然而,仅依赖相似性搜索可能导致多样性不足,因为它只关注查询与内容的匹配,忽略了不同信息之间的差异。在某些应用中,尤其是需要覆盖多个不同方面的信息时,最大边际相关(MMR)的扩展方法可以更好地平衡相关性和多样性。

实践

实践部分见上一部分。

最大边际相关(MMR, Maximum Marginal Relevance)

仅检索最相关的文档可能会忽略信息的多样性。例如,如果只选择最相似的响应,结果可能非常相似甚至包含重复内容。MMR的核心思想是平衡相关性和多样性,即选择与查询最相关的信息,同时确保信息在内容上具有多样性。通过减少不同片段之间的信息重复,MMR可以提供更全面和多样化的结果集。

MMR的流程如下:

  1. 查询向量存储:首先使用嵌入模型将查询转换为向量。
  2. 选择fetch_k个最相似的响应:从向量存储中找到前k个最相似的向量。
  3. 在这些响应中选择k个最具多样性的:通过计算每个响应之间的相似性,MMR会更倾向于选择彼此差异更大的结果,从而增加信息的覆盖范围。这个过程确保返回的结果不仅“最相似”,而且“互补”。

关键参数是lambda,它是相关性和多样性的权重:

  • 当lambda接近1时,MMR更像相似性搜索。
  • 当lambda接近0时,MMR更像随机搜索。
实践

我们可以调整“向量存储”部分的代码以使用MMR方法。完整代码在retrieval/mmr.py文件中。

代码语言:python
代码运行次数:0
运行
复制
# 使用MMR查询向量数据库
question = "How the neural network works?"
# 获取8个最相似的文档,然后选择2个最相关且最具多样性的文档
docs_mmr = vectordb.max_marginal_relevance_search(question, fetch_k=8, k=2)
print(docs_mmr[0].page_content[:100])
print(docs_mmr[1].page_content[:100])

元数据(Metadata)

当我们的查询有特定条件时,可以使用元数据来过滤结果。例如,页码、作者、时间戳等信息,这些信息可以在检索时作为过滤条件,从而提高查询的准确性。

实践

完整代码可在这里查看。

从另一个网站添加新文档,然后过滤特定网站的结果。

代码语言:python
代码运行次数:0
运行
复制
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import ZhipuAIEmbeddings

# 加载网页
loader = WebBaseLoader("https://en.d2l.ai/")
docs = loader.load()

# 分割文本为块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1500,
    chunk_overlap=150
)
splits = text_splitter.split_documents(docs)
# print(len(splits))

# 设置嵌入模型
embeddings = ZhipuAIEmbeddings(
    model="embedding-3",
    api_key="你的_api_key"
)

# 设置持久化目录
persist_directory = r'.'

# 创建向量数据库
vectordb = Chroma.from_documents(
    documents=splits,
    embedding=embeddings,
    persist_directory=persist_directory
)
# print(vectordb._collection.count())

# 从另一个网站添加新文档
new_loader = WebBaseLoader("https://www.deeplearning.ai/")
new_docs = new_loader.load()

# 分割新文档文本为块
new_splits = text_splitter.split_documents(new_docs)

# 添加到现有向量数据库
vectordb.add_documents(new_splits)

# 获取所有文档
all_docs = vectordb.similarity_search("What is the difference between a neural network and a deep learning model?", k=20)

# 打印文档的元数据
for i, doc in enumerate(all_docs):
    print(f"Document {i+1} metadata: {doc.metadata}")
# Document 1 metadata: {'language': 'en', 'source': 'https://en.d2l.ai/', 'title': 'Dive into Deep Learning — Dive into Deep Learning 1.0.3 documentation'}
# Document 2 metadata: {'language': 'en', 'source': 'https://en.d2l.ai/', 'title': 'Dive into Deep Learning — D

LLM辅助检索(LLM Aided Retrieval)

该方法利用语言模型自动解析句子语义,提取过滤信息。

SelfQueryRetriever

LangChain提供的SelfQueryRetriever模块可通过语言模型分析问题语义,提取向量搜索的搜索词文档元数据的过滤条件

  • 例如,对于问题“除了维基百科,还有哪些健康网站?”,SelfQueryRetriever可推断“维基百科”为过滤条件,即排除来源为维基百科的文档。
实践
代码语言:python
代码运行次数:0
运行
复制
from langchain.llms import OpenAI  
from langchain.retrievers.self_query.base import SelfQueryRetriever  
from langchain.chains.query_constructor.base import AttributeInfo  

llm = OpenAI(temperature=0)  

metadata_field_info = [  
    AttributeInfo(  
        name="source",  
        description="文档片段的来源,应为`docs/loaders.pdf`、`docs/text_splitters.pdf`或`docs/vectorstores.pdf`中的一个",  
        type="string",  
    ),  
    AttributeInfo(  
        name="page",  
        description="文档所在的页码",  
        type="integer",  
    ),  
]  

document_content_description = "检索增强生成相关的课程讲义"  
retriever = SelfQueryRetriever.from_llm(  
    llm,  
    vectordb,  
    document_content_description,  
    metadata_field_info,  
    verbose=True  
)  

question = "第二讲的主题是什么?"  

压缩(Compression)

向量检索返回的完整文档片段可能包含大量冗余信息,LangChain的“压缩”机制通过以下步骤优化:

  1. 标准向量检索获取候选文档。
  2. 使用语言模型基于查询语义压缩文档,仅保留相关部分。
    • 例如,查询“蘑菇的营养价值”时,压缩后仅保留与“营养价值”相关的句子。
实践
代码语言:python
代码运行次数:0
运行
复制
from langchain.retrievers import ContextualCompressionRetriever  
from langchain.retrievers.document_compressors import LLMChainExtractor  

def pretty_print_docs(docs):  
    print(f"\n{'-' * 100}\n".join([f"Document {i+1}:\n\n" + d.page_content for i, d in enumerate(docs)]))  

llm = OpenAI(temperature=0)  
compressor = LLMChainExtractor.from_llm(llm)  
compression_retriever = ContextualCompressionRetriever(  
    base_compressor=compressor,  
    base_retriever=vectordb.as_retriever()  
)  
question = "第二讲的主题是什么?"  
compressed_docs = compression_retriever.get_relevant_documents(question)  
pretty_print_docs(compressed_docs)  

问答(Question Answering)

流程

  1. 从向量存储中检索多个相关文档。
  2. 压缩相关片段以适应LLM上下文,生成系统提示(System Prompt)并整合用户问题。
  3. 将信息传递给LLM生成答案。

RetrievalQA链

优势
  • 提高答案准确性:结合检索结果与LLM生成能力。
  • 支持知识库实时更新:依赖向量存储数据,可动态更新。
  • 减轻模型记忆负担:通过外部知识减少对模型内部参数的依赖。
其他方法
  • Map-Reduce:将文档分块,每个块独立生成答案后合并(适合大量文档并行处理)。
  • Refine:基于首个块生成初始答案,后续块逐步优化(适合高质量答案生成)。
  • Map-Rerank:分块生成答案后按相关性排序,取最高分结果(适合精准匹配场景)。
实践

完整代码见这里

代码语言:python
代码运行次数:0
运行
复制
from langchain_community.chat_models import ChatZhipuAI  
from langchain.chains import RetrievalQA  
from langchain.prompts import PromptTemplate  

loader = WebBaseLoader("https://en.d2l.ai/")  
docs = loader.load()  
splits = RecursiveCharacterTextSplitter(chunk_size=1500).split_documents(docs)  
vectordb = Chroma.from_documents(splits, ZhipuAIEmbeddings(api_key="your_key"))  

chat = ChatZhipuAI(model="glm-4-flash", api_key="your_key")  
template = """基于以下上下文回答问题:\n{context}\n问题:{question}\n答案:"""  
qa_chain = RetrievalQA.from_chain_type(  
    chat,  
    retriever=vectordb.as_retriever(),  
    chain_type_kwargs={"prompt": PromptTemplate.from_template(template)}  
)  
result = qa_chain({"query": "这本书的主题是什么?"})  
print(result["result"])  

对话检索链(Conversational Retrieval Chain)

流程

结合对话历史(Memory)与检索能力,实现上下文感知的多轮交互:

  1. 对话历史:记录用户对话上下文。
  2. 问题:用户提问传递给检索模块。
  3. 检索器:从向量存储获取相关内容。
  4. 提示整合:将问题、检索结果与对话历史组合为LLM输入。
  5. LLM生成答案:基于完整上下文生成响应。

记忆模块(Memory)

ConversationBufferMemory存储对话历史,支持多轮交互:

代码语言:python
代码运行次数:0
运行
复制
from langchain.memory import ConversationBufferMemory  
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)  
实践

完整代码见这里

代码语言:python
代码运行次数:0
运行
复制
qa = ConversationalRetrievalChain.from_llm(chat, vectordb.as_retriever(), memory=memory)  
# 第一轮提问  
result = qa.invoke({"question": "推荐一本深度学习书籍"})  
print(result['answer'])  
# 第二轮追问  
result = qa.invoke({"question": "这本书的作者是谁?"})  # 自动关联历史对话  
print(result['answer'])  

最佳实践总结

  1. 数据预处理
    • 使用RecursiveCharacterTextSplitter保留语义,避免硬性切割。
    • 代码文档采用LanguageTextSplitter按语法结构分割。
  2. 向量存储选择
    • 小规模数据:Chroma(本地部署便捷)。
    • 大规模数据:FAISS(高效检索)或Pinecone(云端扩展)。
  3. 检索策略优化
    • 复杂查询结合MMR与元数据过滤,平衡相关性与多样性。
    • 使用SelfQueryRetriever自动解析用户意图,减少手动配置成本。
  4. 对话系统设计
    • 采用ConversationalRetrievalChain维护对话历史,提升交互连贯性。
    • 限制记忆长度(如ConversationTokenBufferMemory),避免上下文膨胀。

资源链接

通过合理组合LangChain组件与RAG流程,可高效构建基于自定义知识库的智能问答系统,显著提升LLM在垂直领域的应用效果。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • LangChain与RAG最佳实践
    • 简介
      • LangChain
      • RAG流程
    • 加载器(Loaders)
      • PDF
      • 网页加载器(Web Base Loader)
    • 分割器(Splitters)
      • 为何分割?
      • 分割器类型
      • 字符文本分割器与递归字符文本分割器示例
    • 向量存储与嵌入(Vectorstores and Embeddings)
      • 嵌入(Embeddings)
      • 向量存储(Vector Stores)
      • 实践
    • 检索(Retrieval)
      • 相似性搜索(Similarity Search)
      • 最大边际相关(MMR, Maximum Marginal Relevance)
      • 元数据(Metadata)
      • LLM辅助检索(LLM Aided Retrieval)
      • 压缩(Compression)
    • 问答(Question Answering)
      • 流程
      • RetrievalQA链
    • 对话检索链(Conversational Retrieval Chain)
      • 流程
      • 记忆模块(Memory)
    • 最佳实践总结
    • 资源链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档