在大模型应用开发中,文档检索是核心能力之一,尤其是针对 PDF 这类非结构化文档的精准检索,能极大提升问答、知识库等场景的体验。本文将基于 LangChain 框架,完整拆解从 PDF 文档加载、文本分割、向量嵌入到相似性检索的全流程,手把手教你实现针对 PDF 文档的智能检索。
一、核心技术栈说明
在开始编码前,先明确本次用到的核心工具和库:
- LangChain:一站式 LLM 应用开发框架,提供文档加载、文本分割、向量存储等全套工具链;
- PyPDFLoader:LangChain 内置的 PDF 文档加载器,可高效解析 PDF 内容;
- RecursiveCharacterTextSplitter:递归字符文本分割器,支持按自定义规则拆分长文本;
- DashScopeEmbeddings:阿里云灵积平台的文本嵌入模型,将文本转化为向量表示;
- FAISS:Facebook 开源的向量数据库,支持高效的相似性检索。
二、实现步骤:从 PDF 到智能检索
1. 文档加载:读取 PDF 内容
首先需要将 PDF 文档的内容加载到程序中,LangChain 的PyPDFLoader提供了极简的加载方式,无需关注底层 PDF 解析细节:
python
from langchain_community.document_loaders import PyPDFLoader
# 初始化PDF加载器,指定PDF文件路径
loader = PyPDFLoader("./PDFQA/卢浮宫.pdf")
# 加载PDF文档,返回Document对象列表
docs = loader.load()
# 打印第一页内容,验证加载结果
print(docs[0].page_content)

除了 PDF,LangChain 还支持维基百科、文本文件等多种数据源加载,比如通过WikipediaLoader加载维基百科内容:
python
from langchain_community.document_loaders import WikipediaLoader
loader = WikipediaLoader(query="周杰伦", load_max_docs=3, lang="zh")
docs = loader.load()
print(docs[1].page_content)
2. 文本分割:解决长文本嵌入限制
PDF 加载后的文本通常较长,而嵌入模型对输入文本长度有约束,且过长的文本会降低检索精度。因此需要将长文本拆分为大小适中的文本块(Chunk)。
这里使用RecursiveCharacterTextSplitter,它会按指定的分隔符递归拆分文本,同时保留文本语义完整性:
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 初始化文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个文本块的最大字符数
chunk_overlap=100, # 相邻文本块的重叠字符数(提升上下文连续性)
separators=["\n\n", "\n", "。", "!", "?", ",", "、", ""] # 中文文本分隔符
)
# 拆分加载的PDF文档
texts = text_splitter.split_documents(docs)
# 打印第三个文本块,验证分割效果
print(texts[2].page_content)

关键参数说明:
chunk_size:控制单个文本块的长度,需结合嵌入模型的输入限制调整;chunk_overlap:重叠部分能避免文本被拆分后丢失上下文关联;separators:优先按列表前序分隔符拆分,适配中文文本的语义停顿规则。
3. 文本嵌入:将文本转化为向量
文本无法直接被计算机用于相似性检索,需要通过嵌入模型转化为数值向量(Embedding)。本文使用阿里云灵积平台的DashScopeEmbeddings,也可替换为 OpenAI Embeddings 等其他模型。
python
import os
from langchain_community.embeddings import DashScopeEmbeddings
# 初始化嵌入模型(需配置阿里云灵积API密钥)
embeddings_model = DashScopeEmbeddings(
model="text-embedding-v2",
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY") # 从环境变量读取API密钥,避免硬编码
)
# 测试文本嵌入效果
# 单文本查询嵌入
query_result = embeddings_model.embed_query("卢浮宫这个名字怎么来的?")
print("单个文本向量长度:", len(query_result))
# 多文本文档嵌入
doc_results = embeddings_model.embed_documents(["My friends call me TOM", "Hello World!"])
print("文档向量数量:", len(doc_results), ",单个文档向量长度:", len(doc_results[0]))

进阶配置 :如果希望降低向量维度(减少存储和计算成本),可使用text-embedding-v3模型并指定dimensions参数:
python
embeddings_model = DashScopeEmbeddings(
model="text-embedding-v3",
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY"),
dimensions=1024 # 指定向量维度为1024
)
4. 向量存储与检索:基于 FAISS 实现相似性查询
将分割后的文本块嵌入为向量后,存入 FAISS 向量数据库,即可实现基于语义的相似性检索。
python
from langchain_community.vectorstores import FAISS
# 将文本块向量存入FAISS
db = FAISS.from_documents(texts, embeddings_model)
# 将数据库转为检索器,用于相似性查询
retriever = db.as_retriever()
# 示例1:检索"卢浮宫这个名字怎么来的?"相关文本
retrieved_docs = retriever.invoke("卢浮宫这个名字怎么来的?")
print("检索结果1:\n", retrieved_docs[0].page_content)
# 示例2:检索"是否可以使用闪光灯"相关文本
retrieved_docs = retriever.invoke("是否可以使用闪光灯")
print("检索结果2:\n", retrieved_docs[0].page_content)

批量处理优化:如果 PDF 文档量较大,可分批将文本块加入 FAISS,避免单次加载过多数据:
python
batch_size = 10
persist_directory = "./faiss_db" # 向量库持久化路径
for i in range(0, len(texts), batch_size):
batch_docs = texts[i:i+batch_size]
if i == 0:
# 第一批文档创建向量库
vectordb = FAISS.from_documents(
documents=batch_docs,
embedding=embeddings_model,
persist_directory=persist_directory
)
else:
# 后续批次添加到现有向量库
vectordb.add_documents(batch_docs)
三、核心流程总结
- 文档加载:通过 PyPDFLoader 读取 PDF 内容,转化为 LangChain Document 对象;
- 文本分割:用 RecursiveCharacterTextSplitter 拆分文本块,平衡长度与语义;
- 文本嵌入:通过 DashScopeEmbeddings 将文本转为向量,实现语义数字化;
- 向量检索:基于 FAISS 构建向量库,通过检索器快速匹配相似文本块。
四、扩展与优化建议
- 模型替换:可将 DashScopeEmbeddings 替换为 OpenAI Embeddings、智谱 AI 嵌入等,适配不同平台;
- 向量库持久化 :使用 FAISS 的
save_local方法保存向量库,避免重复嵌入; - 检索参数调优 :调整
retriever的search_kwargs(如k值),控制返回的相似文本数量; - 多数据源支持:结合 LangChain 的其他 Loader(如 CSVLoader、MarkdownLoader),支持多格式文档检索。
五、应用场景
该方案可直接用于构建 PDF 问答机器人、企业知识库检索系统、智能文档分析工具等场景,核心优势是基于语义的精准检索,相比传统关键词检索更贴合自然语言查询的需求。
通过以上步骤,我们完成了从 PDF 文档加载到语义检索的全流程实现。LangChain 的模块化设计让整个流程高度可定制,开发者可根据实际需求替换组件,快速搭建符合业务场景的文档检索系统。