AI应用高级工程师 · 第04 / 12章

RAG数据工程
文档从上传到可检索

上传PDF不等于知识库已经完成。真正的数据工程要把杂乱文件变成可追踪、可过滤、可更新、不会半成品上线的检索记录。

解析与清洗Chunk切块Metadata版本发布

为什么有了向量数据库还要做数据工程

第3章已经会把一段干净文字变成向量,再找相似片段
→
现实中的问题公司给你的却是 PDF、扫描件、表格、旧版本和权限复杂的文件
→
本章解决把原始文件可靠地加工、切块、加标签、向量化并安全发布
本章默认方案后台异步入库;政策文档按结构切块,过长时递归切分,并用父子切块;子块检索,命中后取回父块;必要时重叠 1—2 句;staging 验收后一次切换 active。

路线12章中的位置

01 模型与上下文 ✓
02 结构化输出 ✓
03 Embedding与向量 ✓
04 RAG数据工程
05 检索、Rerank与评测
06 工具调用
07 Agent与工作流
08 状态与记忆
09 评测与观测
10 AI安全
11 成本与性能
12 部署与LLMOps

本章目录

阅读过程中可按 Ctrl + Z 快速返回当前页面的本章目录;也可以点击右下角按钮。

本章思维导图

知识流向:先完成左侧基础,再进入中间机制,最后用右侧工程控制把它变成可上线能力。节点可直接点击跳转。

MODULE 01

文档采集与解析

按照本模块顺序完成以下知识点,再进入下一模块。

R01PDF 直接检索的局限

人看到的PDF

标题、段落、表格、页眉页脚、图片和页码组合成完整政策。

程序可能读到的内容

文字顺序错乱、表格拆散、扫描页没有文字、重复页眉、乱码和空白。

仓库类比:上传PDF只是货车把整箱货送到仓库门口;解析、清洗、贴标签、分货架、登记库存后,才真正可用。

生活中的例子:PDF像一整箱混装货物,送到仓库门口不等于已经分拣上架。

R02完整离线入库流水线

采集文件
→
解析内容
→
清洗与结构恢复
→
切块+Metadata
→
Embedding
→
写入、校验、发布

这通常是后台异步任务,不是在用户每次提问时重新处理整份PDF。用户查询只使用已经成功发布的知识版本。

生活中的例子:入库流水线像食品加工:收货、清洗、切分、贴标签、质检、合格后上架。

R02-A异步入库机制

同步是上传请求一直等到所有步骤完成;大 PDF 可能处理几分钟,网页会长时间转圈。异步是上传接口先登记任务并立刻返回任务编号,由后台工作进程继续处理。

上传接口
保存文件
任务表/队列
生成 job_id
Worker
逐步处理
状态表
记录进度与错误
发布服务
验收后上线
POST /knowledge/files
返回:{"job_id":"job_7821", "status":"queued"}

后台状态:
queued → parsing → chunking → embedding → validating → published
                         ↘ 任一步失败:failed + error_message

GET /knowledge/jobs/job_7821
返回:{"status":"embedding", "progress":72}

任务队列

位置:上传 API 与后台 Worker 之间

保存“有文件待处理”的消息。可用 Redis、RabbitMQ 或云队列。它不理解文档,只负责可靠交付任务。

Worker

位置:后台独立进程/服务

真正执行解析、清洗、切块和 Embedding。失败时记录步骤;重试使用相同 ID,避免重复数据。

失败边界:只有全部片段成功、数量检查通过、权限版本齐全、关键问题召回正常,才能从 staging 切到 active。失败半成品永远不能被线上检索。

生活中的例子:异步像洗衣店先给取件号,顾客不用站在机器旁等,后台洗好后再更新状态。

R03采集:知识从哪里进入

来源采集方式必须记录
PDF/Word/Excel用户上传、共享目录、对象存储文件名、哈希、上传者、时间、权限
网页受控爬取或官方APIURL、抓取时间、版本、访问范围
业务数据库SQL查询、CDC或定时同步主键、更新时间、数据归属
SaaS系统授权API/Webhook外部ID、同步游标、授权范围

文件哈希:可把文件内容计算成唯一摘要。相同哈希说明内容未变,可以避免重复解析和重复Embedding。

生活中的例子:采集像仓库收货,必须登记供应商、批次、时间和谁有权使用。

R04解析:把文件变成可处理内容

文本型PDF

可以直接提取字符,但需要恢复阅读顺序、标题和页码。

扫描PDF/图片

本质是照片,需要OCR识别文字;还要记录OCR置信度。

表格和版面

不能只按行抽字,需要识别表头、单元格、跨页关系和版面位置。

