「从零到 AI 应用工程师」专栏 · 第 10 篇
RAG 的第一公里不是向量,是干净的文本。
PDF 抽出来乱码、表格碎成渣、手册切成互不相关的半句话——后面 Embedding 再强也是「垃圾进、垃圾出」。
今天两件事:解析(ingest)和分块(chunk)。
一、今天要做出什么
输入:一份 Markdown 或 PDF 样例手册。
输出:若干切片,每条至少带:
{ "text": "……切片正文……", "meta": { "doc_id": "文档指纹", "page": 3, "file_type": "pdf", "source": "示例故障码手册.pdf" } }
验收标准很朴素:能打印出 chunk 数量,抽几条人眼能读懂,页码/来源还在。
二、解析:先变成「页/篇」级文本
Markdown
相对简单:读 UTF-8 → 清洗多余空白 → 往往整篇先当一块,页码下游再标 -1。
按页抽字(常用 pypdf 一类库):
from pypdf import PdfReader def parse_pdf(path: str) -> list[dict]: reader = PdfReader(path) chunks = [] for i, page in enumerate(reader.pages, start=1): text = (page.extract_text() or "").strip() if not text: continue chunks.append({ "text": text, "meta": {"page": i, "file_type": "pdf", "source": path}, }) return chunks
清洗建议:
- 去掉页眉页脚水印行(如「第 N 页 | 某某手册」);
- 折叠过量空行;
- 入库前脱敏手机号、SN 等(公开演示必备)。
doc_id 可用路径 MD5,保证同一文件多切片可追溯、可按文档删除。
三、分块:为什么不能整本塞进模型
| 策略 | 问题 |
|---|---|
| 整本进 Prompt | 超长、贵、噪声大 |
| 切太碎(几十字) | 语义残缺,检索飘 |
| 切太肥(上千字) | 命中后上下文冗余,易带跑偏 |
工程上常用 递归字符切分:按段落 → 句号 → 逗号 → 空格依次降级切开,并保留 overlap(重叠),减少「答案正好砍在刀口上」。
中文友好分隔符示例:
\n\n → \n → 。 → , → 空格
一组可作起点的参数(需用评估集再调):
| 参数 | 建议起点 | 含义 |
|---|---|---|
chunk_size |
500 字左右 | 单片目标长度 |
chunk_overlap |
100 字左右 | 相邻重叠 |
# 伪代码:RecursiveCharacterTextSplitter 一类工具 slices = splitter.split( pages, # 解析结果 chunk_size=500, chunk_overlap=100, ) # 每条继承/补齐 doc_id、page、source
四、元数据不是可选项
向量库若只存向量、不存原文和 meta,你会失去:
- 给大模型看的原文;
- 页码 / 文件名溯源;
- 按
doc_id过滤或整文档删除; - 按业务类别过滤(手册 / 故障码 / 工单)。
记住:text + meta 一起进下游;向量只是检索钥匙。
五、常见坑
- 扫描版 PDF 无文字层 → 抽出来空白,需要 OCR(本专栏先用可选中文字的 PDF)。
- 表格被抽成乱序 → 关键表可转 Markdown 或单独策略。
- 只按固定字数硬切 → 句子从中断开,检索命中率差。
- 忘记 overlap → 跨段知识点经常丢。
- 清洗过度 → 把故障码、阈值电压也洗掉了。
六、带走这三条
- 解析质量决定上限;向量救不了乱码和空页。
- 分块要有重叠、分隔符要适应中文。
- meta(doc_id/page/source)与正文同生共死。
下一篇:Embedding 与向量库——切片如何变成可计算的距离,以及 FAISS / pgvector 怎么选。
这是专栏第 10 篇。两到三天一更,向量检索见。