为什么表格是 RAG 里最容易翻车的部分
三类典型失败
财务报表、实验数据、配置清单、价格表——凡是业务知识库,表格含量都不会低。而表格恰恰是传统文档处理链路上最脆弱的内容类型。生产里反复出现的三类失败:
第一类:表格被解析器撕碎。PDF 解析器按文本流顺序输出内容,表格在文本流里是「逐行串行」的——单元格之间的二维对应关系全部丢失。「第三列的 85% 属于哪一行哪个指标」这种二维定位,解析器根本不知道。切块之后每个 chunk 里的数字成了无主孤魂,检索召回它们毫无意义,生成模型看到也读不懂。
第二类:表头与数据分离。表格的语义一半在表头里。多层表头(一级「2024 年」下面分「Q1 Q2 Q3 Q4」)被解析器拆成不同区域后,数据行彻底失去语境。更常见的场景是长表格跨页:第二页延续的表格没有表头,切出的 chunk 只有裸数据。
第三类:检索到了、算不出来。用户问「哪个季度毛利率最高」,检索能召回到包含全部季度数据的 chunk 已经很难,就算召回了,聚合比较这种「运算型」问题对生成模型也是挑战——它需要在上下文里做跨行比较,一旦数字带单位、带括号负数、带千分位,错误率陡增。
三类失败说明一个事实:表格不能当作普通文本塞进向量库完事。它需要一条专门的链路:识别还原、结构化表示、合理切片、问答分流。
flowchart TD
PDF["PDF / 图片"] --> DET["表格检测 + 结构识别"]
DET --> JSON["结构化存储(表头链 / 行列 / 合并)"]
JSON --> SER1["线性化三元组 → 检索索引"]
JSON --> SER2["Markdown 整表 → 生成上下文"]
SER1 --> R{"问题类型"}
SER2 --> R
R -- "定位型" --> L["行级切片单跳命中"]
R -- "聚合型" --> A["候选行集合 → 代码计算 → 模型解读"]
L --> ANS["答案 + 出处"]
A --> ANS
表格的识别与还原:解析层的硬仗
表格检测与结构识别
PDF 里的表格分两种命运:原生 PDF(可编程生成)里表格可能带线条或布局信息,可以按位置聚类还原;扫描件和图片里的表格则是纯像素,需要视觉模型。学术界的两条主线值得跟踪:
检测 + 结构识别两段式。先用检测模型框出表格区域,再用结构识别模型输出单元格网格与合并信息。这个方向的标准参考是 Table Transformer(Smock 等,PubTables-1M,AAAI 2021),作者发布了百万级标注的 PubTables-1M 数据集,结构识别在这个数据集上已经做到相当高的网格准确率。开源工具链里,Microsoft Table Transformer 权重 + 自研后处理是最常见的组合。
端到端 OCR 重排。Nougat(Meta,2023)为代表,用视觉 Transformer 直接把页面图转成 Markdown/LaTeX,表格在输出里天然是结构化的。端到端的好处是省掉管线拼接,代价是对版式的泛化能力有限——训练数据外的版式,表格识别质量会明显抖动。
工程选型上我们的结论是:没有万能解析器,按文档类型分流。原生 PDF 走文本层提取 + 位置聚类;印刷体扫描件走 Nougat 或 marker 这类端到端工具;版式复杂的历史档案走检测 + 结构识别两段式,并用人工抽检建立每类解析器的质量基线。解析层必须配质量监控:定期抽样对比解析输出与原图,表格网格完整率低于阈值就告警。
无边框表与合并单元格
两个老大难值得单独说。无边框表(靠对齐而非线条组织的表格)是检测模型的高频漏检区,兜底手段是对齐启发式:同一页面内按 y 坐标聚类文本行,列间距稳定的行簇标记为疑似表格,交给 LLM 复核。合并单元格是结构识别的深水区,很多工具的输出是「把合并格的值填到左上角,其余留空」——下游切片时必须做值的前向填充(forward fill),否则每个数据行的语义都是残缺的。
表格的表示:序列化策略的选择
Markdown、HTML 与线性化三元组
结构还原之后,表格要变成可以切块、可以嵌入的文本表示。三种主流序列化各有取舍:
Markdown 管道表。可读性最好,LLM 对 Markdown 表格的理解训练最充分,是我们事实型问答的默认选择。弱点是不支持单元格合并的复杂结构,多层表头只能拍平。
HTML 表格。表达能力最强(rowspan/colspan 完整保留合并结构),适合结构复杂、且下游主要给 LLM 看的场景。token 开销比 Markdown 高三到五成。
线性化三元组。把每个数据格展开成「行头 - 列头 - 值」的三元组文本,如「2024 年 Q1 | 毛利率 | 34.5%」。这种表示对检索最友好——每个三元组自带完整语义,单独成 chunk 也不失去语境;代价是体积膨胀(一个 10x10 的表变成 100 条三元组),并且丢掉了表格的整体形状,不适合「看趋势」类问题。
我们的组合策略:索引用三元组,回答用 Markdown。检索索引进的是三元组切片(召回粒度细、语义完整),召回到目标表格后,从原始结构化存储(解析阶段顺手存的 JSON/HTML)取出整表重新序列化成 Markdown 进上下文。检索要细粒度,生成要全貌,两个阶段用不同表示,各取所长。
行级切片:把表拆成可检索的单元
长表格(几百行的价目表、明细表)是表格问答的另一个坑:整表进索引,嵌入被大量异质行稀释,检索什么都召不准;整表进上下文,token 又装不下。解法是行级切片:每行(或语义上属于同一组的行簇)切一个 chunk,每个切片强制拼上完整表头链和表格标题——表头上下文是切片语义的一半,这一步省不得。
切片粒度之上的一个增强是行摘要:对超长表格,用 LLM 给每行生成一句业务摘要(「这是华东区域 2024 年的销售额数据」)拼进切片。摘要让嵌入向量带上了业务语义,召回率提升明显,代价是索引成本增加。只在核心业务表格上值得开。
def table_chunks(table_json: dict, max_rows_per_chunk: int = 5) -> list[str]:
"""把结构化表格切成带表头上下文的行级切片。"""
header = table_json["header_chain"] # ["2024年", "分区域", "销售额/毛利率"]
title = table_json["caption"] # "表3:分区域经营数据"
chunks = []
for i in range(0, len(table_json["rows"]), max_rows_per_chunk):
rows = table_json["rows"][i:i + max_rows_per_chunk]
md = f"{title}\n表头:{' > '.join(header)}\n"
md += render_markdown_rows(header, rows)
chunks.append(md)
return chunks
表注、单位与版面上下文的注入
表格切片最容易丢的第三块语义是表注与版面语境。业务表格的信息密度远超表格本身:表注里写「单位:人民币万元」「不含税」「括号内为上年同期数」,这些限定条件决定每个数字的正确读法,漏掉表注的切片会让模型把万元当元、把同比当环比。工程约定:表注、来源说明、时间限定必须作为切片的固定字段拼进每片开头,与表头上下文同等地位;同页的正文里若有「下表展示了……(口径说明)」的引导句,也要抽出来挂到表格元数据上——正文引导句是表格口径的常见出处。
版面语境还包括表格的所属章节与邻近内容:同一张销售表,出现在「风险提示」章节和出现在「经营成果」章节,含义完全不同。切片元数据里带上章节路径,检索命中后这个路径还能给生成模型当「这段数字在讲什么」的语境锚点。这些字段的注入成本几乎为零(解析阶段顺手可得),但对表格问答准确率的影响是实打实的——我们在财务知识库上的消测里,仅补齐表注注入一项,数值类问题的错误率就降了约三成,是表格链路里性价比最高的一笔投入。
表格问答:检索之后还要会算
行检索 + 聚合的两段式
表格问题天然分两种:定位型(「A 产品的价格是多少」)和聚合型(「哪个区域增长最快」「总和是多少」)。定位型问题行级切片直接命中,单跳回答;聚合型问题必须两段式——先检索出候选行集合,再让模型在集合上做比较或计算,或者更稳的做法:把候选行转成结构化数据,用代码(Python 沙箱)算,模型只负责读题和解读结果。
数值聚合交给代码执行是吃过亏之后的决定。我们早期让模型直接心算求和,五百行的测试集里两位数以内的误差率超过两成;切换到「模型出查询意图、代码做计算、模型解读结果」的三段式后,计算类错误基本归零,剩下的错误全是检索没召回对的行。
数值对齐与单位陷阱
表格问答特有的两类错误,值得每个团队在评测集里专门建模:单位混写(同一列里「3.5亿」和「35,000万」并存)和括号负数(财务表用括号表示负值,模型容易当成正数)。解法在解析层做数值规范化:识别单位并统一换算成标准单位、括号负数转符号、千分位剥离,规范化结果连同原文本一起存进切片。让生成模型面对的是干净的数字,错误率立竿见影地下降。
工程实践与评测
表格链路的评测要有专属指标,通用的端到端准确率不够定位问题。我们的表格评测集分四层:解析层(网格还原准确率、表头识别率,对人工标注的金标表格)、切片层(目标行所在切片是否被召回,行级召回率)、检索层(端到端的 Top-K 命中)、问答层(定位型与聚合型分开算准确率)。四层指标把错误归因拆开,才知道该优化解析器还是检索器。
基准方面,TAT-QA 和 FinQA 这两个财务表格问答数据集是行业通用参考,新方案上线前先在它们上过一遍基线,避免「自建评测集自嗨」。学术上,LLM 对表格的理解能力有专门的系统研究(如 2024 年的 Table Meets LLM 系列),主要结论对我们有两个提醒:表格序列化方式对模型性能影响显著(Markdown 通常优于纯文本转述);模型对大表格的「看全貌」能力随行列数增长衰减很快——这从模型侧印证了行级切片 + 两段式的必要性。
最后一条经验:表格链路的投入产出比极高,但前提是解析层过关。解析不过关,后面所有优化都是在沙滩上盖楼。先建解析质量监控,再谈表格问答。
小结
表格理解的失败根源在于:表格是二维结构,而文本处理链路是一维的。完整方案分四步:识别还原(按文档类型分流解析器,盯住无边框表与合并单元格两个深水区)、结构化表示(检索用线性化三元组、生成用 Markdown,两阶段各取所长)、行级切片(每片强制拼表头上下文,可选行摘要增强召回)、问答分流(定位型单跳回答,聚合型交给代码执行计算,模型只管读题与解读)。
数值规范化(单位、括号负数、千分位)是表格特有的必修课,四层专属评测(解析/切片/检索/问答)是持续优化的仪表盘。表格链路是 RAG 系统里少有的「投入一次、全库受益」的模块——业务知识库里的表格密度,值得你给它专门建一条管线。
读者留言
COMMENTS 暂无还没有留言,来说第一句?