LangChain 完整处理RAG流程
搭建 RAG 知识库时,很多人首先会遇到一个问题:
能不能直接把一份 PDF、Word 或 Markdown 文档交给大模型?
通常不建议这样做。
一份文档可能有几十页甚至几百页,不仅容易超过模型的上下文长度,而且用户提出的问题往往只和其中几个段落有关。如果每次都把整份文档交给大模型,不仅成本更高,还容易受到无关内容干扰。
因此,在正式问答之前,需要先把原始文件处理成可以被检索的知识。
LangChain 提供了文档加载、文本切分、向量化、向量存储和检索等组件,可以把整个处理过程串联起来:

一、读取文件:获取原始文档内容
第一步,是使用 LangChain 的 Document Loader 读取文件。
针对不同的数据来源,LangChain 提供了不同的 Loader,例如:
• TextLoader:读取 TXT 文件;• PyPDFLoader:读取 PDF 文件;• CSVLoader:读取 CSV 文件;• WebBaseLoader:读取网页内容;• UnstructuredWordDocumentLoader:读取 Word 文件。
例如,使用 PyPDFLoader 读取一份 PDF:
from langchain_community.document_loaders import PyPDFLoaderloader = PyPDFLoader("knowledge.pdf")documents = loader.load()load() 会读取文件内容,并将结果转换成 LangChain 后续组件能够处理的 Document 对象。
print(len(documents))print(documents[0].page_content)print(documents[0].metadata)对于普通文本和结构简单的 PDF,可以直接使用 LangChain Loader。
如果 PDF 中包含复杂表格、多栏排版、图片或扫描内容,一般会使用 LangChain 集成的 Docling、Unstructured 等非结构化文档解析工具,尽可能保留原始文档结构,再进入后续处理流程。
二、加载文档:转换为 Document 对象
在 LangChain 中,读取出来的内容不会只用普通字符串表示,而是会被统一封装成 Document 对象。
一个 Document 主要包含两部分:
from langchain_core.documents import Documentdocument = Document( page_content="LangChain 可以用于构建 RAG 应用。", metadata={ "source": "knowledge.pdf", "page": 1, },)其中:
• page_content:保存正文内容;• metadata:保存文件来源、页码、标题等附加信息。
可以简单理解为:
Document = 文本内容 + 来源信息

为什么要转换为 Document?
因为系统检索到一段内容后,不仅需要知道内容是什么,还需要知道它来自哪个文件、位于哪一页,以及回答时应该引用哪个来源。
同时,无论原始内容来自 PDF、Word、网页还是数据库,只要统一转换成 Document,后续就可以使用相同的切分、向量化和检索流程。
在 LangChain 中,读取文件和转换为 Document 通常会同时完成:
documents = loader.load()返回结果一般是:
list[Document]三、文本切分:将长文档拆分为 Chunk
文档加载完成后,通常不能直接对整份长文档进行向量化,而是需要先将它拆分成多个较小的文本块,也就是 Chunk。
这样做的目的,是让每个文本块只包含一段相对完整、主题集中的内容,从而提高后续检索的准确性。
LangChain 常见的文本切分方式包括:
• 字符切分:按照固定字符数量拆分; • Token 切分:按照模型 Token 数量拆分; • 递归切分:依次按照段落、句子和字符拆分; • 结构化切分:按照 Markdown 标题、HTML 标签或代码结构拆分; • 语义切分:根据文本语义变化确定切分位置。
其中,普通文本最常用的是 RecursiveCharacterTextSplitter。
它会优先按照段落和句子切分;如果文本仍然过长,再继续按照字符切分,尽可能保留原有语义。
from langchain_text_splitters import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=[ "\n\n", "\n", "。", "!", "?", ";", ",", " ", "", ],)chunks = splitter.split_documents(documents)这里有两个核心参数:
• chunk_size:每个文本块的最大长度;• chunk_overlap:相邻文本块之间重复保留的内容。
chunk_overlap 可以减少一句话或一个完整概念刚好在切分位置被截断的问题。

