Skip to content

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 输出必须保留置信度和页坐标。例如:

json
{
  "text": "住宿费:一线城市 500 元/晚",
  "page": 7,
  "bbox": [72, 310, 486, 344],
  "ocr_confidence": 0.96
}

低置信度的金额、日期、否定词(“不得”“不超过”)进入人工复核队列。制度场景里,500 识别成 5000 会比漏掉一段普通说明更危险。

多栏页面要先做阅读顺序检测。可用“区域从上到下、同一行从左到右”的规则作为基线,再用真实样本检查:标题、正文、脚注是否串行。表格应作为表格元素输出,至少保留列名、行号和跨页关系。

3. 结构化中间格式 ​

建议在 chunk 前保存一个中间 JSON,而不是直接写向量库:

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. 清洗的边界 ​

清洗只修复确定的版式噪声:连字符断行、重复页眉、不可见字符、明显重复页面。不要擅自改写制度原文,不要把“可报销”改成“应报销”。

去重分两层:

  1. 文件级:相同内容哈希直接复用解析结果。
  2. 段落级:同一文档因页眉或复制粘贴产生的重复段落只保留一次,同时记录出现页码。

为每次清洗输出差异统计:输入字符数、删除字符数、表格数、OCR 页数、低置信度字段数。异常突变(例如字符数减少 80%)应阻止自动发布。

5. 结构感知切分 ​

固定 500 字切分可以作为基线,但制度文档应优先按标题和条款切分。每个 chunk 继承完整标题路径:

text
员工差旅管理办法 > 第三章 住宿标准 > 第十二条 城市分级

建议一个 chunk 包含一个完整条款或一个小表格;过长条款再按句子切分,并在相邻 chunk 之间保留少量重叠。表格不要从中间切开;超过上下文限制时按连续行切分,每块都带表头和“第 1/3 块”标记。

chunk 记录示例:

json
{
  "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 并从在线索引移除。

更新流程:

  1. 收到文件并计算哈希。
  2. 哈希相同则跳过;不同则生成新 doc_version。
  3. 解析、OCR 抽检、chunk 校验。
  4. 写入新索引,执行权限和数量检查。
  5. 原子切换 current_version。
  6. 记录操作者、时间、解析器版本和失败原因。

若第 4 步失败,继续服务旧版本,并报警;不要先删旧索引再尝试写新索引。删除文档时要按 doc_id 删除所有 chunk、缓存答案和搜索倒排项。

8. 一个最小验收清单 ​

拿三份制度做验收:数字 PDF、扫描 PDF、带跨页表格的 DOCX。

  • [ ] 每份文档都能回到原文件和页码。
  • [ ] 表头没有与数据行分离。
  • [ ] OCR 金额和日期抽样准确率达到团队设定阈值。
  • [ ] 同一问题不会同时召回已失效和当前版本,除非用户明确查询历史。
  • [ ] 删除文档后,旧 chunk 不再被检索。
  • [ ] 无权限账号无法通过关键词或向量命中受限文档。
  • [ ] 解析失败时旧版本仍可回答。

练习与验收 ​

练习:给《差旅管理办法》增加一个 2026-07-01 生效的修订版,只修改一线城市住宿上限。设计两个 chunk ID,写出当前查询和历史查询的过滤条件,并解释为什么旧版本仍需保留。

验收:同学提交中间 JSON、两个版本的元数据和一次删除演练日志。随机抽取一个金额,五分钟内能打开原文页码并指出 OCR 或解析证据。

参考资料 ​

评论

正在加载评论…