为什么有了向量数据库还要做数据工程
路线12章中的位置
本章目录
阅读过程中可按 Ctrl + Z 快速返回当前页面的本章目录;也可以点击右下角按钮。
本章思维导图
知识流向:先完成左侧基础,再进入中间机制,最后用右侧工程控制把它变成可上线能力。节点可直接点击跳转。
文档采集与解析
按照本模块顺序完成以下知识点,再进入下一模块。
R01PDF 直接检索的局限
人看到的PDF
标题、段落、表格、页眉页脚、图片和页码组合成完整政策。
程序可能读到的内容
文字顺序错乱、表格拆散、扫描页没有文字、重复页眉、乱码和空白。
仓库类比:上传PDF只是货车把整箱货送到仓库门口;解析、清洗、贴标签、分货架、登记库存后,才真正可用。
R02完整离线入库流水线
这通常是后台异步任务,不是在用户每次提问时重新处理整份PDF。用户查询只使用已经成功发布的知识版本。
R02-A异步入库机制
同步是上传请求一直等到所有步骤完成;大 PDF 可能处理几分钟,网页会长时间转圈。异步是上传接口先登记任务并立刻返回任务编号,由后台工作进程继续处理。
保存文件
生成 job_id
逐步处理
记录进度与错误
验收后上线
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}Worker
位置:后台独立进程/服务真正执行解析、清洗、切块和 Embedding。失败时记录步骤;重试使用相同 ID,避免重复数据。
失败边界:只有全部片段成功、数量检查通过、权限版本齐全、关键问题召回正常,才能从 staging 切到 active。失败半成品永远不能被线上检索。
R03采集:知识从哪里进入
| 来源 | 采集方式 | 必须记录 |
|---|---|---|
| PDF/Word/Excel | 用户上传、共享目录、对象存储 | 文件名、哈希、上传者、时间、权限 |
| 网页 | 受控爬取或官方API | URL、抓取时间、版本、访问范围 |
| 业务数据库 | 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文档清洗原则
- 每页重复的页眉页脚
- 异常空格和乱码
- 错误换行和断词
- 导航菜单、广告等无关内容
- 明确重复的正文
- “不”“禁止”等否定词
- 表格中的单位与列名
- 条款编号和生效日期
- 例外条件
- 来源与页码
清洗后的文本必须能够追溯到原文件位置;否则模型引用出错时无法回看原文。
切块策略
按照本模块顺序完成以下知识点,再进入下一模块。
R06Chunk 切块的定义
Chunk是向量检索的基本单位。整份政策太大,不能每次全部放进Prompt,因此把它拆成一段段可独立理解的内容。
文档不是按“文件”检索 → 而是按“能回答一个局部问题的片段”检索
R07太大和太小都会出问题
判断标准:不是追求固定字符数,而是让片段围绕一个相对完整的主题,并尽量保留回答问题所需的条件、结论和来源。
R08-A默认切块策略
对于我们要做的AI客服政策、产品手册、FAQ、制度文档,默认采用这一套组合:
① 先按文件结构切:标题 / 章节 / 条款 / FAQ / 一整张表格 ② 某一段仍太长时,再递归地按段落、句子切小 ③ 生成“小子块”用于Embedding和检索 ④ 命中子块后,返回它所属的“父块”给大模型阅读 ⑤ 相邻子块复制1—2句边界文字作为Overlap
结构优先 + 递归兜底 + 父子切块 + 小Overlap
| 方法 | 本项目是否作为默认 | 原因 |
|---|---|---|
| 按结构切块 | 是,主方法 | 政策与手册天然有标题、条款、FAQ;边界最符合人类阅读逻辑。 |
| 递归切块 | 是,兜底 | 某条款太长时,按段落再按句子逐级切,不会粗暴从句子中间截断。 |
| 父子切块 | 是 | 小块更容易精准命中;父块让模型看到完整条件、例外和结论。 |
| Overlap | 是,少量 | 防止刚好在条件与结论之间切断。 |
| 固定长度 | 只作最后兜底 | 最快,但最容易把句子、条款或表格切坏。 |
| 语义切块 | 暂不作为默认 | 要额外模型或规则判断主题边界,成本更高,结果也必须评测;等我们有评测集后再比较。 |
这不是说语义切块不好,而是对“退款政策”这种结构非常清晰的资料,优先利用已存在的标题和条款,比让模型猜主题边界更稳定、更便宜、也更好排错。
R08-B用同一份退款政策,逐步切成真实Chunk
假设解析后的原文如下。为了看清过程,下面故意写得短;真实文档可能更长。
一、适用范围
商品签收后七日内,且商品保持完好,可以申请退货退款。定制商品和已拆封的贴身用品不适用无理由退货。
二、申请材料
申请退款时,用户需要提交订单号、支付凭证和商品照片。资料提交完整后,客服将在一个工作日内审核。
三、退款到账
审核通过后,款项将在三个工作日内原路退回。银行卡到账时间可能受银行处理影响。
第1步:按结构找大边界
解析器先识别标题“适用范围 / 申请材料 / 退款到账”。每个标题下的内容暂时成为一个父块:
父块 P1:一、适用范围 + 两句正文 父块 P2:二、申请材料 + 两句正文 父块 P3:三、退款到账 + 两句正文
如果一个父块已经不长,例如P3,就不用为了凑数字而继续硬切。
第2步:对过长父块递归切小
假设P2在真实文档中有12段,太长。递归切块的真正做法是按优先级尝试边界:
先尝试按“标题”切 如果还太长 → 按“段落”切 如果单段仍太长 → 按“句号/问号/分号”切 如果单句仍特别长 → 最后才按Token数硬切
例如P2变成两个子块:
子块 C2-1: 标题:二、申请材料 申请退款时,用户需要提交订单号、支付凭证和商品照片。 子块 C2-2: 标题:二、申请材料 资料提交完整后,客服将在一个工作日内审核。
注意:子块里重复保留标题,不是浪费。用户单独问“审核多久”,即使只召回C2-2,模型仍知道它属于“申请材料”这部分,而不是把“一天审核”误解成退款到账时间。
R08-C父子切块的结构与流程
父子切块是同一份原文存两种粒度:父块大一点,子块小一点。两者用 parent_id 关联。
申请材料完整段落
C2-1、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 → 要求标出主题变化点 → 在标记处切块
更灵活,但要额外调用模型,成本、速度和稳定性都需要评测。
它适合没有标题、没有段落、主题自然变化的长访谈、会议记录或连续文章。它不适合直接替代政策文档的结构切块,因为政策本来已经明确写了“第一条、第二条、例外情况”。
风险例子:模型可能把“定制商品不适用退货”误判为新主题而单独切出,结果回答时丢掉它属于退款政策的前提。因此语义切块必须与人工抽样和召回评测一起使用,而不是开关一开就认为更高级。
R08常见切块方法
| 方法 | 做法 | 适用 | 风险 |
|---|---|---|---|
| 固定长度 | 每N个字符或Token切一次 | 快速原型、普通纯文本 | 可能从句子和表格中间切断 |
| 递归切块 | 优先标题、段落、句子,太长再继续切 | 通用文档 | 仍需要针对文档结构调整 |
| 按结构切块 | 章节、条款、FAQ、表格作为边界 | 政策、手册、合同 | 解析器必须识别结构 |
| 语义切块 | 检测主题变化位置 | 长文章和主题自然切换内容 | 成本更高,结果需评测 |
| 父子切块 | 小块用于检索,命中后返回更大的父段落 | 既需精准召回又需完整上下文 | 存储与关联更复杂 |
R09-AOverlap从零解释:相邻Chunk故意复制的一小段
Overlap 的中文就是“重叠”。它不是新模型、不是数据库字段、不是检索算法。它只是切块时,把前一个Chunk末尾的一小段文字复制到下一个Chunk开头。
仍然以“申请材料”段落为例:
Chunk A:申请退款时,用户需要提交订单号、支付凭证。 Chunk B:和商品照片。资料提交完整后,客服将在一个工作日内审核。
“支付凭证和商品照片”被切开。单独召回A或B都会不完整。
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塞进大量重复话 → 成本更高,真正不同的信息反而进不来
R09Overlap 的作用
Chunk A:申请退款需要订单号、支付凭证和商品照片。
└──── 重叠内容 ────┐
Chunk B:支付凭证和商品照片提交后,审核通常需要一天。重叠可以避免重要句子刚好落在边界被截断。但重叠太多会造成重复召回、浪费存储和Prompt空间。
Overlap不是固定越大越好。应通过真实问题评测:条件有没有丢、重复结果是否过多、上下文是否浪费。
存储、发布与恢复
按照本模块顺序完成以下知识点,再进入下一模块。
R10-AMetadata 字段来源与用途
Metadata不是让模型看的“备注”,而是程序保存的结构化管理信息。以下继续使用退款政策v3的一个Chunk:
| 字段 | 具体意思 | 谁/哪里写入 | 什么时候使用 |
|---|---|---|---|
document_id | 整份政策的稳定身份证,例如refund-policy | 上传或同步系统生成/绑定 | 把所有Chunk归到同一文件;更新或删除整份政策时使用 |
chunk_id | 这一个小片段的唯一身份证,例如refund-policy-v3-p2-c1 | 切块程序按规则生成 | 向量写入、去重、Upsert、日志引用与定位错误 |
title | 该片段所属标题,例如“退款到账时间” | 解析器从标题结构提取 | 帮助检索、给LLM补上下文、前端展示 |
page | 原始PDF页码 | PDF解析器 | 用户点击引用时回到原文;人工排错 |
version | 政策第几个版本 | 内容发布系统或业务人员配置 | 新旧政策切换、审计和回滚 |
status | staging/active/inactive,分别是待验收、线上有效、已停用 | 入库发布流程写入 | 检索时只允许active,防止半成品或旧版混入 |
effective_at | 什么时候开始生效 | 业务人员或政策管理系统 | 按当前时间过滤版本 |
tenant_id | 属于哪个公司/租户 | 上传入口或同步来源 | 先过滤,防止A公司查到B公司资料 |
access_groups | 哪些角色可以查看,例如customer-service | 权限配置,不应由LLM猜 | 检索前权限过滤 |
source_uri | 原文件在对象存储的位置 | 上传系统 | 回看原文、重新解析、审计 |
content_hash | 该片段内容的“指纹” | 入库程序计算 | 判断内容是否真的变化,避免重复Embedding |
为什么不能让LLM生成这些字段?页码、版本、生效日、公司归属和权限是系统事实,猜错会造成越权或旧政策上线。模型最多辅助提取标题或标签,但关键事实必须来自上传系统、解析器和业务配置。
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。
R12向量数据写入策略
| 操作 | 含义 | 客服知识库例子 |
|---|---|---|
| Insert | 新增一条不存在的记录 | 第一次加入退款政策 |
| Upsert | 按ID存在则更新,不存在则新增 | 重新生成某个chunk向量 |
| Delete | 删除指定ID或满足过滤条件的记录 | 删除被撤回的错误文档 |
| Soft Delete | 不立即物理删除,只标记不可用 | status=inactive,保留审计 |
所有写入必须可重复执行。任务重试时同一个chunk_id不应无限产生重复记录,这叫幂等。
R13安全发布新政策:不要边做边上线
切块与Embedding
召回评测
v4 active
保留或清理
如果100个片段只成功生成60个向量,新版本不能对线上可见。发布应在全部检查通过后一次切换,避免用户看到半套新政策、半套旧政策。
发布前检查
- 解析页数是否合理
- Chunk数量是否异常
- Embedding是否全部完成
- 权限和版本是否齐全
- 关键问题是否召回正确
切换后检查
- 线上只返回active版本
- 缓存是否失效
- 旧向量是否不可检索
- 错误率和无答案率是否异常
- 能否快速回滚
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版本原子发布 → 旧缓存失效、旧版本停用 → 监控与失败恢复
本章总结:从哪里来,解决什么,为什么继续
下一章
第05课:检索、混合搜索、Rerank与召回评测
详细讲查询改写、Metadata过滤、稠密/稀疏/关键词召回、结果融合、Rerank、阈值、无答案判断,以及怎样证明检索真的变好了。