RAG 与文档类型路由:不同文档类型走不同解析管线的工程设计

为什么"一个解析器打天下"必然失败

企业知识库接入的文档形态五花八门:扫描合同、原生 PDF 报告、Excel 台账、PPT 汇报、wiki 页面、邮件线程、代码仓库。工程上常见的做法是挑一个"最强"的解析器(通常是某个 PDF 库或某个多模态解析服务)统一处理,然后发现不同类型的文档解析质量天差地别。问题不在解析器不够强,而在文档类型之间的差异是结构性的:适合扫描件的策略(OCR)对原生文本 PDF 是纯粹的损耗,适合原生 PDF 的文本层抽取对扫描件得到的是空文件。

同为 PDF,可能是三种动物

PDF 是最好的反面教材:扩展名相同,内里完全不同。原生文本 PDF有可提取的文本层,走解析库抽文本加布局规则就够了;纯扫描 PDF是图片序列,必须走 OCR 管线,且版面还原质量决定下游分块质量;混合 PDF最麻烦——部分页有文本层、部分页是扫描插图(典型如带手写签名的合同、附录里嵌扫描报表的标书),统一走文本抽取会静默丢掉扫描页的内容。错误路由的代价不是"解析失败"这种显式错误,而是静默的半份知识:文档进了库、检索也能命中、但一半内容从没存在过——这种失败要到业务用户反馈"系统不知道这个条款"时才会暴露,而那时根因定位已经隔了千山万水。

静默失败比显式失败可怕:质量门是最后一道闸

路由系统最重要的设计原则由此而来:每一次解析都要有质量评估,低于阈值的产物不允许静默入库。质量门的指标按文档类型定制:通用指标是字符产出率(解析出的字符数与文件大小的比值,扫描件走文本抽取时这个值趋近零)、乱码率(非预期字符集字符的占比,典型的 CID 字体乱码、私有编码问题都能被它抓住);结构指标是标题层级完整性(解析出的标题数与目录大纲的吻合度)、表格单元格完整率。低于阈值不是丢弃,而是降级到备选管线重试——这套机制让失败显性化,而显性化的失败才可运营。

路由系统的设计

四层路由信号:从弱到强

路由决策依据的信号按可靠性分四层,逐层升级判定。第一层是扩展名:成本为零但最不可信——.doc 实际是 HTML、.pdf 实际损坏的情况在企业内容里屡见不鲜;扩展名只做初分类的先验。第二层是 MIME 类型与 magic bytes:读文件头的真实字节签名(PDF 的 %PDF-、ZIP 容器的 Office 系列文件),这一层基本能确定容器格式,但仍分不出"PDF 里面的三种动物"。第三层是内容探测:对文档做轻量预扫描,计算文本密度、字符集分布、图片页占比——文本层字符产出率低于阈值判为扫描件,图文混判为混合型。第四层是历史路由记忆:同一来源(同一系统导出的批量文件)的路由历史作为先验,来源稳定的批量文件免重复探测。路由器不需要在第一遍就判对一切——它需要的是快速判定 + 错了能被质量门抓住并纠正,这是四层信号的设计哲学。

管线即插件:统一接口与中间表示

管线架构用插件模式:每条解析管线实现统一接口,声明自己支持的能力;路由器按类型选择管线,下游分块器只面对中间表示(IR)而不面对原始文档:

class ParseResult(TypedDict):
    blocks: list[dict]      # 结构树:[{type: heading/para/table/image, level, text, ...}]
    tables: list[dict]      # 表格:{headers, rows, caption, position}
    images: list[dict]      # 图片:{path, ocr_text, position}
    meta: dict              # 标题、作者、页码映射、语言、原文档 hash
    quality: dict           # 质量门指标:字符产出率、乱码率、结构完整性
    warnings: list[str]     # 非致命问题(跨页拼接失败、字体缺失)

class ParserPipeline(Protocol):
    name: str
    supports: set[str]      # 声明支持的类型标签
    def parse(self, doc: BinaryIO) -> ParseResult: ...

class Router:
    def route(self, raw: BinaryIO, filename: str) -> ParserPipeline:
        sig = sniff_magic(raw)                  # 第二层:magic bytes
        probe = quick_probe(raw)                # 第三层:内容探测
        tag = classify(sig, probe, ext_of(filename))
        return self.registry.pick(tag)          # 返回声明支持该类型的管线

IR 是整个设计的解耦关键:下游的清洗、分块、抽取全部只消费 IR,解析引擎的更换(从开源库换到商业服务、从规则版面分析换到模型版面分析)不影响下游任何代码。反过来,新增一种文档类型只需要写一条新管线进注册表,路由器与下游零改动。这个接口契约还让质量门有了统一的检查点:任何管线的产物都过同一套指标检查,threshold 按类型配置。路由、解析与降级的整体拓扑如下:

flowchart TB
    RAW[原始文档] --> L1[第一层:扩展名先验]
    L1 --> L2[第二层:MIME + magic bytes]
    L2 --> L3[第三层:内容探测<br/>文本密度 / 字符集 / 图片占比]
    L3 --> L4[第四层:历史路由记忆]
    L4 --> RT{路由器选管线}
    RT --> P1[版式管线<br/>布局感知]
    RT --> P2[表格管线<br/>语义恢复]
    RT --> P3[网页邮件管线<br/>正文抽取]
    RT --> P4[OCR 管线]
    P1 & P2 & P3 & P4 --> IR[统一中间表示 IR]
    IR --> QG{质量门}
    QG -->|达标| IDX[入库索引]
    QG -->|不达标| FB[降级链:备选管线 → OCR → 人工队列]
    FB --> IR

