LangChain PDF 处理:从加载到向量库完整流程

一句话概括:PDF 加载 → 文本切割 → 向量化 → 存入向量库,这也是 RAG(检索增强生成)最基础、最核心的前三步,掌握了这三步,就能轻松搭建简单的 PDF 问答系统。

一、第一步:PDF 加载(两种方式,按需选择)

处理 PDF 的第一步,就是把 PDF 里的文本提取出来,LangChain 给我们提供了专门的 PyPDFLoader 工具,操作非常简单,还支持两种加载模式,适配不同大小的 PDF 文件。
(注意这个好像还得额外下载一个处理pdf的包)

1. 核心依赖导入

1
from langchain_community.document_loaders import PyPDFLoader

2. 实例化加载器(必传参数说明)

PyPDFLoader 有多个参数,但只有一个是必须传的——PDF 文件路径,其他参数(如密码、提取图片)默认关闭即可,新手不用额外配置。

1
2
# 实例化加载器,传入 PDF 文件路径(必填)
loader = PyPDFLoader("你的文件路径.pdf") # 示例:"./test.pdf"

3. 两种加载方式(重点区分)

根据 PDF 文件大小选择对应的加载方式,避免内存溢出(OOM),这是新手最容易忽略的点!

方式1:普通加载(load())—— 适合小文件

直接一次性将整个 PDF 加载到内存,返回一个 Document 对象列表,每一个 Document 对应 PDF 的一页。

1
2
# 普通加载,返回 List[Document]
docs = loader.load()

方式2:懒加载(lazy_load())—— 适合大文件

采用生成器模式,按需读取每一页,不会一次性占用大量内存,避免大文件加载时出现 OOM 错误,推荐处理大 PDF 时优先使用。

1
2
3
4
5
6
7
# 懒加载,返回生成器,按需读取
lazy_docs = loader.lazy_load()

# 迭代使用(逐页处理)
for doc in lazy_docs:
print(f"当前页:{doc.metadata['page']}") # 查看当前加载的页码
# 后续切割、向量化逻辑可在这里编写

关键补充:Document 对象是什么?

不管哪种加载方式,最终得到的都是 Document 对象(或其列表),它是 LangChain 统一的文本载体,结构非常简单,包含两个核心部分:

1
2
3
4
Document(
page_content="PDF 页面提取的纯文本内容", # 必须:提取的文本
metadata={"source": "你的文件路径.pdf", "page": 0} # 可选:来源、页码等信息
)

这个 Document 列表非常重要,后续的文本切割、向量化,都需要用到它!

二、第二步:文本切割(

加载后的 PDF 文本通常很长(比如一页几百上千字),如果直接进行向量化,会导致两个问题:一是向量检索不精准,二是后续传入大模型时超出上下文窗口。因此,文本切割是必不可少的一步。

新手首选切割工具:RecursiveCharacterTextSplitter,它是 LangChain 最通用、效果最好的文本分割器,能智能按段落、换行、空格切割,不会生硬切断一句话,保证语义连贯。

1. 依赖导入

1
from langchain_text_splitters import RecursiveCharacterTextSplitter

2. 实例化分割器(核心参数详解)

分割器有三个核心参数,新手只需关注前两个,第三个参数默认即可,不用额外配置:

1
2
3
4
5
6
# 实例化文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 核心参数1:每块文本的长度(单位:字符)
chunk_overlap=50, # 核心参数2:块与块之间的重叠长度(保证语义连贯)
separators=["\n\n", "\n", " ", ""] # 可选:切割优先级,默认即可
)

3. 执行切割(得到小块 Document 列表)

调用分割器的 split_documents 方法,传入第一步加载得到的 Document 列表,切割后返回的依然是 List[Document],只是每个 Document 的内容变成了 500 字符左右的小块。

1
2
# 切割文本,得到小块 Document 列表
splits = text_splitter.split_documents(docs) # docs 是第一步加载的结果

新手避坑:为什么必须切割?

  • 避免大模型上下文超限:大模型的上下文窗口有限,长文本无法直接传入。
  • 提升检索精度:小块文本向量化后,能更精准地匹配用户的查询(比如用户问某个细节,能快速定位到对应的小块)。
  • 提高处理速度:小块文本向量化、检索的速度更快,节省时间。

三、第三步:向量化 + 存入向量库(核心步骤)

文本切割完成后,下一步就是将这些小块文本转成计算机能理解的“向量”(一堆数字),然后存入向量库,方便后续进行相似性检索。这一步需要两个核心工具:嵌入模型(负责转向量)向量数据库(负责存向量)

