本地构建 RAG 应用的实践指南:使用 LangChain 进行检索增强生成
【精选优质专栏推荐】
- 《AI 技术前沿》 —— 紧跟 AI 最新趋势与应用
- 《网络安全新手快速入门(附漏洞挖掘案例)》 —— 零基础安全入门必看
- 《BurpSuite 入门教程(附实战图文)》 —— 渗透测试必备工具详解
- 《网安渗透工具使用教程(全)》 —— 一站式工具手册
- 《CTF 新手入门实战教程》 —— 从题目讲解到实战技巧
- 《前后端项目开发(新手必知必会)》 —— 实战驱动快速上手
每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

前言
检索增强生成(RAG)是一类系统,它通过将从文档库中检索到的知识上下文整合到传统语言模型(LLMs,无论大小)中,从而在用户查询时生成更加准确和相关的响应。
在这一背景下,LangChain 作为一个框架受到广泛关注,它简化了 RAG 应用的开发,提供了将 LLM 与外部数据源集成的编排工具,管理检索管道,并能够稳健且可扩展地处理不同复杂程度的工作流。
本文讲解如何使用 LangChain 构建一个简单的本地 RAG 应用,并在每一步详细说明所需的关键组件。为便于实践,本文将使用 Python 进行演示。
教程正文
下图展示了一个基础 RAG 系统的简化示意及其核心组件,我们将手动实现这些组件:

首先,我们需要安装必要的库和框架。LangChain 的最新版本可能需要 langchain-community,因此也应一并安装。FAISS 是一个用于向量数据库中高效相似性搜索的框架。为了加载和使用现有的 LLM,我们将使用 Hugging Face 的 transformers 和 sentence-transformers 库,其中后者提供了针对句子级别优化的预训练文本嵌入模型。
首先安装上述库:
pip install langchain langchain_community faiss-cpu sentence-transformers transformers
接着,添加必要的导入以开始编码:
from langchain.llms import HuggingFacePipeline
from langchain.chains import RetrievalQA
from langchain.chains.question_answering import load_qa_chain
from langchain.prompts import PromptTemplate
from transformers import pipeline
from langchain_community.vectorstores import FAISS
我们需要的第一个组件是一个知识库,通过向量数据库实现。该数据库存储将被 RAG 系统用于上下文检索的文本文档,信息通常以向量嵌入的形式编码。我们将使用一个小型集合,其中包含九份描述东亚和东南亚多个目的地的简短文档。该数据集以压缩的 .zip 文件形式提供。
下面的代码将在本地下载数据集、解压并提取文本文件:
import urllib.request
import zipfile
import os
zip_url = "https://github.com/gakudo-ai/open-datasets/raw/refs/heads/main/asia_documents.zip"
zip_path = "asia_documents.zip"
extract_folder = "asia_txt_files"
print("Downloading zip file...")
urllib.request.urlretrieve(zip_url, zip_path)
print("Download complete!")
print("Extracting files...")
os.makedirs(extract_folder, exist_ok=True)
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(extract_folder)
print(f"Files extracted to: {extract_folder}")
print("Extracted files:")
print(os.listdir(extract_folder))
现在我们已有一个“原始”的文本文档知识库,需要将整个数据拆分为若干块并转换为向量嵌入。
在 RAG 系统中,分块(chunking) 非常重要,因为 LLM 存在 token 限制。高效检索相关信息需要将文档切分为可管理的片段,同时保持上下文完整性,这对于 LLM 生成恰当响应至关重要。
关于将文档转化为分块并存入适合 RAG 应用的向量数据库:FAISS 将负责高效存储和索引向量嵌入,从而实现准确且高效的基于相似度的搜索。其 as_retriever() 方法便于与 LangChain 的检索方法集成,使相关文档块能够被动态检索,以优化 LLM 的响应。
在创建嵌入时,我们使用了专门为此任务训练的 Hugging Face 模型,即 all-MiniLM-L6-v2。
import os
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
folder_path = "asia_txt_files"
documents = []
for filename in os.listdir(folder_path):
if filename.endswith(".txt"):
file_path = os.path.join(folder_path, filename)
loader = TextLoader(file_path)
documents.extend(loader.load())
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=100)
docs = text_splitter.split_documents(documents)
embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vectorstore = FAISS.from_documents(docs, embedding_model)
retriever = vectorstore.as_retriever()
在上一段代码的结尾,我们初始化了 RAG 的关键组件之一:retriever。要让它完全发挥作用,当然需要将 RAG 的其余部分拼接起来。
接下来,将全面发挥 LangChain 的编排能力。下面的代码首先定义了一个用于文本生成的 LLM 管道,使用 Hugging Face 的 Transformers 库和 GPT-2 模型。
参数说明:
-
device=0:确保模型在 GPU 上运行(如果可用),显著提升推理速度。如果在 notebook 环境中运行,建议保持该设置。
-
max_new_tokens:用于控制生成响应的长度,避免过长或被截断,同时保持连贯性。之后可以尝试调整该值,观察其对生成结果质量的影响。
接下来,我们定义一个提示模板,即一个结构化且格式化的提示,能够动态包含检索到的上下文,确保模型生成基于相关知识的响应。这一步对应之前讨论的 RAG 架构中的增强模块。
然后,我们定义 LLM 链,这是 LangChain 的关键组件,用于协调 LLM 与包含增强输入的提示模板之间的交互,确保查询-响应流程结构化。
最后,使用 RetrievalQA 类初始化问答(QA)对象。在 LangChain 中,在创建链之后指定目标语言任务类型,是定义适用于特定任务(例如问答)的应用的关键。上述类将检索器(retriever)与 LLM 链连接起来。现在,我们已经将所有 RAG 构建模块完整拼接在一起。
prompt_template = "Answer the following question based on the provided context: {context}\n\nQuestion: {query}\nAnswer:"
prompt = PromptTemplate(input_variables=["query", "context"], template=prompt_template)
llm_chain = LLMChain(llm=llm, prompt=prompt)
retrieval_qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
verbose=True
)
现在只剩最后一步,定义一个辅助函数,用于确保增强上下文保持在 LLM 指定的 token 限制内,并在必要时截断输入。
def truncate_to_max_tokens(text, max_tokens=500):
tokens = text.split()
if len(tokens) > max_tokens:
return " ".join(tokens[:max_tokens])
return text
现在用一个关于亚洲美食的问题测试我们的 RAG 系统。观察其表现(注意,不要期望过高,因为我们使用的是小型、可管理的预训练模型用于学习目的,输出质量不会达到 ChatGPT 水平)。之后,我们可以尝试将下面注释行中的 1 改为 2 或更多,以调整用于增强的 top K 检索文档数量。
query = "What are the best Asian cuisine dishes?"
# 重要:默认只使用 top-1 文档
retrieved_docs = retriever.get_relevant_documents(query)[:1]
context = " ".join([doc.page_content for doc in retrieved_docs])
context = truncate_to_max_tokens(context, max_tokens=500)
response = retrieval_qa.run(query)
print("Answer:", response)
这是一个示例响应:
问题:What are the best Asian cuisine dishes?
参考答案:许多人希望食用“酸味”食物以获得良好效果。这包括许多受欢迎的亚洲菜肴,如辣米饭、斑豆、酱油等。许多亚洲食品公司将甜味(酱油、酸奶油以及西瓜、卷心菜和姜的组合)作为主要成分,这使得许多人能够享受传统的中国、日本和越南美食,这些菜肴都非常美味。
越南美食通常更受越南学生和专业人士欢迎,他们希望了解本国文化,可能会花时间学习或参加课程以取得优异成绩。
日本料理则呈现多样化。日本料理中最受欢迎的菜肴通常是炒饭(鼘雅)、炒蔬菜(鞊雅)和海藻(雅枊),但还有其他……
总体表现尚可。文本中部分信息可能存在不一致,但整体来看,系统合理利用了从原始文档数据集中检索到的信息,并结合了 GPT-2 的有限能力,这些能力受到其训练时通用数据集的影响。
总结
我们已经初步了解了如何在本地构建第一个 RAG 应用,具体是依靠 LangChain 来编排其主要组件,主要包括检索器(retriever)和语言模型。
通过这些步骤和技术,我们可以构建符合自身需求的本地 RAG 应用,应用潜力巨大。
更多推荐

所有评论(0)