输入:refund-policy-v3.pdf

解析输出示意:
{
  "pages": [
    {"page": 1, "blocks": [{"type": "title", "text": "退款政策"}]},
    {"page": 2, "blocks": [{"type": "paragraph", "text": "退款将在..."}]}
  ]
}
生活中的例子:解析像把扫描的纸质菜单录入系统,还要恢复标题、表格和正确阅读顺序。

R05文档清洗原则

通常需要处理
  • 每页重复的页眉页脚
  • 异常空格和乱码
  • 错误换行和断词
  • 导航菜单、广告等无关内容
  • 明确重复的正文
不能盲目删除
  • “不”“禁止”等否定词
  • 表格中的单位与列名
  • 条款编号和生效日期
  • 例外条件
  • 来源与页码

清洗后的文本必须能够追溯到原文件位置;否则模型引用出错时无法回看原文。

生活中的例子:清洗像摘菜,去掉泥土但不能把“不含花生”中的“不”一起删掉。
MODULE 02

切块策略

按照本模块顺序完成以下知识点,再进入下一模块。

R06Chunk 切块的定义

Chunk是向量检索的基本单位。整份政策太大,不能每次全部放进Prompt,因此把它拆成一段段可独立理解的内容。

退款政策
Chunk 1:适用范围——签收七日内且商品完好可申请退货。
Chunk 2:退款时间——审核通过后三个工作日内原路返回。
Chunk 3:特殊商品——定制商品不适用无理由退货。

文档不是按“文件”检索 → 而是按“能回答一个局部问题的片段”检索

生活中的例子:Chunk像把一本菜谱拆成一道道完整菜谱卡,搜索时不必搬整本书。

R07太大和太小都会出问题

Chunk太大
  • 一个片段混入多个主题
  • 相似度不够集中
  • 占用更多上下文Token
  • 召回后包含大量无关文字
Chunk太小
  • 条件与结论被拆开
  • 代词失去指向
  • 表头和表格内容分离
  • 检索结果无法独立理解

判断标准:不是追求固定字符数,而是让片段围绕一个相对完整的主题,并尽量保留回答问题所需的条件、结论和来源。

生活中的例子:切得太大像一张卡写十道菜,太小像把“盐2克”和菜名分到两张卡。

R08-A默认切块策略

对于我们要做的AI客服政策、产品手册、FAQ、制度文档,默认采用这一套组合:

① 先按文件结构切:标题 / 章节 / 条款 / FAQ / 一整张表格
② 某一段仍太长时,再递归地按段落、句子切小
③ 生成“小子块”用于Embedding和检索
④ 命中子块后,返回它所属的“父块”给大模型阅读
⑤ 相邻子块复制1—2句边界文字作为Overlap

结构优先 + 递归兜底 + 父子切块 + 小Overlap

方法本项目是否作为默认原因
按结构切块是,主方法政策与手册天然有标题、条款、FAQ;边界最符合人类阅读逻辑。
递归切块是,兜底某条款太长时,按段落再按句子逐级切,不会粗暴从句子中间截断。
父子切块是小块更容易精准命中;父块让模型看到完整条件、例外和结论。
Overlap是,少量防止刚好在条件与结论之间切断。
固定长度只作最后兜底最快,但最容易把句子、条款或表格切坏。
语义切块暂不作为默认要额外模型或规则判断主题边界,成本更高,结果也必须评测;等我们有评测集后再比较。

这不是说语义切块不好,而是对“退款政策”这种结构非常清晰的资料,优先利用已存在的标题和条款,比让模型猜主题边界更稳定、更便宜、也更好排错。

生活中的例子:政策文档先按章节切,就像搬家先按房间装箱,箱子太大再按抽屉继续分。

R08-B用同一份退款政策,逐步切成真实Chunk

假设解析后的原文如下。为了看清过程,下面故意写得短;真实文档可能更长。

《退款政策 v3》
一、适用范围
商品签收后七日内,且商品保持完好,可以申请退货退款。定制商品和已拆封的贴身用品不适用无理由退货。

二、申请材料
申请退款时,用户需要提交订单号、支付凭证和商品照片。资料提交完整后,客服将在一个工作日内审核。

三、退款到账
审核通过后,款项将在三个工作日内原路退回。银行卡到账时间可能受银行处理影响。

第1步:按结构找大边界

解析器先识别标题“适用范围 / 申请材料 / 退款到账”。每个标题下的内容暂时成为一个父块:

父块 P1:一、适用范围 + 两句正文
父块 P2:二、申请材料 + 两句正文
父块 P3:三、退款到账 + 两句正文