为什么路由在入库时做,而不是检索时做

一个常见的偷懒设计是"检索时再解析"——查询来了现读文档现解析。这条路的问题不在延迟(现解析确实慢),而在质量评估与优化的可能性:入库时解析意味着解析结果可以被评测、被缓存、被人工修正、被重新路由降级,检索时解析意味着每一次查询都承担解析的不确定性,且失败的文档永远没有修复机会。入库时解析把解析变成一次性的、可运营的资产生产,检索时只消费资产——这是解析层和检索层的正确分工,也是内容侧优化(解析质量回归、golden set 评测)能开展的前提。

各类型管线的要点

版式文档:布局感知与标题层级重建

PDF 与 PPT 的核心挑战是视觉结构到逻辑结构的还原。规则式方案(字号聚类判标题、缩进判层级)在规整文档上够用,但在设计感强的文档上失效;模型式方案(LayoutLMv3、DiT 这类文档版面分析模型,Huang 等人 2022)在复杂版面上显著更稳,代价是 GPU 与延迟。实践推荐混合策略:先规则后模型,规则置信度高的区块不进模型——多数企业文档是规整的,模型算力花在少数疑难文档上。两个容易被忽略的细节:跨页段落拼接(段落被页边界切成两半,简单按页分块会把上下文切断,需要根据行末标点与缩进判断拼接)与页眉页脚剥离(重复出现的页面装饰文本如果进索引,会污染每一块的 embedding——检索时大量不相关的页命中全是同一个页眉)。

表格密集型文档:语义恢复优先

Excel 台账与财务报表的难点不是"提取出表格"而是表格语义的恢复:合并单元格展开后每个数据格需要继承完整的表头路径("2024 年 Q3 华东区营收"而不是孤立的一个数字),横向表与纵向表的方向判定(指标做行还是做列),多层表头的展开规则。下游策略上,逐行句子化(每行数据渲染成"2024 年 Q3,华东区营收为 X 万元"的自然语言句)通常比整表转 Markdown 更适合检索——行级句子让向量检索能精确命中"华东区营收"这类行级查询,整表转 Markdown 则把所有行焊死在一个大块里,embedding 被稀释。表格语义恢复的完整展开在本系列表格理解那篇,这里强调的是路由层的意义:表格密集型文档必须整份走表格专线,而不是当作普通 PDF 混过通用解析。

网页与邮件:噪音剥离与结构保留

HTML 类内容的核心是正文抽取:导航栏、广告、版权声明、相关推荐这些噪音必须剥离(Readability 类算法按文本密度与链接密度判正文区块),否则噪音文本会成块进库。邮件线程的特殊结构要保留:发件人、时间戳、引用层级(回复链的缩进结构本身就是语义——"这句话是谁在什么语境下说的"依赖它),把邮件线程拍平成纯文本会毁掉对话语境。这两类管线产出的 IR 与版式文档同构(blocks 树),但各自的清洗规则完全不同——这正是插件化的价值所在。

工程实践:回归评测与降级链

解析管线自己的 golden set

解析管线是代码,代码改动就要有回归测试。为每种文档类型维护一个小规模 golden set(每种类型 10 到 30 份代表性文档 + 期望 IR 的结构断言:标题数量、关键表格的行列数、正文段落抽样比对),任何解析库升级、规则修改都要跑一遍 golden set,断言失败即回归。这个投入很小(golden set 一次搭建、增量维护),但它拦住的损失巨大——解析层的静默退化是 RAG 系统里最难排查的一类故障,因为症状出现在检索质量上,根因却在入库管线,两者隔着整条链路。

降级链与人工兜底

每类文档配置一条降级链:首选管线失败或质量门不达标 → 备选管线 → OCR 通道 → 人工处理队列。降级链的每一步都要记录路由决策与质量指标(这些日志本身就是 golden set 的素材来源:被降级的文档往往暴露了路由信号的盲区)。人工队列的定位要克制:只收质量门连续降级仍不达标、且业务价值明确的文档(高频访问的、来源权威的),人工修复的产物回写为该类型的 golden 样本——让人工投入沉淀为管线改进的素材,而不是一次性的救火。

小结

文档类型路由的设计要点可以浓缩成四条。第一,类型差异是结构性的,统一解析器必然顾此失彼:同为 PDF 可能是原生文本、扫描件、混合型三种动物,错误路由的典型代价是静默的半份知识。第二,路由信号分四层逐级升级:扩展名只做先验、magic bytes 定容器、内容探测定内型、历史记忆降成本;路由器不求一次判对,求的是"错了能被抓住"。第三,质量门是防静默失败的总闸:字符产出率、乱码率、结构完整性指标按类型配置阈值,不达标的产物降级重试而非入库——显性化的失败才可运营。第四,管线即插件、IR 即契约:下游只消费统一中间表示,解析引擎可换、文档类型可增,互不牵连;路由在入库时做,解析成为可评测、可缓存、可人工修正的一次性资产生产。最后,给解析管线自己配上 golden set 与降级日志——解析层的静默退化是 RAG 故障里最难排查的类型,回归评测是唯一的系统性防线。这套架构的本质是把"解析"从一个黑盒步骤升级为一条有质量度量的生产线,检索质量的上限因此从入库那一刻就被决定了。

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?