基于 LangChain 搭建最小化 RAG 系统实战
检索增强生成(Retrieval-Augmented Generation, RAG)是近年来大模型应用中最核心的技术架构之一。它通过引入外部知识库,有效缓解了大模型的知识幻觉问题。本文将利用 LangChain 框架,从零开始搭建一个最简化的 RAG 系统,解析其核心原理与实现细节。
环境准备
建议使用 Python 3.9+ 环境。LangChain 版本更新较快,建议安装最新稳定版以避免兼容性问题。
主要依赖包包括:
langchain:核心框架chromadb:向量数据库sentence-transformers或huggingface_hub:用于加载 BGE 嵌入模型PyPDF2或unstructured:视数据格式而定
RAG 核心原理
RAG 流程主要分为三个步骤:
- 索引建立:将非结构化文本数据通过向量化模型转换为向量,并存储到向量数据库中。
- 检索:根据用户查询,在向量数据库中查找语义最相关的 Top-K 片段。
- 生成:将检索到的上下文与用户问题拼接成提示词,输入给大语言模型生成最终答案。
该架构的核心在于利用向量相似度匹配解决信息检索的准确性问题,同时利用大模型的生成能力保证回答的自然流畅。
索引建立
我们使用 Chroma 作为轻量级向量数据库,调用 BGE (BAAI General Embedding) 模型完成文本向量化。原始数据采用 Markdown 格式,便于直接使用 TextLoader 加载。
代码实现
import os
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceBgeEmbeddings
# 配置模型路径
BGE_MODEL_PATH = "BAAI/bge-large-zh"
root_dir = "./data" # 替换为你的数据目录
def extract_file_dirs(directory):
file_paths = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(".md"):
fp = os.path.join(root, file)
file_paths.append(fp)
return file_paths
# 1. 加载文档
files = extract_file_dirs(root_dir)
loaders = [TextLoader(f, encoding='utf-8') for f in files]
docs = []
for l in loaders:
docs.extend(l.load())
# 2. 文本切片
# chunk_size 控制每个片段的大小,chunk_overlap 控制重叠部分以保留上下文连贯性
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
documents = text_splitter.split_documents(docs)
# 3. 向量化与存储
embedding = HuggingFaceBgeEmbeddings(model_name=BGE_MODEL_PATH)
vectorstore = Chroma.from_documents(
documents=documents,
embedding=embedding,
persist_directory="./vectorstore"
)
注意:实际生产中需确保 model_factory 中的 LLM 客户端已正确初始化。此处仅展示向量库构建逻辑。
检索模块
将向量数据库转换为检索器(Retriever)是 LangChain 的标准用法。通过 as_retriever() 方法,可以方便地调用检索接口。
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
query = "如何快速找到最有价值的内容?"
docs = retriever.invoke(query)
for doc in docs:
print(doc.page_content)
这里使用了 invoke 方法替代旧版的 get_relevant_documents,这是 LangChain 较新版本的推荐写法。search_kwargs 允许自定义检索参数,如返回数量 k 或搜索类型(similarity, mmr 等)。
生成模块
定义提示词模板,将检索到的上下文与用户问题结合。使用 LangChain Expression Language (LCEL) 构建链式处理流程。
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 假设 model 已初始化为具体的 LLM 实例
# from model_factory import yi_llm
# model = yi_llm
template = """Answer the question based only on the following context:
{context}
Question: {question},请用中文输出答案。
"""
prompt = ChatPromptTemplate.from_template(template)
def format_docs(docs):
return "\n\n".join([d.page_content for d in docs])
# 构建 Chain
chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
response = chain.invoke(query)
print(response)
LCEL 的优势在于其流式处理能力与模块化设计,便于调试和组合不同的组件。RunnablePassthrough 用于透传用户问题,format_docs 负责格式化检索结果。
效果评估与优化
在实际应用中,简单的 RAG 系统可能面临以下挑战:
- 检索精度不足:如果切片过大或过小,都会影响语义匹配。建议尝试不同的
chunk_size或使用元数据过滤。 - 大模型幻觉:即使有上下文,模型仍可能编造信息。可通过在 Prompt 中强调'若不知道则回答不知道'来缓解。
- 延迟问题:向量检索 + 大模型生成存在串行延迟。可考虑异步处理或缓存常用查询。
优化方向
- 混合检索:结合关键词检索(BM25)与向量检索,提升召回率。
- 重排序(Re-ranking):对初步检索结果进行精细打分,选取最相关片段。
- 查询改写:对用户问题进行扩展或重写,使其更适合向量空间匹配。
总结
本文演示了如何使用 LangChain、Chroma 和 BGE 模型构建基础的 RAG 系统。虽然示例代码较为精简,但涵盖了从数据加载、向量化、检索到生成的完整链路。开发者可根据具体业务场景,引入更复杂的检索策略与模型微调方案,以提升系统的鲁棒性与实用性。
在实际部署前,请务必检查所有依赖包的版本兼容性,并确保敏感数据(如 API Key)通过环境变量管理,避免硬编码在代码中。