主播用的组合是:NVIDIAEmbeddings(云端嵌入模型)+ Chroma(本地向量库)

1. 依赖导入

1
2
3
from langchain_community.vectorstores import Chroma
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
import os # 用于读取 API Key(避免硬编码)

2. 初始化嵌入模型(NVIDIAEmbeddings)

嵌入模型的作用是将文本(page_content)转成高维向量,这里用 NVIDIA 的云端嵌入模型,需要先获取 API Key(免费申请),然后传入模型参数。

1
2
3
4
5
# 初始化 NVIDIA 嵌入模型
embeddings = NVIDIAEmbeddings(
nvidia_api_key=os.getenv("NVIDIA_API_KEY"), # 从环境变量读取 API Key(推荐)
model="nvidia/llama-3.2-nv-embedqa-1b-v2", # NVIDIA 官方嵌入模型,适配问答场景
)

补充:如果没有 NVIDIA API Key,也可以用本地开源嵌入模型(如 OllamaEmbeddings),后续会单独补充。

3. 向量化 + 存入 Chroma 向量库(最关键一步)

使用 Chroma 的 from_documents 方法,一键完成“向量化 + 存储”,

正确代码

1
2
3
4
5
6
7
8
vector_store = Chroma.from_documents(

    documents=splits,                          # 分割后的 List[Document]
    embedding=embeddings,                      # 嵌入模型实例,如 OpenAIEmbeddings()
    persist_directory="./chroma_db",           # 本地持久化目录(自动保存)
    ids=[f"doc_{i}" for i in range(len(splits))],  # 自定义文档 ID 列表(可选,默认自动生成)

)

避坑:参数名错误

很多人会把参数名写成 embeddings(加 s),这样会直接报错!正确的参数名是 embedding(单数),一定要记牢!

关键补充:vector_store 里到底存了什么?

vector_store

是一个向量库对象,不是普通的列表或字典,里面每一个小块文本都对应三样东西: 1.元数据(metadata):来源 PDF 路径、页码等信息,方便后续追溯。 2.文本内容(page_content):切割后的小块文本。 3.向量(embedding):文本转成的高维数字数组,计算机就是通过对比这些向量,找到相似的文本。

四、完整可运行代码(整合版)

把上面三步整合起来,就是一份完整的 PDF 处理代码,新手可以直接复制,替换自己的 PDF 路径和 API Key 就能运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 1. 加载 PDF
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("你的文件路径.pdf")
docs = loader.load() # 小文件用这个,大文件用 loader.lazy_load()

# 2. 切割文本
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
splits = text_splitter.split_documents(docs)

# 3. 向量化 + 存入向量库
from langchain_community.vectorstores import Chroma
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
import os

# 初始化嵌入模型
embeddings = NVIDIAEmbeddings(
nvidia_api_key=os.getenv("NVIDIA_API_KEY"),
model="nvidia/llama-3.2-nv-embedqa-1b-v2",
)

# 存入 Chroma 向量库
vector_store = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db",

)

# 可选:持久化向量库(确保数据不丢失)
vector_store.persist()
print("PDF 处理完成,向量库已保存到 ./chroma_db 目录!")

五、总结 + 补充要点

梳理到这里,整个 PDF 处理流程就非常清晰了,新手只需记住:

  1. 加载:用 PyPDFLoader,小文件 load(),大文件 lazy_load(),得到 List[Document]。
  2. 切割:用 RecursiveCharacterTextSplitter,核心参数 chunk_size 和 chunk_overlap,得到小块 List[Document]。
  3. 向量化:用 NVIDIAEmbeddings(或本地模型),存入 Chroma,记住参数名是 embedding(单数)。

补充两个问题

1. 为什么要加 persist_directory?

如果不加这个参数,向量库只会存在内存里,程序一重启,之前的向量化结果就全丢了,下次还要重新加载、切割、向量化,非常浪费时间。加了之后,向量库会保存到本地目录,下次直接加载即可。

2. 没有 NVIDIA API Key 怎么办?

可以用本地开源嵌入模型,比如 OllamaEmbeddings,无需 API Key,直接本地运行,替换嵌入模型部分即可:

1
2
3
4
from langchain_community.embeddings import OllamaEmbeddings

# 本地嵌入模型,无需 API Key
embeddings = OllamaEmbeddings(model="mxbai-embed-large")
1