日期:2026-07-18
本文为工程实践复盘,客户信息已脱敏;所述数据均为示意值。
这个项目里,我们对接了一家某省级国资集团的内网知识库改造。他们沉淀了十几年的制度文件、招标文档、审计报告,文档总量约 80 万页,格式非常杂:有原生 PDF,有扫描件(占比约 35%),有 Word 转出来的双栏排版(占比约 28%),还有大量带合并单元格的财务报表和含数学公式的技术白皮书(含表格文档约 46%)。最初的方案是直接把文件喂给某开源社区的切片工具,再用我们采用的向量检索与 RAG 技术平台做入库。结果上线后一测,检索召回率惨不忍睹——用户问“2023 年差旅报销标准”,系统返回的是某页脚注释。我们当时复盘,根因不在检索模型,而在最前端的解析环节:表格被当成纯文本拆散、双栏被串行读成一团、公式识别成乱码。说白了,解析这一步把结构丢了,后面的 RAG 再怎么调参都救不回来。更麻烦的是,这部分脏数据一旦入库,会持续污染向量空间,返工成本极高。
我们在这个项目里搭了一条独立的文档解析流水线,位置在切片入库之前,作为 RAG 知识库层的前置环节。整条链路分四段:先做版式分析(判断单栏/多栏、页眉页脚、图文位置关系),再做表格与公式抽取,然后是扫描件的 OCR 与纠错,最后输出统一的结构化 JSON,交给下游切片模块按“语义块”切分。这条流水线跑在我们采用的私有化底座上,和向量库、embedding 服务同机柜部署,避免大文件在网络上反复搬运。解析服务本身做成无状态,从对象存储拉文件、写回结构化结果,靠队列削峰,可水平扩容到约 16 个 worker。输出的 JSON schema 约定了 block 类型(text / table / formula / image / caption),下游切片严格按 block 边界切,绝不跨 block 切断一个表格或一段公式。
复杂版式与多栏布局分析。 双栏甚至三栏的 PDF 最容易串行错读。我们的做法是先按页面做连通域分析(connected component analysis),用投影法得到列分隔的空白带:当某纵向空白带的宽度超过页面宽度的约 8% 时,判定为列间隙。再对每段文本块做空间排序(reading order),排序规则优先 y 轴、同 y 带内按 x 轴,遇到跨栏标题单独标记。对版式特别乱的文件,回退到基于版面检测模型的方案,输出带 bbox 的 block 列表,再人工规则兜底。
# layout_config.yaml(脱敏示意)
layout:
method: projection_plus_model
columns: auto # 自动探测 1/2/3 栏
column_gap_ratio: 0.08 # 空白带宽度阈值
detect_regions:
- header
- footer
- sidebar
- figure
reading_order: [y, x] # 先 y 后 x 排序
fallback: rule_based表格与公式结构还原。 表格最怕合并单元格和跨页表。我们当时用了一套“线检测 + 单元格聚类”的组合:先检测横竖分割线,缺失处用文本对齐关系补线,再聚类出单元格矩阵,对跨页表用表头指纹(首列语义 hash)做续表拼接。公式则走 LaTeX OCR 模型,输出 MathML 供检索时保留语义。实测一个 18 列的结算表,仅靠补线就能把跨列合并单元还原出来。
案例片段(已脱敏):一张 18 列的供应商结算表,原方案把表头与数据行混切,检索“质保金比例”命中率几乎为零。启用线检测后日志如下:
[layout] table detected: rows=42 cols=18 bbox=(88,120)-(1502,980) [table] merged_cell fixed: (3,0)-(3,2) -> span=3 [table] cross_page append match header_fingerprint=9f3a2c [eval] table recall=0.97 before=0.41
扫描件 OCR 与纠错。 扫描件最大的坑是低清、倾斜和印章遮挡。我们采用我们采用的 OCR 引擎配合版面级后处理:先做二值化与去噪,倾斜校正用 Hough 变换(校正角度范围约 ±15°),印章区域用颜色阈值(R>150 且 G<100)mask 掉再做识别,避免红章文字串入正文。纠错层接了一个领域词典(制度/财务/人名),对 OCR 输出的低置信度 token 做编辑距离替换,并用 n-gram 语言模型打分,把“差旅报消”自动纠成“差旅报销”。
图文混排顺序还原。 图文混排里,配图说明(caption)和正文容易被拆到不同块。我们给每个图块计算“最近文本归属”,按空间距离把 caption 挂到对应图,并保留图在文档流中的顺序号,下游切片时图与其上下文同属一个语义块,避免图被孤立成噪声。
改造前后我们在约 2.3 万页脱敏样本上做了离线评测(其中另取约 500 页人工标注为黄金集做精度核对),核心指标如下(均为示意值):
| 指标 | 改造前 | 改造后 | 说明 |
|---|---|---|---|
| 解析完整率 | 约 68% | 约 95% | 含表格/公式/图文结构 |
| 表格还原准确率 | 约 41% | 约 94% | 合并单元格+跨页拼接 |
| OCR 字错率(CER) | 约 6.8% | 约 1.9% | 领域纠错后 |
| 单页入库耗时 | 约 1.4s | 约 0.9s | 含解析+切片+向量化 |
从业务侧看,同一批问答测试集上,端到端 RAG 的检索命中率从约 62% 提升到约 91%,用户可感知的“答非所问”工单下降明显。整套流水线在我们采用的底座上稳定跑了三个月,峰值日解析约 4 万页,GPU 利用率维持在约 70%,未出现解析队列积压。值得一提的是,解析耗时反而下降,原因是结构干净后下游切片与向量化减少了大量重试与重试拼接。
第一,解析质量决定 RAG 上限。这个项目里我们最大的教训是:一开始把精力全花在检索重排上,效果上不去,回头治解析才解决根本问题。先治版式再切片,顺序不能反。
第二,版式分析要“规则 + 模型”双保险。纯规则在规整文件上快且稳,纯模型在乱版式上更准,分级回退最划算,既控成本又保下限。
第三,评测要落到结构级指标。只盯“能不能读出来”会掩盖表格/公式丢失,必须单独量化表格还原准确率和 OCR 字错率,最好留一份人工黄金集做回归。
第四,图文混排的顺序还原别忽视,图被孤立会污染向量空间,直接拉低检索质量;caption 归属和顺序号要随 block 一起进 schema。
第五,解析服务做成无状态 + 队列削峰,和向量库同机柜,避免大文件在网络上反复搬运,这是规模化时不被 I/O 拖垮的关键。