RAG 入门第二课:从 PDF 深度解析到智能入库全链路

概述

在构建 RAG 系统时,我们常把精力花在调优大模型上,却忽略了最基础也最关键的一环------数据处理。俗话说"垃圾进,垃圾出",如果文档读取乱码、切分支离破碎,再强大的模型也无法给出精准答案。

今天,我们将打通从PDF 深度解析智能切分向量入库的完整链路,并重点解析那些你可能混淆的核心组件。

核心组件大揭秘:它们到底在干什么?

在开始写代码前,我们先解决一个概念问题。很多初学者容易混淆 LangChain、Text Splitters 和 ChromaDB 的关系。

我们可以把构建 RAG 系统比作**"经营一家图书馆"**:

组件/库 角色类比 核心职责 为什么需要它?
PyMuPDF 图书扫描仪/修复师 文档解析与处理 它能把 PDF 这种"图片/二进制"格式翻译成可编辑的文本,还能提取图片、表格,甚至给 PDF 加水印。
LangChain 图书馆总管 应用开发框架 它提供了一套标准流程,把"读取、切分、存储、问答"这些散乱的工具串联起来,不用你自己造轮子。
langchain-text-splitters 图书装订工 数据预处理 它是 LangChain 的独立分包。专门负责把厚书切成小卡片,保证语义完整,不让句子断在半截。
ChromaDB 图书管理员 向量存储与检索 它不负责切分,只负责记忆。它把切好的卡片变成数字(向量)存起来,当你提问时,它能瞬间找到最相关的那张卡片。

** 关系图解**

PyMuPDF (读取) → Text Splitters (切分) → ChromaDB (存储)

整个流程由 LangChain 框架进行编排。

第一阶段:文档读取利器------PyMuPDF

在之前的尝试中,我们可能遇到过 PDF 读取乱码的问题。这是因为 PDF 本质上是二进制文件,Python 原生的 open() 无法理解其编码。

PyMuPDF (fitz) 是目前 Python 生态中最强大、速度最快的 PDF 处理库之一(基于 MuPDF 引擎)。

1. 安装与导入

注意:库的安装名和导入名是不一样的,这是新手常踩的坑。

bash 复制代码
# 安装命令
pip install pymupdf
python 复制代码
import fitz  # 导入名为 fitz

2. 核心功能概览

除了我们今天要用的"提取文本",它还支持:

  • 文档渲染:把 PDF 页面转成高清图片(PNG/JPG),常用于做文档预览。
  • 文档编辑:合并 PDF、拆分页面、添加水印、绘制图形(矩形、圆形)。
  • 元数据提取:获取作者、创建时间、关键词等信息。
  • 图像处理:提取 PDF 中嵌入的所有图片。

3. 代码实现

我们封装一个简单的函数,不仅能读文本,还能顺便看看文档的基本信息。

python 复制代码
import fitz  # PyMuPDF

def read_pdf_advanced(file_path):
    """
    使用 PyMuPDF 读取 PDF,并提取文本和元数据
    """
    try:
        doc = fitz.open(file_path)
        
        # 1. 提取元数据(可选)
        metadata = doc.metadata
        print(f" 文档标题: {metadata.get('title', '无标题')}")
        print(f" 总页数: {doc.page_count}")
        
        # 2. 提取全文文本
        full_text = ""
        for page in doc:
            full_text += page.get_text()
            
        return full_text
    except Exception as e:
        print(f" 读取失败: {e}")
        return ""

# 模拟读取(如果没有真实文件,这里用长文本模拟)
raw_text = read_pdf_advanced("handbook.pdf")

# 如果没有文件,为了演示后续流程,我们使用这段模拟文本
if not raw_text:
    raw_text = """
    ### 数据库初始化步骤
    1. 首先,你需要配置环境变量。请确保在 .env 文件中填写正确的数据库地址。
    2. 接下来,运行初始化脚本。在终端输入以下命令:python init_db.py --config default.json
    3. 最后,检查日志。如果看到 "Success" 字样,说明启动成功。
    """
第二阶段:智能切分策略

拿到文本后,我们不能直接扔给 AI,必须切分。这里我们对比一下"暴力切分"和"智能切分"的区别。

环境准备:

由于 LangChain 的架构调整,切分器现在是一个独立的包。

bash 复制代码
pip install langchain-text-splitters

切分策略对比:

策略 行为模式 结果预测 适用场景
固定长度切分 像切香肠一样,每 50 个字切一刀,不管是不是切断了单词。 容易出现 python initi 这种残缺代码,AI 无法理解。 处理纯乱码或无空格的特殊数据。
递归字符切分 优先找段落换行符 \n\n,找不到再找句号 ,最后才按字符切。 保持了句子和段落的完整性,AI 读起来更顺畅。 绝大多数 RAG 场景(推荐)

代码实战:

注意看 chunk_overlap(重叠区)的设置,它就像"上集回顾",保证了上下文的连贯性。

你可以把 overlap 理解为"砌墙时的水泥"或者"拼图的重叠边"

它不仅仅是为了回顾剧情。

更是为了粘合。它确保即使切分刀法再狠,关键的句子结构(比如主语、谓语)也不会被彻底切断,让 AI 在阅读每一个片段时,都能稍微"瞥见"一点前一个片段的内容。

python 复制代码
from langchain_text_splitters import CharacterTextSplitter, RecursiveCharacterTextSplitter