如果一个父块已经不长,例如P3,就不用为了凑数字而继续硬切。

第2步:对过长父块递归切小

假设P2在真实文档中有12段,太长。递归切块的真正做法是按优先级尝试边界:

先尝试按“标题”切
如果还太长 → 按“段落”切
如果单段仍太长 → 按“句号/问号/分号”切
如果单句仍特别长 → 最后才按Token数硬切

例如P2变成两个子块:

子块 C2-1:
标题:二、申请材料
申请退款时,用户需要提交订单号、支付凭证和商品照片。

子块 C2-2:
标题:二、申请材料
资料提交完整后,客服将在一个工作日内审核。

注意:子块里重复保留标题,不是浪费。用户单独问“审核多久”,即使只召回C2-2,模型仍知道它属于“申请材料”这部分,而不是把“一天审核”误解成退款到账时间。

生活中的例子:递归切块像先沿章节分,仍太长就沿段落分,再太长才沿句子分。

R08-C父子切块的结构与流程

父子切块是同一份原文存两种粒度:父块大一点,子块小一点。两者用 parent_id 关联。

父块 P2
申请材料完整段落
→
切成子块
C2-1、C2-2
→
只给子块做Embedding
用于精准检索
→
命中C2-2
取回父块P2给LLM
父块表 / 文档存储:
parent_id = P2
text = "申请退款时...一个工作日内审核。"

子块向量记录:
chunk_id = C2-1
parent_id = P2
text = "申请退款时,用户需要提交订单号、支付凭证和商品照片。"
vector = [...]

chunk_id = C2-2
parent_id = P2
text = "资料提交完整后,客服将在一个工作日内审核。"
vector = [...]

用户问:“审核要多久?”时:

问题 → Embedding → 命中子块 C2-2
→ 程序读取 parent_id=P2
→ 取回完整父块P2
→ LLM看到“要先提交哪些材料 + 审核多久”
→ 更不容易脱离条件回答
父子切块的优点父子切块的代价
子块小,检索更精准;父块完整,回答时不易丢条件;适合制度、手册、合同和FAQ。要额外维护parent_id;正文可能存两份或需回查父块;更新时必须同时更新父与子。

何时不必使用?如果每一个结构段本身已经很短、可以独立回答,例如一问一答的FAQ,直接把FAQ作为一个Chunk就够了。

生活中的例子:父子切块像商品小标签负责被搜索,找到后拿出包含完整说明的大包装给模型阅读。

R08-D语义切块的机制与适用边界

语义切块不是按固定长度,而是尝试发现“话题从这里变了”。常见实现有两种:

方法一:句子向量相似度

把文章拆成句子
→ 每句做Embedding
→ 比较相邻句向量相似度
→ 相似度突然降低的位置
   可能就是主题边界

例如前5句都讲退款条件,第6句开始讲物流;第5、6句相似度明显降低,就在中间切。

方法二:模型判断边界

把一小段连续句子交给LLM
→ 要求标出主题变化点
→ 在标记处切块

更灵活,但要额外调用模型,成本、速度和稳定性都需要评测。

它适合没有标题、没有段落、主题自然变化的长访谈、会议记录或连续文章。它不适合直接替代政策文档的结构切块,因为政策本来已经明确写了“第一条、第二条、例外情况”。

风险例子:模型可能把“定制商品不适用退货”误判为新主题而单独切出,结果回答时丢掉它属于退款政策的前提。因此语义切块必须与人工抽样和召回评测一起使用,而不是开关一开就认为更高级。

生活中的例子:语义切块像听演讲时在话题明显转折处换一页笔记,而不是固定每500字换页。

R08常见切块方法

方法做法适用风险
固定长度每N个字符或Token切一次快速原型、普通纯文本可能从句子和表格中间切断
递归切块优先标题、段落、句子,太长再继续切通用文档仍需要针对文档结构调整
按结构切块章节、条款、FAQ、表格作为边界政策、手册、合同解析器必须识别结构
语义切块检测主题变化位置长文章和主题自然切换内容成本更高,结果需评测
父子切块小块用于检索,命中后返回更大的父段落既需精准召回又需完整上下文存储与关联更复杂
生活中的例子:各种切块方法像不同分装方式,要根据政策、FAQ、表格和长文章选择。

R09-AOverlap从零解释:相邻Chunk故意复制的一小段

Overlap 的中文就是“重叠”。它不是新模型、不是数据库字段、不是检索算法。它只是切块时,把前一个Chunk末尾的一小段文字复制到下一个Chunk开头。

仍然以“申请材料”段落为例:

没有Overlap
Chunk A:申请退款时,用户需要提交订单号、支付凭证。

