手把手教你用 LangChain 构建 RAG 问答系统:从索引到生成的完整实践
如何将非结构化数据转化为可检索的向量索引如何通过检索技术让模型获取外部知识如何用提示工程整合上下文和问题驱动生成这套流程不仅适用于网页内容,还可以扩展到文档、数据库等多种数据源。如果你在搭建过程中遇到向量存储性能问题或提示模板优化需求,欢迎在评论区留言交流。
在大语言模型应用开发中,让模型理解特定领域知识并准确回答相关问题,是构建垂直领域智能助手的核心需求。检索增强生成(RAG)技术通过「先检索后生成」的模式,有效解决了模型对最新或专有数据的依赖问题。今天我们就来拆解如何用 LangChain 实现一个完整的 RAG 流程,从数据索引到问答生成,掌握这个让模型「有记忆」的关键技术。
一、RAG 核心架构:索引与生成的双轮驱动
RAG 系统的核心是构建「数据高速公路」,让用户问题能快速找到相关知识片段并生成答案。整个流程分为两大阶段:
- 离线索引阶段:将非结构化数据转化为模型可快速检索的格式
- 在线问答阶段:实时检索相关知识并驱动模型生成回答
我们将以加载一篇博客文章为例,演示如何一步步搭建这个「数据高速公路」。
二、索引构建:让数据准备好被「搜索」
1. 数据加载:从网页提取有效内容
首先需要把网页内容转化为模型可处理的文档对象。LangChain 的WebBaseLoader
结合 BeautifulSoup,可以精准提取指定标签的内容:
python
import bs4
from langchain_community.document_loaders import WebBaseLoader
# 定义筛选规则:只保留包含特定类名的HTML标签
bs4_strainer = bs4.SoupStrainer(class_=("post-title", "post-header", "post-content"))
loader = WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs={"parse_only": bs4_strainer} # 传递给BeautifulSoup的解析参数
)
docs = loader.load() # 加载并解析网页内容
这样处理后,我们得到一个包含网页核心内容(标题、头部、正文)的 Document 对象,剔除了广告、导航等无关信息。
2. 文本拆分:让大块内容「可消化」
由于模型的上下文窗口有限,且大块文本不利于精准检索,需要将长文本拆分为合适大小的块:
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个块1000字符
chunk_overlap=200, # 块之间重叠200字符,避免信息断层
add_start_index=True # 记录每个块在原文中的起始位置
)
all_splits = text_splitter.split_documents(docs) # 拆分文档
print(f"Split into {len(all_splits)} sub-documents.") # 输出拆分后的块数量
这里使用RecursiveCharacterTextSplitter
,它会优先在标点符号或空格处拆分,保证语义完整。重叠设置是为了防止关键信息被截断。
3. 向量存储:让数据具备「语义检索」能力
拆分后的文本块需要转化为向量形式,以便进行语义相似度搜索。我们使用 Ollama 的词嵌入模型生成向量,并存储到内存向量库中:
python
from langchain_ollama import OllamaEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
embeddings = OllamaEmbeddings(model="llama3") # 初始化词嵌入模型
vector_store = InMemoryVectorStore(embeddings) # 创建内存向量存储
# 将文本块添加到向量库,返回每个文档的唯一ID
document_ids = vector_store.add_documents(documents=all_splits)
向量存储的核心是通过词嵌入将文本转化为高维空间中的点,相似内容的点在空间中距离更近,从而实现基于语义的检索而非关键词匹配。
三、问答生成:让模型结合「外部记忆」回答
1. 检索相关知识:找到问题的「答案线索」
当用户提问时,首先需要从向量库中检索最相关的文本块。使用similarity_search
方法进行余弦相似度搜索:
python
def retrieve(state: State):
# 根据用户问题检索相关文档,默认返回最相似的5个
retrieved_docs = vector_store.similarity_search(state["question"])
return {"context": retrieved_docs}
这里的State
是自定义的类型,用于跟踪问题、检索到的上下文和生成的答案。检索结果会作为「外部记忆」传递给模型。
2. 生成回答:用「问题 + 上下文」驱动模型输出
LangChain 提供了预定义的 RAG 提示模板,可以直接从提示中心拉取,避免手动设计提示的复杂性:
python
from langchain import hub
prompt = hub.pull("rlm/rag-prompt") # 获取预定义的RAG提示模板
def generate(state: State):
# 将检索到的文档内容拼接成上下文
docs_content = "\n\n".join(doc.page_content for doc in state["context"])
# 生成模型输入消息
messages = prompt.invoke({"question": state["question"], "context": docs_content})
# 调用LLM生成回答
response = llm.invoke(messages)
return {"answer": response.content}
提示模板会自动将问题和上下文格式化为模型适合的输入格式,比如在问题前添加「根据以下内容回答问题」等引导语,提升回答的准确性。
3. 流程整合:用 LangGraph 构建完整闭环
通过 LangGraph 可以将检索和生成步骤整合成一个可复用的流程,支持流式输出、异步调用等高级功能:
python
from langgraph.graph import START, StateGraph
from typing_extensions import List, TypedDict
# 定义应用状态,跟踪问题、上下文和答案
class State(TypedDict):
question: str
context: List[Document]
answer: str
# 构建状态图,按顺序连接检索和生成步骤
graph_builder = StateGraph(State).add_sequence([retrieve, generate])
graph_builder.add_edge(START, "retrieve") # 从检索步骤开始
graph = graph_builder.compile() # 编译生成可执行的流程图
这种声明式的流程定义,让我们可以清晰看到数据在各个步骤之间的流动,方便后续的调试和扩展。
四、关键技术点解析
1. 文本拆分策略选择
- chunk_size:太小会导致上下文不足,太大则可能超出模型窗口,需根据具体模型调整(示例中 1000 字符适用于大多数场景)
- chunk_overlap:保留重叠部分可以避免跨块的关键信息丢失,通常设为 chunk_size 的 10%-20%
2. 向量存储选型建议
- InMemoryVectorStore:适合演示和小规模数据,生产环境建议使用 Pinecone、FAISS 等专用向量数据库
- 词嵌入模型:根据数据语言选择(中文场景可尝试 BAAI 的 Chinese-LLaMA-Embed),Ollama 支持本地部署模型,适合隐私敏感场景
3. 提示模板的重要性
预定义的 RAG 提示模板经过优化,包含:
- 明确的指令(如「根据以下内容回答问题」)
- 上下文和问题的清晰分隔
- 示例输入输出(可选,用于 Few-Shot 学习)
这些要素能显著提升模型利用外部知识的能力。
五、避坑指南与最佳实践
- 数据清洗:加载网页内容时务必过滤无关标签,脏数据会严重影响检索和生成质量
- 嵌入模型适配:确保嵌入模型与 LLM 在训练数据分布上保持一致,避免语义鸿沟
- 检索结果数量:
similarity_search
默认返回 5 个文档,可通过k
参数调整,过多可能引入噪声,过少可能信息不足 - 流式输出支持:LangGraph 的流程天然支持流式生成,可通过
stream()
方法实现逐字输出,提升用户体验
六、总结:从代码到能力的跨越
通过这个 RAG 系统的实现,我们掌握了:
- 如何将非结构化数据转化为可检索的向量索引
- 如何通过检索技术让模型获取外部知识
- 如何用提示工程整合上下文和问题驱动生成
这套流程不仅适用于网页内容,还可以扩展到文档、数据库等多种数据源。如果你在搭建过程中遇到向量存储性能问题或提示模板优化需求,欢迎在评论区留言交流。觉得有帮助的话,别忘了点赞收藏,关注我们获取更多 LangChain 实战技巧,一起解锁大语言模型的更多可能性!

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)