🔥 上一篇讲完 RAG 是什么,很多人以为「切块 → 向量化 → 存库」一行字就过去了。 但真动手做过的人都知道:这一行字里的水,比后面所有环节加起来还深。 这篇把「离线入库」这条线从例子讲到流水线,再拆成三段细抠。建议先赞后看,不然刷没了。
写在前面:那个被你一眼带过的例子
你问模型:「我们公司报销制度里,出差高铁票超过多少要提前审批?」它答不上来,因为没读过你们制度。RAG 的做法是:先去《报销制度.pdf》里捞出相关的几段,拼进提示词,让模型照着答。
流程看着很顺。但它悄悄跳过了一件事——
那份 PDF,是怎么变成「可以被检索的几段」的?
RAG 的最小流程,第一步通常就一行:
入库(离线):把《报销制度.pdf》切块 → 每块向量化 → 存进向量库。
一行字,三个动作。讲 RAG 时注意力都在检索和生成上,这一行就这么被带过了。但真要做,你会发现这行字里的水,比后面所有环节加起来还深。
一、把例子做厚:300 页手册的「灵魂三问」
把资料换真实一点。你现在要做的不是 30 页报销制度,而是某设备厂商的售后知识库——一份 300 页的《产品服务手册》:
- 📝 主体是文字章节,讲安装、保养、故障排查
- 📊 中间夹着十几张参数表(型号 / 保修期 / 服务方式)
- 🖼️ 几页是安装流程图,信息全在图里
- 📷 最后 20 页是扫描件——老型号纸质文档拍的照片
- 🗂️ 文件夹里还躺着三个版本:
手册 v1.pdf、手册 v2.pdf、手册(最终版).pdf
客服要用它答客户问题,比如「XR-300 的保修期是多久」。
现在回头看「切块 → 向量化 → 存进向量库」,你会发现每一步都在反问你:
| 那行字 | 它实际在问你 |
|---|---|
| 「切块」 | 切成多大?按什么切?那张参数表切成两半后,「3 年」还知道属于哪个型号吗? |
| 「向量化」 | 用哪个模型?中文技术手册和英文通用语料,用同一个模型合适吗? |
| 「存进向量库」 | 三百页切下来上千个块,再乘以几百份手册,用户问一句要等几秒? |
这三个问题,分别对应资料准备、转向量、存库三个环节。它们合起来,就是入库要做的事。
二、一句话说清痛点:资料和「能被检索」之间,隔着好几道转换
你的资料和「能被检索」之间,隔着好几道转换。
- PDF 不是文本;
- 表格不只是字;
- 三百页不能整个塞进去;
- 中文技术词和通用语义不是一回事;
- 几十万个块不能挨个比对。
而且这些转换有个共同特点:它们全发生在用户提问之前。 用户问「XR-300 保修期多久」,他等的是那零点几秒的响应,不会想到你在背后花了三小时把三百页手册拆成上千个块。
💡 一句话记住离线:提前把重活干完,让真正应答的时候只做最轻的事。
三、先分清两件事:离线 vs 在线(分界线图)
往下讲之前,先钉死两个词,后面全靠它们。
还是那份手册。要让客服能查它,你得先干一件事:把三百页拆块、转向量、存索引。这一步花三小时,做一次,之后能用好几个月。干这活时系统还没上线,客服也还没问。
→ 这叫离线(也叫入库、建索引)。
做完系统上线,客服开始用。他每敲一个问题,系统就跑一遍:问题变向量 → 去索引找 → 排序 → 喂模型 → 返回。一天可能跑几千次。
→ 这叫在线(也叫应答、检索)。
🎯 区分标准只有一条:看它多久跑一次。 做一次、之后长期用的是离线;每次提问都跑一遍的,是在线。
切块 → 向量化 → 存进向量库 离线,做一次
↓
向量库 ← 两侧的分界
↓
问题 → 检索 → 拼进提示词 → 生成 在线,每次都跑
中间那个向量库就是分界线:离线往里写,在线往外读。 本篇只讲上半截——从原始文件到索引那一段。
四、为什么它要单独拎出来讲?心态差决定成败
因为它的节奏,和在线那一侧完全不同。
- 离线:做一次能用很久。做错了也没人知道,大不了重跑。三百页花三小时慢慢解析?没关系,这是一次性成本。所以你尽可以用最贵、最慢、但最准的工具。
- 在线:用户每问一次它就跑一次。慢一点用户干等,答错了用户立刻看见。你在这加任何环节,成本都要乘以每天的调用量。
⚠️ 最常见的心态错位:挑 PDF 解析器时纠结「这个一页要两秒,太慢了」——这纠结是错的。三百页跑一次十分钟,跑完就完了,你该关心它准不准、表格保不保得住。 反过来,等后面讲「重排」时还抱着「慢慢来」给它上个很重的模型,上线就会发现每次问答多等两秒,用户开始抱怨。
同样一句「用更好的工具」,在入库这一侧几乎总是划算的,到了检索那一侧就要精打细算。
五、离线流水线全景:7 步走完
它是一条流水线,把原始文件变成可检索的索引。拿那份手册从头到尾走一遍:
| 步骤 | 动作 | 手里拿的 → 交出去的 | 要盯的点 |
|---|---|---|---|
| ① 解析 | PDF+20 页扫描件 → 纯文本 | 原始文件 → 纯文本 | 参数表散架没?章节标题还在不在?扫描件单独走 OCR |
| ② 清洗 | 去噪、理版本、保结构 | 纯文本 → 干净文本 | 三个版本理清留一个,保住标题层级给 ④ 用 |
| ③ 标元数据 | 标位置(第几页/章/条/版本/谁能看) | 干净文本 → 带位置的文本 | 位置信息最全,切完就找不回 |
| ④ 切块 | 512 字一块、相邻留重叠 | 带位置文本 → 上千个块 | 句子被拦腰切断没? |
| ⑤ 向量化 | 上千块过 embedding 模型 | 上千个块 → 上千个向量 | 选哪个模型?中文看中文榜,客服要用同一个 |
| ⑥ 建索引 | 建向量索引 + 字面索引 + 过滤配置 | 上千向量 → 可检索索引 | 配好按字段过滤(哪些归哪个部门看) |
| ⑦ 维护 | 新版/下架/权限变了,索引跟着变 | 可检索索引 → 跟着变化更新 | 最容易漏的一环 |
原始文件
↓ ① 解析
纯文本
↓ ② 清洗
干净文本
↓ ③ 标元数据
带位置的文本
↓ ④ 切块
上千个块
↓ ⑤ 向量化
上千个向量
↓ ⑥ 建索引
可检索的索引
↓ ⑦ 维护
跟着资料变化更新
🔥 三次「带代价的变换」(这张表建议截图)
流水线里有三次变换是牺牲了东西换来的,值得单独记住:
| 步骤 | 丢掉了什么 | 后来在哪补回来 |
|---|---|---|
| ① 解析 | 版面信息(哪是表格、哪是标题、图里画了啥)→ 抽成纯文本就没了 | 第 ② 步专门「保结构」留下来 |
| ⑤ 向量化 | 字面精确性(XR-200 和 XR-300 向量可能很像) | 第 ⑥ 步同时建的字面索引补回 |
| ⑥ 建索引 | 一点精度换速度(找到的未必绝对最近,但足够近) | ——(这是 ANN 的天然权衡) |
🧠 实用心法:后面遇到任何一个技术,你都能问一句「它补的是哪一次损失」。答得出来,这个技术为什么存在就清楚了。
这三段合起来就是:①②③④ = 资料准备,⑤ = 转向量,⑥⑦ = 存库。 下面分别展开。
六、第一段:资料准备(①②③④)
对应解析、清洗、标元数据、切块。
📌 它解决什么痛点
你的资料,从格式到内容,几乎每个地方都在和「变成可检索的文本」作对。
-
PDF 的双栏陷阱:PDF 全称 Portable Document Format,设计目标是「任何设备看起来都一样」,本质是一套排版指令。它有文字层,但文字顺序未必是你眼睛看到的阅读顺序。一份双栏手册,抽出来可能变成「左栏第一段、右栏第一段、左栏第二段」这种交错。
-
表格散架:表格在 PDF 里往往只是一堆带坐标的字符。你看到的是:
型号 保修期 服务方式 XR-200 3年 上门 XR-300 5年 返厂抽出来变成:
型号 XR-200 3年 上门 XR-300 5年 返厂表头还在,但「3 年」属于哪个型号、是哪一列,全丢了。检索到这块时既含「3 年」也含「5 年」,模型只能猜。
-
扫描件:最极端——压根没文字层,整页就是一张图,不做 OCR 一个字都拿不到。你那 20 页老型号文档就属于这类。
💡 为什么需要它
因为后面所有环节处理的都是这一步的产出。内容在这步丢了,后面再强的模型、再精巧的检索,也只是在一堆残缺的块里挑挑拣拣。RAG 的效果上限,很大程度由资料本身质量决定——这话不是随便说的,这一步才是「资料质量」真正发生的地方。
🧩 它是什么
输入是 PDF、Word、网页、扫描件(乱七八糟什么都有);输出是上千个块,每块几百字,带着它来自哪一页、哪一条、谁能看。
🔧 四个动作,按顺序来
① 解析(把格式变成文字)
坑按格式分:
- PDF:双栏乱序 + 表格散架
- Word / HTML:自带结构标签,相对好办,但容易把「看起来像标题的东西」当成真标题
- 真正难点是表格——不是把字抽出来,而是抽出结构,知道哪一行属于哪个表头
- 扫描件:没 OCR 一个字都没有
- 图文混排:流程图、图表里的信息在纯文本里直接消失
工具梯度(按难度往上走):
| 工具 | 定位 | 适用 |
|---|---|---|
| PyMuPDF | 快而轻 | 文字层干净的 PDF |
| MinerU / Marker | 做了版面分析,保标题层级 + 表格结构 | 中文 PDF 表现更好 |
| PaddleOCR | OCR | 扫描件 |
Databricks ai_parse_document 等 | 高质量版面解析(非通用文本抽取) | 表格/层级保不保得住是关键区别 |
✅ 怎么验收解析质量:笨办法但有效——随机挑几页,把抽出来的文本和原文对着读一遍(尤其表格页、带层级的章节页);更工程化的办法——换两种解析器跑同一批问题,看检索指标差多少。差得多,说明解析就是瓶颈。这步偷懒,后面所有调试都会变成无头公案。
② 清洗(让抽出来的字能用),三件事:
- 去噪:页眉页脚页码水印乱码。看着无害,但每页都重复,会被切进每一个块。三百页每页页眉都是「XX 公司产品服务手册 · 内部资料」,你的向量库就会有一大批块开头十几个字一模一样——重复内容稀释语义、降低区分度。
- 归一化:去重 + 处理版本。你那三个版本(v1/v2/最终版)高度重叠,不处理,检索会召回好几个几乎一样的块挤占候选,还不知该信哪个。
- 保结构:标题层级、列表、表格。最易被跳过、后果最严重——因为切块主流做法是按文档结构切(按标题/章节/条款),标题层级在清洗时打平了,这条路就断了,只能退回按字数硬切。
🎯 清洗的判据不是「看着干净了」,而是下游需要的信息还在不在。
③ 标元数据(给每块贴「从哪来」)
- 显式元数据(客观事实:哪个文件、第几页、第几章第几条、什么时间、谁有权看):
- 引用溯源:答「5 年」能附「来自《产品服务手册》第 47 页 3.2 条」,没这个答案没法核验,RAG 的溯源优势也没了;
- 权限过滤:检索时先筛掉该用户看不到的块,而不是答完再删(后者等于已经泄露)。
- 语义元数据(给检索补信号,如文档摘要或块所在章节标题,拼进块一起向量化):孤立的块往往缺上下文。第 47 页有块写「保修期为 5 年」,自己没说哪个产品;前面拼上「第三章 XR-300 产品规格」,才能被正确检索到。
⚠️ 必须在切块之前做:元数据是按块标的,你得先知道每块落在文档的什么位置。切完再补等于重新定位,而位置信息已经没了。
④ 切块(切成多大一块)
两难:上下文窗口有限,三百页必须切块只捞相关;但切法影响质量——太碎句子被拦腰切断读不懂,太大混两个不相干主题、向量被稀释什么都不像。这题没有理论最优解,答案是实验出来的。
七种切法(越往下越精确,但对上游结构依赖越强、成本越高):
| 切法 | 做法 | 适合 |
|---|---|---|
| 固定大小(按字数) | 最省事 | 永远从这里起步 |
| 递归字符切法 | 先段落 → 不行按句子 → 再按字符 | 通用文本 |
| 文档结构切法 | 按标题/条款/章节 | 手册、规范、合同(前提:标题层级在清洗时保住) |
| 语义分块 | 按相邻句子向量相似度找边界 | 叙述性长文 |
| 父子块 | 小块检索、命中返回所在大块 | 既要定位精确又要上下文完整 |
| 句子窗口 | 句子为单位检索、返回带前后几句 | 事实型问答 |
| 命题级切块 | 切成原子命题 | 精度最高,代价是入库多跑一次模型 |
参数两个:
- 块大小三档:256 偏精确事实、512 均衡、1024 偏上下文完整。问「保修期多久」小块够,问「整个安装流程分几步」块太小会漏。
- 重叠:相邻两块故意重复一小段,专防一句话被切断。
😂 这个坑有多常见:我拿《RAG 基础概念》那份文档切块,固定 120 字符、重叠 30,其中一个块结尾是:
为什么要切:上
后半句跑到下一块去了。这个块被检索到时,读到的是一句没说完的话。
🎯 怎么选(别拍脑袋):先用固定大小跑出基线,再根据暴露的问题换策略(连基线什么样都不知道,就没法判断换策略有没有变好);然后用检索指标做网格实验,把块大小和重叠各取几档,跑评测集看 Recall@k 怎么变。这步不难,但绝大多数人跳过了,凭感觉定个 512 就一直用下去。
🔗 顺序为什么不能乱
解析 → 清洗 → 标元数据 → 切块是链式的。两个最常见错误:
- 先切块再想起来标元数据 → 位置信息已丢,只能拿块内容反推;
- 清洗时把标题层级打平了,然后想按文档结构切 → 结构信息在清洗那步就没了。
这一环节真正的纪律不是「每一步做得多好」,而是顺序不能乱,且每一步都留下下游需要的信息。
🚀 两个进阶项
- 多模态入库(处理流程图):第一档让视觉语言模型(VLM)给图片生成文字描述当普通文本块入库,改动最小但细节会丢;第二档 ColPali,跳过 OCR 直接把页面图像编码成多向量,保住表格/版式/图注。文字为主用第一档,图表密集用第二档,混合的就两路都走再融合。
- 图谱构建(从文本抽实体和关系建知识图谱):三种范式——规则抽取快但脆(否定句是硬伤)、LLM 抽取准但贵、混合流水线折中。注意区分:建图谱是入库时做的事,用图谱做检索是另一回事,放更后面讲。
🧪 验收
一份 300 页的产品服务手册,你会怎么切?为什么?
七、第二段:转向量(⑤)
对应向量化。
📌 它解决什么痛点
文字之间,没法直接算「像不像」。
客户问「XR-300 能保多久」,手册写的是「XR-300 保修期 5 年」。「能保多久」和「保修期」字面上一共重合几个字?一个都没有——按字面匹配搜,一个都搜不到。
反过来,手册里同时有「XR-200 保修期 3 年」和「XR-300 保修期 5 年」,这两句字面重合度极高。按字面搜「XR-200」,两句都命中,模型看着两个数字开始猜。
字面匹配两头不讨好。需要的不是「字像不像」,而是「意思像不像」。
💡 为什么需要它
因为检索要按语义来,不是按字面来。这是整个 RAG 的地基——所谓「把问题变成可检索的形式(通常是向量)」,说的就是这一步。
🧩 它是什么
把一段文本变成一串数字。核心性质只有一句:语义相近的文本,向量也相近。 有了这个性质,「意思像不像」就成了一道能算的题——两串数字方向多接近,用余弦相似度一算就知道,不需要任何字面重合。
🔧 主要流程
选一个 embedding 模型,把上千个块挨个喂进去,得到上千个向量。流程简单得没什么可说,难点全在选型。
🚨 它有个特殊身份:唯一横跨两条管道的部件。 离线侧用它把文档变向量,在线侧还要用它把用户问题变向量。所以有个踩了就全盘皆错的约束:两边必须是同一个模型。 原因:不同模型把文本映射到的向量空间不一样。A 模型建索引、B 模型算问题,再比相似度——等于在两张不同的地图上量距离,数字毫无意义。而且这种错不会报错,只会让你的检索效果莫名其妙地差。
📚 涉及的知识点
- 余弦相似度:两个向量方向的接近程度,检索时的打分依据。
- 维度:向量有多长,常见 768 / 1024 / 1536。同一任务必须统一,不能一半 768 一半 1536 混着来。
- 多语言:要做跨语言检索就得选支持多语言的模型。
- Matryoshka(嵌套表征):可变维度,允许把向量截断一截换速度,精度损失可控。想先跑起来再优化延迟,这个特性很好用。
- 多向量表示:一个块不只用一个向量,而是多个。是晚交互路线(ColBERT 那类)的基础,比单向量准,代价是存储膨胀。
- 怎么选型(看三个基准):
- MTEB:50 多个数据集上的 embedding 综合排行榜;
- CMTEB:中文榜——中文场景一定要看这个,别只盯英文榜,两边排名差异可以很大;
- BEIR:18 个数据集的零样本检索基准,更偏检索器整体选型。
- BGE-M3 值得单独提:一个模型同时出稠密向量、稀疏向量、多向量,等于把后面混合检索的成本砍掉一半。中文场景基本是默认选项之一。
- 要不要微调:垂域可以微调 embedding,但判定标准要清楚——大多数场景不需要。只有你的领域黑话密集到通用模型明显抓不住,而且你有标注数据,才值得做。
- ⚠️ 必须记住的坑:换 embedding 模型等于全量重建索引。新旧向量不在同一空间,不能混用。所以选型要在建库之前定下来,别建了一半换。
🧪 验收
中文法律文档问答,你选哪个 embedding 模型?依据是什么?
八、第三段:存库(⑥⑦)
对应建索引与之后的维护。
📌 它解决什么痛点
块少的时候没这问题。三百页切出一千个块,挨个算相似度也就几毫秒。
但真实场景是几百份手册、几十万个块。用户问一句,你把几十万个向量挨个比一遍——这个延迟没人受得了。
💡 为什么需要它
速度。而且不只是速度,还有过滤:检索时要能按条件缩圈,比如这个客服只能看消费级产品文档,不能看工业级的。
🧩 它是什么
把向量组织成一种结构,让「找出最像的几个」这件事足够快。
🔧 主要流程
建索引,同时并行建一份稀疏索引,配好过滤条件。
📚 涉及的知识点
-
为什么用近似检索:精确最近邻要挨个比对,量一大做不到。ANN(近似最近邻) 接受「找到的未必绝对最近,但足够近」,用一点点精度换回大量速度。核心权衡是召回率、延迟、内存三者平衡。
-
两种索引结构:
索引 原理 特点 可调参数(本质都是召回率↔延迟) HNSW 图索引 快,但吃内存 efSearchIVF + 量化 聚类 + 压缩 省内存,精度略低 nprobe -
稀疏索引要并行建(容易漏):后面混合检索是向量一路 + 字面匹配(BM25)一路,各出一张榜再融合。融合发生在检索时,但字面那一路的倒排索引必须在入库时就建好。BM25 参数:
k1一般 1.2~2.0,b取 0.75。 -
元数据过滤两种做法:
pre-filter先按条件缩圈再找近邻;post-filter先检索再筛。它的隐藏身份:同时就是权限控制的实现方式——「只查这个部门能看的文档」本身就是一次过滤。 -
多向量的存储问题:走晚交互(ColBERT 那类),文档是 token 级向量,存储膨胀很多倍,要有量化和 token pooling 这些压缩手段。
-
怎么选库:
库 定位 FAISS 库内嵌,轻量 Chroma 本地起手,开发友好 pgvector 已有 Postgres 直接能用 Qdrant / Milvus 上量、要分布式、要混合检索原生支持 Weaviate 自带混合检索、模块化 何时必须换?三个信号:并发、持久化、混合检索的原生支持。部署一般从单机(Milvus Lite、Docker Compose)演进到云原生(Milvus 集群或托管)。
-
增量更新与版本管理:新增文档不该每次全量重建,要支持增量入库。删除同步是最容易出问题的一环——文档下线、权限变了,索引里的旧块有没有跟着删,很多系统会漏。索引本身也要有版本方便回滚。质量检查(空块、重复块、超长块、元数据缺失)可以用代码断言自动化跑。
🧪 验收
10 万个块,要按部门权限过滤,每天更新,你怎么选库?索引怎么配?
九、一个反直觉的地方:重跑的代价不在计算,在连锁反应
前面说离线「做错了大不了重跑」,这话没错,但漏算了代价。
重跑的成本不在计算,在于连锁反应。假设你的切块策略有问题,很多句子被切断。你基于这套索引,花两周把在线侧的相似度阈值、候选数量、重排模型都调好了,指标看着还行——因为你在用一套有缺陷的块去迁就。这时候你回头改了切块策略,索引重建了,然后你会发现:下游那些调好的参数全部作废,得重新调一遍。 块粒度变了,最优候选数量变了,重排模型表现也变了。
🎯 正确心态:既不是「反正能重跑随便搞」,也不是「必须一次做到完美」,而是尽早把它做到能验证的程度,然后用评测集固定下来。之后再动它,就要有心理准备:下游要跟着重调。
这也解释了一件看起来奇怪的事——入库虽然排在最前面,却需要一个属于检索和生成环节的工具(评测集)来兜底。
十、总验收 + 知识地图
🧠 自测三连(评论区交作业)
- 一份 300 页手册从原始文件到能检索,中间经历了哪几次形态变化?每次得到什么、丢掉什么、后来在哪补回?
- 为什么 embedding 模型「离线建索引」和「在线算问题」必须用同一个?用不同模型会怎样?
- 10 万个块、要按部门权限过滤、每天更新——你选哪个向量库?索引怎么配?
🗺️ 知识地图(形态变化链)
原始文件 ──①解析──▶ 纯文本 ──②清洗──▶ 干净文本
│ │
│ ③标元数据▶ 带出处的文本
│ │
└──────────────────④切块──────────▶ 上千个块
│
⑤向量化▶ 上千个向量
│
⑥建索引+字面索引+过滤▶ 可检索索引
│
⑦维护▶ 跟着资料变化更新
能顺着「原始文件 → 纯文本 → 带出处的块 → 向量 → 索引」这条线,把每一跳的得失说清楚,入库这条线就立住了。
写在最后
很多人以为 RAG 的难点在「检索和生成」,但真正的水,全在上线之前那行被一眼带过的「切块 → 向量化 → 存库」里。
解析抽散了表格、清洗打平了标题、切块切断了句子、embedding 选错了榜、索引建漏了过滤——随便哪一步偷懒,后面调再久的检索参数都是无头公案。
👍 觉得有用的话,点赞 + 收藏 + 关注 三连走起。 💬 评论区聊聊:你做 RAG 入库时,踩过最离谱的解析/切块坑是什么?下期想看「解析篇」硬核拆解,还是「检索篇」实战,留言告诉我。
下一篇预告:《解析篇——把格式还原成文字的第一道关》。