在大语言模型应用开发中,让模型理解特定领域知识并准确回答相关问题,是构建垂直领域智能助手的核心需求。检索增强生成(RAG)技术通过「先检索后生成」的模式,有效解决了模型对最新或专有数据的依赖问题。今天我们就来拆解如何用 LangChain 实现一个完整的 RAG 流程,从数据索引到问答生成,掌握这个让模型「有记忆」的关键技术。

一、RAG 核心架构:索引与生成的双轮驱动

RAG 系统的核心是构建「数据高速公路」,让用户问题能快速找到相关知识片段并生成答案。整个流程分为两大阶段:

  1. 离线索引阶段:将非结构化数据转化为模型可快速检索的格式
  2. 在线问答阶段:实时检索相关知识并驱动模型生成回答

我们将以加载一篇博客文章为例,演示如何一步步搭建这个「数据高速公路」。

二、索引构建:让数据准备好被「搜索」

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 学习)
    这些要素能显著提升模型利用外部知识的能力。

五、避坑指南与最佳实践

  1. 数据清洗:加载网页内容时务必过滤无关标签,脏数据会严重影响检索和生成质量
  2. 嵌入模型适配:确保嵌入模型与 LLM 在训练数据分布上保持一致,避免语义鸿沟
  3. 检索结果数量similarity_search默认返回 5 个文档,可通过k参数调整,过多可能引入噪声,过少可能信息不足
  4. 流式输出支持:LangGraph 的流程天然支持流式生成,可通过stream()方法实现逐字输出,提升用户体验

六、总结:从代码到能力的跨越

通过这个 RAG 系统的实现,我们掌握了:

  1. 如何将非结构化数据转化为可检索的向量索引
  2. 如何通过检索技术让模型获取外部知识
  3. 如何用提示工程整合上下文和问题驱动生成

这套流程不仅适用于网页内容,还可以扩展到文档、数据库等多种数据源。如果你在搭建过程中遇到向量存储性能问题或提示模板优化需求,欢迎在评论区留言交流。觉得有帮助的话,别忘了点赞收藏,关注我们获取更多 LangChain 实战技巧,一起解锁大语言模型的更多可能性!

Logo

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

更多推荐