用LangChain实现RAG:从零搭建知识问答系统

用LangChain实现RAG:从零搭建知识问答系统
用LangChain实现RAG:从零搭建知识问答系统

RAG与LangChain:为何组合是AI开发者的利器

检索增强生成(Retrieval-Augmented Generation, RAG)是目前大模型落地中最受关注的范式之一。它通过将外部知识库(如文档、数据库)中的相关信息检索出来,作为上下文提供给生成模型,从而有效缓解大模型“幻觉”问题,并支持基于私有数据的问答。LangChain作为流行的LLM应用开发框架,提供了模块化的组件来快速搭建RAG管线:文档加载器、文本分割器、向量存储、检索器、提示模板与LLM封装。本文将从零开始,带你构建一个完整的知识问答系统。

第一步:环境准备与依赖安装

首先创建一个新的Python虚拟环境(推荐Python 3.9+),然后安装所需包:

pip install langchain langchain-community langchain-openai chromadb tiktoken pypdf

我们使用OpenAI的嵌入模型和ChatGPT作为LLM,同时使用Chroma作为本地向量数据库。如果你没有OpenAI API密钥,也可以替换为开源模型(如使用Ollama和HuggingFace嵌入)。

第二步:加载文档

假设我们有一个PDF文件(knowledge.pdf)作为知识源。LangChain的PyPDFLoader可以轻松读取:

from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("knowledge.pdf")
documents = loader.load()
print(f"加载了 {len(documents)} 页")

对于其他格式(如txt、Markdown、HTML),LangChain也有对应的加载器。如果需要从网页抓取,可以使用WebBaseLoader

第三步:文本分割(Chunking)

文档内容通常过长,不适合直接送入LLM上下文窗口。我们需要将文档切分成适当大小的块(chunks),并保留一定的重叠以保持语义连贯。使用RecursiveCharacterTextSplitter

from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # 每块约1000字符
    chunk_overlap=200,    # 重叠200字符
    separators=["nn", "n", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"生成了 {len(chunks)} 个文本块")

第四步:创建向量存储

接下来,我们需要将每个文本块转换为向量(embedding),存储到向量数据库中以便后续检索。这里使用OpenAI的嵌入模型和Chroma:

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents=chunks, 
    embedding=embeddings,
    persist_directory="./chroma_db"  # 持久化到磁盘
)
vectorstore.persist()
print("向量存储创建完成")

注意:如果你使用本地模型,可以替换为HuggingFaceEmbeddings

第五步:构建检索器

向量存储本身可以作为一个检索器。我们可以指定检索时要返回的块数(top-k),以及是否使用Similarity Score Threshold来过滤低质量结果。

retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 3}  # 返回最相关的3个块
)

第六步:设计提示模板(Prompt Template)

为了让LLM正确利用检索到的上下文回答问题,我们需要设计一个明确的提示。LangChain的ChatPromptTemplate使这一过程更加简洁:

from langchain_core.prompts import ChatPromptTemplate
template = """你是一个知识问答助手。请使用以下上下文来回答问题。如果上下文不足以回答问题,请直接说“我不知道”,不要编造信息。
上下文:
{context}
问题:{question}
回答:"""
prompt = ChatPromptTemplate.from_template(template)

第七步:组合完整的RAG链条

现在我们将上述组件串联起来。使用LangChain的LCEL(LangChain Expression Language)可以优雅地构建链条:

from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
# 初始化LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 定义处理函数:将检索到的文档格式化为字符串
def format_docs(docs):
    return "nn".join(doc.page_content for doc in docs)
# 构建RAG链条
rag_chain = (
    RunnableParallel(
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
    )
    | prompt
    | llm
)

这里我们用RunnableParallel并行执行检索和传递问题,然后将结果送入提示模板,最后调用LLM生成答案。

第八步:运行问答系统

现在我们就可以向链条提问了:

question = "LangChain中如何实现文本分割?"
result = rag_chain.invoke(question)
print(result.content)

输出:“在LangChain中,可以使用RecursiveCharacterTextSplitter类,设定chunk_size和chunk_overlap参数…” (根据你的知识文档回答)

如果需要完整的对话记忆,可以进一步集成HistoryAwareRetrieverConversationBufferMemory,但本文先聚焦于基础RAG。

进阶优化与注意事项

  • 选择合适的分块策略:对于结构化文档(如Markdown、代码),可以使用MarkdownTextSplitterPythonCodeTextSplitter
  • 嵌入模型选择:如果数据是中文,推荐使用国产嵌入模型(如BGE、M3E)以提高检索准确性。
  • 检索后处理:可以使用EnsembleRetriever组合多种检索方法,或加入重新排序(Re-ranking)步骤来提升质量。
  • 成本控制:对大量文档进行嵌入会消耗API额度,建议使用本地向量数据库和开源模型。
  • 安全性:确保检索内容不包含敏感信息,并添加必要的权限控制。

总结

本文演示了如何使用LangChain从零开始搭建一个基于RAG的知识问答系统。核心流程包括:文档加载 → 文本分割 → 向量化存储 → 检索 → 提示模板 → 生成。整个管线仅需数十行代码,大大降低了AI开发的门槛。

下一步,你可以尝试替换为本地模型(如通过Ollama部署Llama 3),或集成用户界面(如Gradio/Streamlit),从而打造一个真正可用的企业级知识库助手。RAG技术仍在快速演进,关注最新论文和LangChain更新,将帮助你的AI应用保持领先。

阅读剩余
THE END