文本切分并不是越碎越好。Chunk 太大,检索结果容易包含无关内容;Chunk 太小,又可能破坏语义完整性。
实际项目中,如果处理复杂 PDF、Word 或 PPT,一般会先通过 Docling、Unstructured 等工具提取标题、段落和表格结构,再使用 LangChain 进行二次切分。
四、向量化与存储:构建可检索的知识索引
文本切分完成后,我们已经得到多个相对独立的 Chunk。
但这些 Chunk 仍然是普通文本,计算机无法直接判断两段文字在语义上是否相近。因此,需要使用 Embedding 模型把文本转换成一组数字,也就是文本向量。
例如:
“LangChain 如何切分文档?” ↓ Embedding 模型 ↓[0.12, -0.38, 0.71, ...]语义越相近的文本,生成的向量通常也越接近。
例如,“如何拆分长文档?”和“长文本应该怎样切块?”虽然关键词不完全相同,但表达的意思接近,因此可以通过向量相似度找到彼此。
下面使用 LangChain 集成的 Hugging Face Embedding 模型:
from langchain_huggingface import HuggingFaceEmbeddingsembeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-m3", encode_kwargs={ "normalize_embeddings": True, },)Embedding 模型不负责生成答案,它只负责把文本转换成可以进行相似度计算的向量。
生成向量后,还需要将向量保存到 Vector Store,也就是向量存储中。
下面使用 LangChain 集成的 Chroma:
from langchain_chroma import Chromavector_store = Chroma.from_documents( documents=chunks, embedding=embeddings, collection_name="rag_documents", persist_directory="./chroma_db",)这段代码会依次完成:
读取每个 Chunk→ 调用 Embedding 模型生成向量→ 保存向量、原文和 metadata向量存储中通常包含:
• 文本向量; • 原始 Chunk; • metadata;• 文档 ID。

检索时,用户问题也会使用同一个 Embedding 模型生成查询向量,然后在 Vector Store 中搜索相近的文档向量。
因此,可以把两者的职责理解为:
• Embedding:负责表示文本语义; • Vector Store:负责保存和搜索语义向量。
五、混合检索:同时理解语义和关键词
文档完成向量存储后,就可以创建 Retriever,也就是检索器。
Retriever 可以简单理解为:
接收用户问题,并返回相关
Document的组件。
单独使用向量检索时,系统能够理解语义相近但表达不同的问题。
但是对于错误码、产品型号、文件编号和专有名词等精确关键词,BM25 检索通常更加有效。
因此,实际 RAG 项目经常把两种检索方式组合起来:
• 向量检索:负责理解语义; • BM25:负责匹配关键词。
这种方式称为混合检索,也就是 Hybrid Search。
先创建向量检索器:
vector_retriever = vector_store.as_retriever( search_type="similarity", search_kwargs={"k": 4},)再创建 BM25 检索器:
import jiebafrom langchain_community.retrievers import BM25Retrieverbm25_retriever = BM25Retriever.from_documents( chunks, preprocess_func=jieba.lcut,)bm25_retriever.k = 4对于中文文档,需要先进行中文分词,因此这里使用 jieba.lcut。
最后,使用 EnsembleRetriever 融合两种检索结果:
from langchain_classic.retrievers.ensemble import EnsembleRetrieverhybrid_retriever = EnsembleRetriever( retrievers=[ vector_retriever, bm25_retriever, ], weights=[ 0.6, 0.4, ],)results = hybrid_retriever.invoke( "E01 故障出现后应该怎样处理?")
在这个问题中:
• 向量检索可以理解“故障出现后怎样处理”的语义; • BM25 可以准确匹配“E01”这个关键词; • EnsembleRetriever再将两种检索结果进行融合和排序。
这样既保留了语义检索能力,也提高了精确关键词的召回效果。
查看检索结果:
for index, doc in enumerate(results, start=1): print(f"结果 {index}") print(doc.page_content) print(doc.metadata)Retriever 返回的仍然是相关 Document,并不是最终答案。
六、总结
通过 LangChain,我们可以把一份普通文档逐步处理成可检索的知识内容。
其中:
• Document Loader 负责读取不同类型的文件; • Document 负责统一保存正文和来源信息; • Text Splitter 负责将长文档拆分成多个 Chunk; • Embedding 模型负责将文本转换为向量; • Vector Store 负责保存向量、原文和元数据; • Retriever 负责根据用户问题检索相关内容。
对于普通问题,可以使用向量检索理解语义;对于错误码、型号和专有名词等内容,可以加入 BM25 关键词检索,再通过 EnsembleRetriever 融合两种检索结果。
到这里,我们已经完成了 RAG 中的文档读取、切分、向量化、存储和检索。
不过,检索返回的仍然只是相关文档片段。完整的 RAG 还需要把这些内容加入 Prompt,再交给大语言模型生成最终答案。
夜雨聆风