Chunk B:和商品照片。资料提交完整后,客服将在一个工作日内审核。

“支付凭证和商品照片”被切开。单独召回A或B都会不完整。

有Overlap
Chunk A:申请退款时,用户需要提交订单号、支付凭证和商品照片。

Chunk B:支付凭证和商品照片。资料提交完整后,客服将在一个工作日内审核。

“支付凭证和商品照片”在两个块中都存在,因此无论命中A或B,都能读到完整条件。

实际怎么设置

设置方式例子适合
按句子每个子块与下一个子块重叠最后1—2句政策、FAQ、普通中文文本;我们默认使用
按Token每块500 Token,下一块重复前一块最后50 Token难以稳定按句子解析的长纯文本
不重叠0句/0 Token每块本身独立完整,如一问一答FAQ或完整表格行

我们当前默认:结构切块后,只有当一个父块仍被拆成多个子块时,才让相邻子块重叠1—2句;整段FAQ或整条政策无需机械重叠。

Overlap太大有什么坏处

如果每块500 Token,却重叠300 Token:
同一句话会出现在很多向量记录中
→ 检索Top-k可能全是相似重复块
→ 大模型Prompt塞进大量重复话
→ 成本更高,真正不同的信息反而进不来
生活中的例子:Overlap像把上一页最后一句抄到下一页开头,避免翻页时条件和结论断开。

R09Overlap 的作用

Chunk A:申请退款需要订单号、支付凭证和商品照片。
                         └──── 重叠内容 ────┐
Chunk B:支付凭证和商品照片提交后,审核通常需要一天。

重叠可以避免重要句子刚好落在边界被截断。但重叠太多会造成重复召回、浪费存储和Prompt空间。

Overlap不是固定越大越好。应通过真实问题评测:条件有没有丢、重复结果是否过多、上下文是否浪费。

生活中的例子:重叠太大像每箱都重复放半箱相同货物,浪费空间且盘点时重复。
MODULE 03

存储、发布与恢复

按照本模块顺序完成以下知识点,再进入下一模块。

R10-AMetadata 字段来源与用途

Metadata不是让模型看的“备注”,而是程序保存的结构化管理信息。以下继续使用退款政策v3的一个Chunk:

字段具体意思谁/哪里写入什么时候使用
document_id整份政策的稳定身份证,例如refund-policy上传或同步系统生成/绑定把所有Chunk归到同一文件;更新或删除整份政策时使用
chunk_id这一个小片段的唯一身份证,例如refund-policy-v3-p2-c1切块程序按规则生成向量写入、去重、Upsert、日志引用与定位错误
title该片段所属标题,例如“退款到账时间”解析器从标题结构提取帮助检索、给LLM补上下文、前端展示
page原始PDF页码PDF解析器用户点击引用时回到原文;人工排错
version政策第几个版本内容发布系统或业务人员配置新旧政策切换、审计和回滚
statusstaging/active/inactive,分别是待验收、线上有效、已停用入库发布流程写入检索时只允许active,防止半成品或旧版混入
effective_at什么时候开始生效业务人员或政策管理系统按当前时间过滤版本
tenant_id属于哪个公司/租户上传入口或同步来源先过滤,防止A公司查到B公司资料
access_groups哪些角色可以查看,例如customer-service权限配置,不应由LLM猜检索前权限过滤
source_uri原文件在对象存储的位置上传系统回看原文、重新解析、审计
content_hash该片段内容的“指纹”入库程序计算判断内容是否真的变化,避免重复Embedding

为什么不能让LLM生成这些字段?页码、版本、生效日、公司归属和权限是系统事实,猜错会造成越权或旧政策上线。模型最多辅助提取标题或标签,但关键事实必须来自上传系统、解析器和业务配置。

生活中的例子:Metadata像快递标签,写明货主、版本、权限、来源和批次,供程序过滤管理。

R10Metadata 生成流程

{
  "document_id": "refund-policy",
  "chunk_id": "refund-policy-v3-p2-c1",
  "title": "退款到账时间",
  "page": 2,
  "version": 3,
  "status": "staging",
  "effective_at": "2026-09-01",
  "tenant_id": "company-a",
  "access_groups": ["customer-service"],
  "source_uri": "s3://policies/refund-v3.pdf",
  "content_hash": "..."
}

一部分来自上传系统,例如用户和租户;一部分来自文件,例如页码和标题;另一部分来自业务配置,例如生效时间和权限。不能全部让大模型猜。

生活中的例子:关键标签来自上传系统和业务配置,像海关信息不能让包裹自己猜。

R11Collection 与 Vector Index

Collection

