用LangChain构建RAG应用:从零实现本地知识库问答系统

用LangChain构建RAG应用:从零实现本地知识库问答系统
用LangChain构建RAG应用:从零实现本地知识库问答系统

为什么需要RAG架构?

大语言模型(LLM)虽然在自然语言处理上表现卓越,但存在两个致命短板:知识过时幻觉问题。模型只能基于训练数据中的信息作答,无法感知实时动态或私域数据。RAG正是解决这一困境的工业级方案:它将信息检索与文本生成有机结合,先从外部知识库中检索出与用户查询最相关的文本片段,再将其作为上下文交给LLM生成精准回答。

系统架构概览

在动手编码之前,我们需要清晰的架构认知。一个标准的本地RAG系统由以下四个核心组件构成:

  1. 文档加载器(Loader):读取本地磁盘中的PDF、TXT、Markdown等各类格式文件,将其转换为纯文本内容。
  2. 文本分割器(Splitter):由于大模型上下文窗口有限,需要将长篇文档按照固定块大小和重叠度切分为若干短文本块(Chunk)。
  3. 向量化与存储(Vector Store):使用Embedding模型将文本块转换为语义向量,并写入向量数据库(如FAISS、Chroma)以便后续快速相似性检索。
  4. 检索与生成链路(Retrieval + Generation):将用户查询向量化后在库中检索TopK最相似的文本块,拼接为Prompt后发给LLM生成最终回复。

环境准备与依赖安装

建议使用Python 3.9以上版本,创建虚拟环境后执行以下安装命令:

pip install langchain langchain-community langchain-openai
pip install chromadb faiss-cpu
pip install pypdf unstructured

本教程使用开源Embedding模型BAAI/bge-small-zh-v1.5保证中文语义理解效果,并搭配OpenAI格式兼容的本地LLM服务(如Ollama部署的Qwen模型),实现完全本地化的知识库问答,敏感数据不出内网。

第一步:加载与切分文档

我们首先使用DirectoryLoader递归加载本地文档目录,并通过RecursiveCharacterTextSplitter对文档进行智能切分。

from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 加载所有markdown和txt文件
loader = DirectoryLoader("./knowledge_base/", glob="**/*.md")
documents = loader.load()
# 切分文本,块大小500字符,重叠50字符
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["nn", "n", "。", "!"],
)
chunks = text_splitter.split_documents(documents)
print(f"文档已切分为 {len(chunks)} 个文本块")

关键在于chunk_sizechunk_overlap的平衡:块太大则检索精度下降且浪费Token;块太小则易截断语义。重叠部分能避免关键信息恰好被切断导致检索遗漏。

第二步:向量化存储

采用HuggingFace的BGE系列中文Embedding模型,将文本块转换为768维向量。随后使用FAISS构建内存索引并持久化到本地磁盘。

from langchain_community.embeddings import HuggingFaceBgeEmbeddings
from langchain_community.vectorstores import FAISS
# 初始化Embedding模型,指定使用CPU运行
embedding_model = HuggingFaceBgeEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={"device": "cpu"},
    encode_kwargs={"normalize_embeddings": True},
)
# 构建向量库并保存到本地
vectorstore = FAISS.from_documents(chunks, embedding_model)
vectorstore.save_local("./faiss_index")

FAISS是Meta开源的相似性搜索库,在百万级向量规模下依然具备毫秒级检索速度。若涉及分布式协作,可替换为Chroma或Milvus。

第三步:构建检索问答链

使用LangChain的RetrievalQA链式封装,将向量库的检索器与本地LLM无缝串联。这里通过Ollama调用Qwen2.5-7B模型作为生成引擎。

from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# 配置本地模型
llm = Ollama(model="qwen2.5:7b", temperature=0.2)
# 深度定制Prompt模板,严格约束模型依据上下文作答
prompt_template = """基于以下已知信息,简洁专业地回答问题。
如果信息不足,请直接说明"知识库中未找到相关内容"。
禁止编造不存在的细节。
上下文:
{context}
问题:{question}
回答:"""
QA_CHAIN_PROMPT = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"],
)
# 组装RAG链路
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vectorstore.as_retriever(search_kwargs={"k": 4}),
    chain_type="stuff",
    chain_type_kwargs={"prompt": QA_CHAIN_PROMPT},
    return_source_documents=True,
)
# 执行查询
result = qa_chain.invoke("公司新员工的转正流程是什么?")
print(result["result"])
print("参考文档:", result["source_documents"])

此处search_kwargs={"k": 4}表示召回4个最相关的文本块。stuff链类型会将所有检索结果一次性塞入Prompt,适合文本量适中的场景;若检索结果较多,可改用map_reducerefine链型。

优化技巧与进阶方向

为使系统效果更优,可以从以下几个维度进行迭代优化:

  • 混合检索策略:将BM25稀疏检索与向量稠密检索融合,通过RRF算法重排,兼顾关键词匹配与语义匹配。
  • 重排序(Reranker):在检索后加入Cross-Encoder重排序模型(如bge-reranker-base),显著提升TopK结果的相关性精度。
  • 多轮对话记忆:引入LangChain的ConversationBufferMemory,将历史对话压缩后作为上下文,支撑追问式交互。
  • 增量更新机制:文档库需要动态增加内容,可在每次导入新文档时仅对新文档执行切分和向量化并追加至现有FAISS索引。

结语

本文完整演示了使用LangChain构建本地知识库问答系统的核心流程。从文档加载、文本切分到向量检索与生成回答,仅需不到100行代码即可搭建一个可用性极高的RAG应用。该架构已广泛落地于企业智能客服、法律条文咨询、科研文献问答等场景。建议读者在此基础上,根据私有数据的格式特征调整切分策略,并针对具体业务评估测试集,持续优化检索精度与回答质量。

阅读剩余
THE END