02. 数据摄取:把制度文档变成可追溯知识
学习目标
本章完成后,你应该能:
- 判断 PDF、扫描件、Word 和网页该走哪条解析路径。
- 设计保留标题、表格、页码和权限的 chunk 记录。
- 处理重复、修订、删除和重新发布,而不留下“幽灵答案”。
- 为每个 chunk 找到原文位置,并能让审计人员复核。
场景统一为“员工制度知识库”。用户问“2026 年出差住宿标准是多少”时,系统必须知道制度名称、版本、生效日、适用地区和原文页码,而不只是返回一段相似文字。
1. 先盘点输入,不要先写切分器
把文件按来源建一张清单:
| 类型 | 例子 | 主要风险 | 首选处理 |
|---|---|---|---|
| 数字 PDF | 《差旅管理办法.pdf》 | 双栏、页眉重复 | 布局解析,保留页码 |
| 扫描 PDF | 盖章制度扫描件 | 无文字层、错字 | OCR + 人工抽检 |
| DOCX | 修订中的制度草稿 | 表格、批注、隐藏文本 | 解析段落与表格,忽略批注 |
| HTML | 内部门户页面 | 导航、脚本、权限 | 抽取正文并记录 URL |
| XLSX | 城市住宿上限表 | 单元格语义被打散 | 保留表头,按行建事实 |
不要把所有输入都转成纯文本再处理。纯文本会丢失“表头属于哪一列”“这一段位于第几页”等信息,后续无法生成可靠引用。
建立原始资产表,最少包含:source_id、原始 URI、内容哈希、抓取时间、操作者、权限标签、解析器版本。哈希用来判断文件是否真的变化,解析器版本用来解释为什么同一文件的 chunk 发生变化。
2. 解析与 OCR 的分流
数字 PDF 先检查是否存在文字层。若每页抽出的字符数接近零,再调用 OCR;不要对全部数字 PDF 无条件 OCR,因为速度慢且可能把清晰文本变成识别错误。
OCR 输出必须保留置信度和页坐标。例如:
{
"text": "住宿费:一线城市 500 元/晚",
"page": 7,
"bbox": [72, 310, 486, 344],
"ocr_confidence": 0.96
}低置信度的金额、日期、否定词(“不得”“不超过”)进入人工复核队列。制度场景里,500 识别成 5000 会比漏掉一段普通说明更危险。
多栏页面要先做阅读顺序检测。可用“区域从上到下、同一行从左到右”的规则作为基线,再用真实样本检查:标题、正文、脚注是否串行。表格应作为表格元素输出,至少保留列名、行号和跨页关系。
3. 结构化中间格式
建议在 chunk 前保存一个中间 JSON,而不是直接写向量库:
{
"doc_id": "travel-policy-2026-v3",
"title": "员工差旅管理办法",
"version": "3.0",
"effective_from": "2026-01-01",
"elements": [
{"kind":"heading","level":2,"text":"第三章 住宿标准","page":6},
{"kind":"paragraph","text":"员工出差应选择协议酒店。","page":6},
{"kind":"table","headers":["城市级别","上限"],"rows":[["一线城市","500 元/晚"]],"page_start":7,"page_end":8}
]
}kind 至少区分标题、段落、列表、表格、图片说明和页眉页脚。页眉页脚可删除,但删除规则要记录;“制度名称 + 版本号”如果位于页眉,不能误删。
表格转文本时,把表头复制到每一行:住宿标准|城市级别=一线城市|上限=500 元/晚。这样向量检索命中“500 元”时,模型仍能看到它对应的字段。
4. 清洗的边界
清洗只修复确定的版式噪声:连字符断行、重复页眉、不可见字符、明显重复页面。不要擅自改写制度原文,不要把“可报销”改成“应报销”。
去重分两层:
- 文件级:相同内容哈希直接复用解析结果。
- 段落级:同一文档因页眉或复制粘贴产生的重复段落只保留一次,同时记录出现页码。
为每次清洗输出差异统计:输入字符数、删除字符数、表格数、OCR 页数、低置信度字段数。异常突变(例如字符数减少 80%)应阻止自动发布。
5. 结构感知切分
固定 500 字切分可以作为基线,但制度文档应优先按标题和条款切分。每个 chunk 继承完整标题路径:
员工差旅管理办法 > 第三章 住宿标准 > 第十二条 城市分级建议一个 chunk 包含一个完整条款或一个小表格;过长条款再按句子切分,并在相邻 chunk 之间保留少量重叠。表格不要从中间切开;超过上下文限制时按连续行切分,每块都带表头和“第 1/3 块”标记。
chunk 记录示例:
{
"chunk_id":"travel-policy-2026-v3:p7:table-01:part-1",
"text":"...",
"heading_path":["第三章 住宿标准","第十二条 城市分级"],
"page_start":7,"page_end":7,
"source_uri":"s3://policy/travel-v3.pdf",
"doc_version":"3.0","effective_from":"2026-01-01",
"acl":["all-employees"],"content_hash":"sha256:..."
}chunk ID 要稳定。用文档 ID、元素位置和内容哈希组成;不要用数据库自增 ID,否则重建索引后引用会全部变化。
6. 元数据与权限
把检索会用到的字段放在元数据里,而不是藏在正文:制度类型、部门、地区、语言、版本、生效日、失效日、密级、适用人群。权限过滤必须在召回前执行;召回后再过滤可能泄露“用户曾经不应看到的标题”。
时间过滤要明确语义:查询“当前标准”使用 effective_from <= now < effective_to;查询“2024 年报销”则按业务日期匹配版本。没有结束日期的版本用空值表示,不要填一个假日期。
7. 增删更新与回滚
采用不可变版本 + 当前指针:新文件先解析、校验、建索引,全部成功后再把文档指针切到新版本。旧版本保留一段审计期,删除请求则标记 deleted_at 并从在线索引移除。
更新流程:
- 收到文件并计算哈希。
- 哈希相同则跳过;不同则生成新
doc_version。 - 解析、OCR 抽检、chunk 校验。
- 写入新索引,执行权限和数量检查。
- 原子切换
current_version。 - 记录操作者、时间、解析器版本和失败原因。
若第 4 步失败,继续服务旧版本,并报警;不要先删旧索引再尝试写新索引。删除文档时要按 doc_id 删除所有 chunk、缓存答案和搜索倒排项。
8. 一个最小验收清单
拿三份制度做验收:数字 PDF、扫描 PDF、带跨页表格的 DOCX。
- [ ] 每份文档都能回到原文件和页码。
- [ ] 表头没有与数据行分离。
- [ ] OCR 金额和日期抽样准确率达到团队设定阈值。
- [ ] 同一问题不会同时召回已失效和当前版本,除非用户明确查询历史。
- [ ] 删除文档后,旧 chunk 不再被检索。
- [ ] 无权限账号无法通过关键词或向量命中受限文档。
- [ ] 解析失败时旧版本仍可回答。
练习与验收
练习:给《差旅管理办法》增加一个 2026-07-01 生效的修订版,只修改一线城市住宿上限。设计两个 chunk ID,写出当前查询和历史查询的过滤条件,并解释为什么旧版本仍需保留。
验收:同学提交中间 JSON、两个版本的元数据和一次删除演练日志。随机抽取一个金额,五分钟内能打开原文页码并指出 OCR 或解析证据。
评论
正在加载评论…