print("=== 切分策略对比演示 ===\n")

# --- 策略 A:固定长度(反面教材) ---
# separator="" 意味着强制按字符切,不按语义
fixed_splitter = CharacterTextSplitter(
    chunk_size=50, 
    chunk_overlap=0, 
    separator="" 
)
fixed_chunks = fixed_splitter.split_text(raw_text)

print(" 固定长度切分 (注意看代码被切断了):")
print(f"[块1] -> {fixed_chunks[0]}")
print("-" * 50)

# --- 策略 B:递归切分(正确姿势) ---
recursive_splitter = RecursiveCharacterTextSplitter(
    chunk_size=80,       # 每个块的目标大小
    chunk_overlap=20,    # 重叠 20 个字符,保留上下文
    separators=["\n\n", "\n", "。", " ", ""] # 切分优先级:段落 > 换行 > 句号 > 空格
)

recursive_chunks = recursive_splitter.split_text(raw_text)

print(" 递归智能切分 (语义完整,带有重叠):")
for i, chunk in enumerate(recursive_chunks):
    print(f"[块 {i+1}] (长度: {len(chunk)}) -> {chunk}")
    print("-" * 50)
第三阶段:向量入库(ChromaDB)

切分好的文本块,我们需要把它们存起来,并且要存成"向量"格式,以便进行语义搜索。

流程图:数据是如何入库的?
翻译成数字向量
存储
文本块
嵌入模型 Embedding Model
向量
ChromaDB 数据库
索引完成

代码实现:

这里我们需要安装 chromadbsentence-transformers(用于提供嵌入模型)。

bash 复制代码
pip install chromadb sentence-transformers
python 复制代码
import chromadb
from sentence_transformers import SentenceTransformer

# 1. 初始化设置
# 创建一个本地持久化数据库,数据会保存在 ./rag_db 文件夹中
client = chromadb.PersistentClient(path="./rag_db")
collection = client.get_or_create_collection(name="employee_handbook")

# 加载嵌入模型 (这里使用一个轻量级的多语言模型)
embedding_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

# 2. 数据入库
# 我们需要把文本转换成向量,然后存入 ChromaDB
print(f"\n 正在处理 {len(recursive_chunks)} 个文本块...")

documents = []
embeddings = []
ids = []

for i, chunk in enumerate(recursive_chunks):
    # 生成向量 (将文本转化为计算机可计算的数字列表)
    vector = embedding_model.encode(chunk).tolist()
    
    documents.append(chunk)
    embeddings.append(vector)
    ids.append(f"chunk_{i}")

# 批量添加
collection.add(
    documents=documents,
    embeddings=embeddings,
    ids=ids
)

print(" 成功入库!知识库已就绪。")
第四阶段:检索测试(开卷考试)

最后,我们模拟用户提问,验证系统是否真的"学会"了这些知识。

python 复制代码
# 用户提问
query = "怎么初始化数据库?"

# 1. 将问题向量化
query_vector = embedding_model.encode(query).tolist()

# 2. 在 ChromaDB 中搜索最相似的 Top 1 结果
results = collection.query(
    query_embeddings=[query_vector], # 注意:chromadb 要求传入列表
    n_results=1
)

# 3. 展示结果
retrieved_text = results['documents'][0][0]
print(f"\n 用户提问:{query}")
print(f" 系统检索到的参考资料:\n{retrieved_text}")
总结

通过今天的实战,我们不仅解决了一个 PDF 读取的问题,更重要的是理清了 RAG 系统的"三驾马车":

  1. PyMuPDF:负责把非结构化数据(PDF)变成结构化文本。
  2. LangChain Text Splitters:负责把长文本变成 AI 能消化的语义块。
  3. ChromaDB:负责把这些块变成向量并建立索引。

现在,你的系统已经具备了"记忆"能力,下一步就是接入大模型(LLM),让它根据这些记忆来回答问题了!

相关推荐
Flying pigs~~3 小时前
检索增强生成RAG项目tools_03:mysql➕redis➕milvus
人工智能·agent·milvus·rag·智能体·检索增强生成
guslegend3 小时前
第9节:FAISS,HNSW还是BM25?如何选择最适合业务的向量检索引擎?如何选择最适合业务的向量检索引擎
人工智能·大模型·faiss·rag
发光的叮当猫3 小时前
AI工程可能会遇到的一些问题
人工智能·微调·rag·ai工程
QC·Rex1 天前
向量数据库架构与应用实战:从原理到生产部署
向量数据库·rag·相似度搜索·hnsw 算法·ai 基础设施
deephub1 天前
Karpathy的LLM Wiki:一种将RAG从解释器模式升级为编译器模式的架构
人工智能·大语言模型·知识库·rag
guslegend1 天前
第8节:打造可配置,可扩展的自动化预处理流水线
人工智能·大模型·rag
guslegend2 天前
第6节:OCR文本错漏频发?结合LLM纠错,让图像文本也能精确使用
人工智能·大模型·ocr·rag
@atweiwei2 天前
langchainrust:Rust 版 LangChain 框架(LLM+Agent+RAG)
开发语言·rust·langchain·agent·向量数据库·rag
小饕3 天前
RAG 学习之-向量数据库与 FAISS 索引完全指南:从原理到选型实战
人工智能·rag·大模型应用