一组共享向量配置的记录集合,类似数据库中的表或一个专门书库。通常规定向量维度和距离算法。

Vector Index

帮助快速寻找近邻的数据结构,类似图书馆索引。没有近似索引时可能需要逐条比较全部向量。

创建Collection(通用伪代码):
name = "customer_service_chunks"
vector_dimension = 1536
distance = "cosine"

创建或配置索引:
index_type = "HNSW"

必须匹配:Collection配置的维度必须和Embedding模型输出维度一致;距离算法应遵循模型建议。换Embedding模型时不能直接把不同坐标系混进原Collection。

生活中的例子:Collection像同规格书库,Vector Index像书库快速索引,帮助不用逐本比较。

R12向量数据写入策略

操作含义客服知识库例子
Insert新增一条不存在的记录第一次加入退款政策
Upsert按ID存在则更新,不存在则新增重新生成某个chunk向量
Delete删除指定ID或满足过滤条件的记录删除被撤回的错误文档
Soft Delete不立即物理删除,只标记不可用status=inactive,保留审计

所有写入必须可重复执行。任务重试时同一个chunk_id不应无限产生重复记录,这叫幂等。

生活中的例子:Upsert像按商品编号上架:已有就更新,没有就新增;相同任务重试不会堆出重复商品。

R13安全发布新政策:不要边做边上线

新文件v4
→
staging解析
切块与Embedding
→
数量、错误、抽样
召回评测
→
原子切换
v4 active
→
v3 inactive
保留或清理

如果100个片段只成功生成60个向量,新版本不能对线上可见。发布应在全部检查通过后一次切换,避免用户看到半套新政策、半套旧政策。

发布前检查

  • 解析页数是否合理
  • Chunk数量是否异常
  • Embedding是否全部完成
  • 权限和版本是否齐全
  • 关键问题是否召回正确

切换后检查

  • 线上只返回active版本
  • 缓存是否失效
  • 旧向量是否不可检索
  • 错误率和无答案率是否异常
  • 能否快速回滚
生活中的例子:Staging像新菜单先在试菜间验收,全部合格后一次切换给正式餐厅。

R14增量更新与删除

不应每次改一个字都重建全部知识库。可以使用文件哈希、段落哈希或业务更新时间找出变化部分。

旧版本chunks:A, B, C, D
新版本chunks:A, B修改, C, E新增

需要处理:
B → 重新Embedding并Upsert
D → 停用或Delete
E → 新增并Embedding
A、C → 内容未变,可复用

增量更新更省成本,但关联和一致性更复杂。小数据集可以先安全全量重建;规模增长后再使用增量流水线。

生活中的例子:增量更新像只替换书里改动的几页,而不是每改一个字就重印整座图书馆。

R15失败恢复与可观测性

ingestion_job
├─ job_id
├─ document_id / version
├─ status: queued | parsing | chunking | embedding | validating | published | failed
├─ total_chunks / completed_chunks / failed_chunks
├─ parser_version / embedding_model
├─ error_code / error_message
└─ started_at / finished_at

任务失败后,根据状态从失败步骤继续,不要重复已完成的昂贵操作。上线前还要对账:文档片段数量、向量数量和可检索数量是否一致。

可靠入库 = 可重试 + 幂等 + 可回放 + 发布前校验 + 可回滚

生活中的例子:任务日志像工厂工单,清楚记录在哪道工序失败,恢复时从正确位置继续。

R16本章完整架构

文件上传/API同步
→ 原文件进入对象存储
→ 创建ingestion_job
→ 解析器提取文字、表格、页码
→ 清洗与结构恢复
→ 按标题/条款/语义切块
→ 加document_id、权限、版本、来源metadata
→ 批量调用Embedding
→ Upsert向量记录
→ 数量校验、抽样、召回评测
→ active版本原子发布
→ 旧缓存失效、旧版本停用
→ 监控与失败恢复
生活中的例子:完整架构像一条可追踪生产线,原料、加工、质检、发布和召回都有编号。

本章总结:从哪里来,解决什么,为什么继续

上一章留下的问题怎样生产高质量可检索Chunk
本章形成的能力文档必须经过采集、解析、清洗、切块、标注、向量化、验证和版本发布后才能上线。
下一章必须继续解决有了Chunk后怎样找准、排好并评测

下一章

第05课:检索、混合搜索、Rerank与召回评测

详细讲查询改写、Metadata过滤、稠密/稀疏/关键词召回、结果融合、Rerank、阈值、无答案判断,以及怎样证明检索真的变好了。

← 上一章03 Embedding下一章 →05 检索、混合搜索、Rerank与召回评测
↑ 返回本章目录Ctrl+Z