# LangChain RAG 应用开发组件深入:整体介绍
在 LangChain 中构建 RAG 应用时,核心链路不只是“用户提问 -> 大模型回答”。一个可维护的知识库问答系统,还需要把外部数据稳定地加载、解析、转换、分割、向量化并写入向量库,然后在用户提问时通过检索器召回相关文档,再把证据交给 Prompt 与 LLM。
这一部分主要围绕 LangChain 在 RAG 应用开发中常用的组件展开,包括文档加载器、文档分割器、文档转换器、VectorStore 组件以及检索器。
# 01. LangChain 文档组件与文档加载器
Document 组件是 RAG 应用中的基础数据结构。它负责承载文档正文和元数据,是文档加载器、文档分割器、向量数据库、检索器之间传递数据的统一对象。
文档加载器负责从不同数据源中读取内容,并转换成 LangChain 可处理的 Document。常见数据源包括 CSV 文件、HTML 网页、PDF 文件、文件夹、Markdown 文件、Office 文档,以及通用文件。
对于企业内部系统、数据库记录、API 接口、工单、知识库等高度定制化数据源,通用加载器往往只能解决“读出来”的问题,不能保证格式符合业务要求。这时就需要自定义文档加载器,把业务数据统一封装成 Document。
文档加载器这一层的重点有三个:
- 屏蔽不同数据源的读取差异。
- 保留 source、page、doc_id、line_number 等定位信息。
- 输出标准 Document,交给后续切分、转换、向量化和检索链路。
# 02. LangChain 文档转换器与分割器
文档被加载出来以后,通常不能直接写入向量库。原始文档可能过长、格式混乱、包含噪声,或者缺少后续检索需要的元数据。因此,在向量化之前,经常需要先经过文档转换器和文档分割器。
文档转换器用于把一组 Document 转换成另一组 Document。转换动作可以包括拆分、合并、过滤、翻译、内容压缩、HTML 转文本、元数据增强等。
文档分割器则专门解决 chunk 粒度问题。不同的分割方式会直接影响召回质量和最终答案质量:
- chunk 太大,可能超过上下文窗口,也会把无关内容带入 Prompt。
- chunk 太小,可能丢失完整语义,模型拿到的证据不足。
- chunk 没有重叠,跨段信息容易被切断。
- chunk 没有 metadata,后续难以追踪来源。
LangChain 提供了多种文本分割器,例如字符分割器、递归字符文本分割器、HTML 标题分割器、JSON 分割器、基于 token 的分割器,以及语义分割器。实际使用时,需要根据文档类型、语言、内容结构和检索目标选择合适的分割策略。
# 03. VectorStore 组件与检索器
VectorStore 组件用于存储文本向量,并提供相似性搜索能力。在 RAG 应用中,向量库不是简单的“存一下向量”,它还承担了相似度检索、分数过滤、metadata 过滤、MMR 多样性召回等工作。
常见检索方式包括:
- 普通相似性检索:根据 query 向量找最相似的文档片段。
- 带得分的相似性检索:返回文档的同时返回相关性得分。
- 阈值过滤检索:只保留高于指定分数的结果。
- MMR 检索:在相关性和多样性之间做平衡,减少重复片段。
检索器是 RAG 应用面向上层链路的统一检索接口。它可以封装 VectorStore,也可以封装第三方搜索服务、数据库查询、关键词检索或自定义检索逻辑。
一个检索器是否好用,关键不只在于能不能返回 Document,还在于能否控制检索参数、记录检索过程、保留分数和来源,并在结果不好时快速定位问题。
# 整体链路
RAG 深入部分可以按两条链路理解。
离线或异步数据处理链路:
外部数据源 -> Loader -> Document -> Transformer -> TextSplitter -> Embedding -> VectorStore
在线问答链路:
User Query -> Retriever -> Documents -> Prompt -> LLM -> Answer
这两条链路之间的连接点就是向量库与检索器。前者决定知识如何进入系统,后者决定用户提问时能否找到正确证据。
# 组件边界
| 组件 | 职责 |
|---|---|
| Document | 承载正文和元数据 |
| Loader | 读取外部数据并转换成 Document |
| Blob | 表示原始二进制或文件数据 |
| BlobParser | 把 Blob 解析成 Document |
| DocumentTransformer | 对 Document 列表做转换 |
| TextSplitter | 把长文档切分成 chunk |
| VectorStore | 存储向量并提供相似性搜索 |
| Retriever | 对外提供统一检索接口 |
把这些组件拆清楚后,RAG 应用就不再是一段混在一起的脚本,而是一条可以逐层调试的工程链路。
# 最新版 LangChain 用法提示
LangChain 1.x 之后,RAG 相关组件的整体思想仍然成立,但部分调用方式更推荐使用新的 Runnable 风格和拆分式写法。
| 组件/写法 | 现在是否建议 | 最新版建议 |
|---|---|---|
Document(page_content, metadata) | 仍然建议 | 继续使用 langchain_core.documents.Document,metadata 要保留来源、页码、版本和权限字段 |
Loader.load() | 仍然建议 | 小文件或一次性加载可以继续用 |
Loader.lazy_load() | 仍然建议 | 大文件、目录、批量导入更推荐懒加载 |
load_and_split() | 不优先建议 | 更推荐先 load() 或 lazy_load(),再显式调用 splitter |
RecursiveCharacterTextSplitter | 仍然建议 | 通用文本切分优先使用它,并设置 chunk_size、chunk_overlap、add_start_index=True |
VectorStore.as_retriever() | 仍然建议 | 用 search_type 和 search_kwargs 控制 similarity、mmr、score threshold |
retriever.get_relevant_documents() | 不建议 | 使用 retriever.invoke(query),批量场景使用 retriever.batch([...]) |
langchain.retrievers 旧导入 | 不建议 | 如果确实依赖旧检索器,使用 langchain-classic;新代码优先用 langchain_core、集成包和 Runnable 写法 |
最新版更推荐的整体写法是把加载、切分、入库、检索拆开:
from langchain_text_splitters import RecursiveCharacterTextSplitter
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
add_start_index=True,
)
chunks = splitter.split_documents(docs)
vector_store.add_documents(chunks)
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 4},
)
documents = retriever.invoke("用户的问题")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这样写比直接把所有步骤包进一个方法里更清晰,也更符合生产排查需要:每一步都能单独检查输入、输出和参数。
# 拓展
RAG 工程链路可以继续向三个方向扩展。
第一,数据入口可以从文件扩展到业务系统。除了 Markdown、PDF、Word、网页,还可以接入数据库、对象存储、客服工单、CRM、内部接口、日志系统。接入方式不同,但最终都应该转换成统一的 Document。
第二,检索可以从单一路径扩展到多路召回。除了向量相似度检索,还可以加入关键词检索、结构化 SQL 查询、标签过滤、时间过滤、权限过滤、重排序模型。多路召回后再合并、去重、排序,通常比单纯调大 top_k 更稳定。
第三,评估可以从人工查看扩展到固定测试集。每次修改 Loader、Splitter、Embedding、VectorStore 或 Retriever,都应该用固定问题集回归,观察目标证据是否被召回、答案是否引用正确、无答案问题是否拒答。
# 常见问题
# 为什么 RAG Demo 能跑,接真实数据后效果变差?
Demo 数据通常干净、短小、结构统一;真实数据会有格式混乱、重复内容、空白噪声、权限边界、版本过期、表格丢失、图片文字无法解析等问题。真实 RAG 的难点主要在数据处理链路,而不是简单调用 LLM。
# 为什么不能把加载、切分、入库、问答都写在一段代码里?
这样短期能跑,长期无法维护。线上结果变差时,无法判断问题来自数据读取、文本清洗、chunk 切分、向量检索、Prompt 组织还是模型输出。拆成组件后,每一层都能单独调试和替换。
# 为什么检索器要作为独立组件?
检索器把底层检索策略封装起来,上层只需要输入 query 并拿到 Document。底层可以是向量库、ES、数据库、自定义 API,甚至多路召回组合。这样 RAG 链路不依赖某一种具体数据库实现。
# 面试题
# RAG 深入阶段主要解决什么问题?
主要解决数据处理和检索工程化问题,包括文档加载、文档解析、文档转换、文本切分、向量存储、检索封装、metadata 追踪、线上问题回放等。
# RAG 中 Document、VectorStore、Retriever 的关系是什么?
Document 是数据载体,VectorStore 存储 Document 对应的向量并提供相似度搜索,Retriever 封装检索策略并向上层应用返回相关 Document。
# RAG 效果不好时如何排查?
先检查数据是否正确进入向量库,再检查 chunk 是否完整,然后检查 query、filter、top_k、score、召回文档和最终 Prompt。只有确认检索证据正确后,再去调整 Prompt 或模型。
# 生产问题排查
| 问题 | 常见原因 | 处理方式 |
|---|---|---|
| 召回不到正确文档 | chunk 太大或太小、metadata filter 过严、Embedding 模型不适合 | 调整切分参数,放宽过滤条件,重建索引 |
| 返回很多重复片段 | 文档重复入库、chunk overlap 过大、top_k 过高 | 做文档去重,降低 overlap,使用 MMR |
| 答案无法引用来源 | metadata 缺少 source、page、doc_id | 入库前补齐 metadata |
| 用户看到无权限内容 | metadata 没有权限字段,检索时没有 filter | 增加 tenant_id、acl 等字段并强制过滤 |
| 更新文档后仍返回旧内容 | chunk_id 不稳定,旧向量未删除 | 使用稳定 doc_id/chunk_id,更新前删除旧版本 |
| Prompt 太长 | top_k 过大,chunk 太大,召回内容重复 | 降低 top_k,压缩 chunk,增加重排序和去重 |