做论文复现 AI Agent 的朋友应该都有体会:Agent 跑得顺不顺,往往不取决于模型脑洞有多大,而取决于喂进去的那篇论文 PDF 是不是“干净货”。我最近在模拟项目X的复现流程里,前前后后把 PDF 解析部分调了小两个月,评测分数从 54 一路拉到 92。中间踩的坑,套用标题里的说法,真就是一本“西游记”——双栏排版、公式乱码、表格错位、图注分离,每一难都有好几层解法。这篇文章就把整个过程摊开讲:为什么 PDF 解析会成为复现 Agent 的第一个深水区,怎么从无脑文本抽取一步步迭代成版面感知的结构化解析,以及每一轮调试背后的评测逻辑。
在动手之前,得先说明这类 Agent 大概长什么样。所谓论文复现 Agent,输入往往就是一篇论文 PDF,输出是一套可执行的复现方案、实验配置和核心代码逻辑。模型本身再聪明,第一步仍然要把论文里的标题、方法、公式、表格和超参稳定地抽出来,才能做后续推理和代码生成。而这个环节里,PDF 解析不是“锦上添花”,而是“雪中送炭”。以下四节,是我从 54 分打磨到 92 分的完整记录。
1. 为什么PDF解析是复现Agent的第一道拦路虎
1.1 PDF底层是“为打印而生”的伪结构化格式
先说一个很多人会忽略的底层事实:PDF 本质上不是文档格式,而是一套页面描述语言。打开一份 PDF,可以理解为拿到了一张“打印指令集合”,里面记录的是:在这个绝对坐标位置、用这个字体、画这串字形;在那个位置、画一条多长的线段。页面里的正文、标题、表格、公式,对 PDF 阅读器来说全部降解为“几何对象”,没有“这一段是摘要”“这一行是表头”这种语义。
所以,从 PDF 里提取文字,和从 Word 里复制粘贴是两码事。Word 文档自带段落树、样式表、表格结构,解析器照着读取就行。PDF 没有这些东西,必须靠坐标、字体、字号、相对位置去反推结构。这就像拿到一份传真件,内容清清楚楚,但如果你想自动化处理里面的“答案”,得先把版式规则重新发明一遍。
我第一轮踩得最狠的坑就在这里:拿文本抽取库直接输出页面上的文字流,以为“抽出来就行”。结果出来的顺序完全不是人读的顺序,更不是论文的逻辑顺序。后来我意识到,要把 PDF 解析好,第一步不是抽文本,而是把“版面上的每个框是什么、彼此什么关系”搞清楚,再谈文本内容。
1.2 论文PDF的双栏与公式“妖路”
论文 PDF 比其他文档难解析,不是没有原因的。绝大多数会议和期刊模板都是双栏排版,正文区域分成左右两栏,有些甚至是三栏。文本抽取工具按内容流输出的时候,常常是“左栏读几行,跳到右栏读几行,再跳回左栏底部”,一段完整的摘要能被切成七八个碎片。
双栏只是第一难,公式才是真正的重灾区。数学公式在 PDF 里不是“一个公式对象”,而是一堆独立字形按基线拼出来的组合:分式的横线是一条线段,分子分母是两组不同字号的文本块,求和符号的下限、上限都是单独的小字号字符。用普通文本抽取拿到的公式,经常被拆成“一行半个积分号”“一行上标”“一行下标”这种残肢状态。
更麻烦的是字体编码问题。不少论文的数学字体做的是子集嵌入,符号落入了字体私有区,抽出来要么是乱码,要么是某种自定义 Unicode 字符。这种字符复制出去根本不可用,直接喂给大模型,模型也只能靠猜。
另外,表格在图中的坐标位置也是大问题。表格的边框线是矢量图形对象,单元格文本是文本对象,两者在内容流里完全是两路数据。纯文本抽取只能拿到散装文字,根本拿不到“哪几个字属于同一格、哪行是表头、哪些列是对齐的”这类结构信息。图注和图片本身更是彻底分离,光靠文本流根本没法把“图 1”和它对应的插图绑定起来。
1.3 复现Agent的要求比普通RAG苛刻得多
有人会说,现在大模型都很聪明,文本流乱一点、公式残缺一点,模型也能“猜”出来吧?普通检索问答场景确实是这样,容忍度很高,几个字符偏差不影响整体理解。但论文复现 Agent 完全是另一回事。
复现任务里,Agent 要把论文里的公式转换成代码、把实验配置表格里的超参抽取成 json、把算法伪代码还原成可执行脚本。任何一步的解析错误,都会顺着链路一路放大。公式里少一个负号,网络结构就偏了;表格里错一位小数,训练配置就废了;阅读顺序错乱,Agent 会把摘要里的背景信息当成方法部分来解读。
打个比方,普通 RAG 是“读散文,理解大意”,前后漏几行不影响情绪。论文复现 Agent 是“照着菜谱做菜”,食材用量标错了,顺序步骤漏一步,成品就完全不是那个东西。所以论文复现 Agent 对 PDF 解析的期待,不是“能读”,而是“能抄作业”——抄得准、抄得全、还能按原格式落地。
这也是为什么我把精力重点放在“结构化重建”上,而不是单纯提高文本字符正确率。后面你会在评分权重里看到,顺序、公式、表格这三项占了总分的大头,就是因为它们直接影响 Agent 的复现成功率。
2. 基线版本与54分配置
2.1 第一版解析器的实现思路
先交代一下 54 分版本的架构。我当时的目标很朴素:做一个能跑的基线,先把 PDF 文字抽出来,再用规则整理成 markdown,喂给 Agent。整个链路大致是:
- 用 PyMuPDF 按页提取字符级信息,拿到每个字符的坐标、字体、字号;
- 按 y 坐标把字符聚类成“行”,同一行内按 x 坐标排序,拼成行文本;
- 根据行间距判断哪些行属于同一段落,最后拼成段落列表;
- 表格、公式不做区域识别,全部当普通文本流过;
- 元字段(标题、作者、摘要、章节)用正则和头几个页面启发式匹配。
这个方案写起来很快,跑起来也不会崩,但结果就是“看起来每个模块都有,每个模块都很糙”。我当时的想法是先跑通流程,后面再逐项优化。没想到的是,流程跑通之后,问题不是单个模块不够好,而是整个信息的空间结构被彻底抹平了。
2.2 五维度评分体系与分数构成
为了不靠感觉调优,我搭了一套自动评分体系。评测集里每篇论文都有人工标注的“标准解析结果”,机器解析结果和标准结果比对,拆成五个维度:
| 维度 | 评测内容 | 权重 |
|---|---|---|
| 文本字符 | 正文段落的字符级 F1 | 15% |
| 阅读顺序 | 段落级顺序一致性 | 30% |
| 公式完整度 | 公式区域字符召回与合并正确率 | 25% |
| 表格结构 | 单元格划分、行列对齐、跨行列合并还原 | 20% |
| 元字段识别 | 标题、作者、摘要、章节标题的 F1 | 10% |
加权总分公式:总分 = 100 × (文本字符 F1 × 0.15 + 阅读顺序 ACC × 0.3 + 公式完整率 × 0.25 + 表格结构 ACC × 0.2 + 元字段 F1 × 0.1)。
54 分版本的具体状态大概是:文本字符 F1 0.88,阅读顺序 ACC 0.41,公式完整率 0.58,表格结构 ACC 0.37,元字段 F1 0.67。加权算出来差不多就是 54 分。
这套权重不是随便拍的。顺序权重最高,是因为后面所有结构重建都依赖正确顺序;公式和表格各占 25% 和 20%,是因为复现 Agent 下游要用它们生成代码和配置。文本字符权重反而只有 15%,因为字符层面大家都能做得差不多,拉开差距的关键在结构。
2.3 54分试卷逐题分析:问题到底出在哪
把 54 分解剖开来看,很多问题其实是连在一起的。文本字符 F1 有 0.88,说明基础的字符抽取没有大毛病,字体解析也基本靠谱。但阅读顺序只有 0.41,意味着大量段落顺序是错乱的,尤其是双栏论文,左栏读完跳右栏、再跳回左栏的情况非常普遍。
公式完整率 0.58 说明有一半左右的公式区域是不完整的。一部分是公式被拆到多行,另一部分是公式和正文混在一个段落里,无法区分边界。表格结构 0.37 几乎等于没有表格结构,因为基线的确没做任何表格重建,所有单元格文本都平铺在文本流里,行列关系全靠正则去猜,自然一塌糊涂。
比较意外的是元字段 F1 0.67,比我预想高一点。原因是很多论文的前几页版式相对规整,标题、作者、摘要的坐标位置比较固定,启发式规则能蒙对一部分。但一旦遇到首页带复杂图注、多机构作者、跨栏摘要的论文,正则就失灵了。
看完这个五维画像,我心里就有数了:54 分的瓶颈不在“抽不到文本”,而在“文本没有结构”。如果继续在字符层面做清洗、加正则,天花板也就是 60 出头。真正应该做的,是把解析思路从“读文本流”切换到“读版面”。
3. 关键迭代:从“读文本流”到“读版面”
3.1 版面分析先行,双栏阅读顺序终于对了
第一次大幅度提分,靠的是引入版面分析。思路很简单:先让模型告诉我页面上每个区域是什么类型、边界框在哪,我再根据区域框去安排阅读顺序。
我在这一步选了一个公开的版面目标检测模型,识别类别包括标题、作者区、摘要、正文、公式、图片、表格、图注、页眉页脚九类。模型先给出一堆带置信度的框,然后我做了三件事:
- 过滤掉页眉页脚和置信度低于阈值的框;
- 根据所有框的 x 坐标分布检测栏数,把页面切成左右两栏;
- 栏内按 y 坐标从上到下排序,栏之间按 x 从左到右拼接。
这一步做完,阅读顺序 ACC 直接从 0.41 跳到 0.85 左右,总分离 54 直奔 70。
有一点非常关键:坐标系的转换。PyMuPDF 返回的坐标原点在页面左上角,y 轴向下;而 PDF 内容流的原生坐标系原点在左下角,y 轴向上。如果混用两个坐标系做排序,很容易出现整个页面的顺序整体倒过来。我第一次就栽在这儿,评测出来摘要出现在了最后,排查半天才发现是两个库的坐标系没对齐。
我个人建议,做版面分析时把所有坐标统一到同一个约定,例如统一用“左上角原点、y 向下”的像素坐标,所有模型推理、规则判断都用同一套,能省掉大量隐性 bug。
3.2 公式块合并,数学内容不再“粉身碎骨”
版面分析给了公式区域框,但区域框还只是第一步。双栏论文里的公式经常跨行排列,一个分式上分子一行、分母一行、横线一条,模型只告诉你“这片区域是公式”,不会自动把这几行合并成一个完整公式。
公式块的合并,我核心用的是三个维度的判定:
- 垂直方向:同行文本块的 y 坐标重叠度;
- 字体维度:公式区字符往往不是正文默认字体,通常是衬线斜体或专门的数学字体;
- 基线维度:分子、分母、上下标都有各自的基线,可以通过相邻行的基线差判断是否属于同一公式。
合并策略是:当检测到某个区域的上一行和下一行在字体和基线特征上都与正文不同,且行间距远小于正文行距,就判定为同一个公式块的延续。把所有判定为同一公式的行按原始顺序拼成一个多行块,保留内部的空格和相对换行,而不是强制压成一行。这个“保留原样”的细节很关键,因为公式的二维结构(分式、积分上下限)一旦被压平,语义就丢失了。
对特殊字符,我的处理原则是“不过度修正”。字体私有区里抽出来的 Unicode 字符,先原样保留,不做规则替换成 LaTeX,也不强行转义。等后续有需要时再通过专门模型或者提示词让 Agent 去理解。实测下来,先在文本层保留原字符,比急着做符号归一化要稳得多——后者很容易把某个真正有意义的符号替换成错的东西,造成二次伤害。
做完公式块合并之后,公式完整率从 0.58 升到 0.85 附近,总分又涨了几分。
3.3 表格结构重建,从“一坨文本”到行列JSON
表格是这次迭代里最费周章的部分,也是从 80 分往 92 分走的关键分水岭。
我先从 PDF 的矢量图层抽所有横线和竖线段,把线段按坐标聚类成“水平线组”和“垂直线组”。然后,用这些线组的 x、y 值构建表格网格:所有水平线的 y 坐标和垂直线的 x 坐标两两交叉,形成一个个单元格矩形。再遍历每个矩形,把落在矩形内部的文本块归入该单元格,最终输出 JSON。
听起来思路很简单,实际全是坑:
- 有的论文表格不是全网格,只有横向线没有纵向线,比如三线表。这种情况必须退化为“按列 x 坐标聚类 + 按行 y 坐标聚类”,再根据文本的对齐关系推断单元格边界。
- 合并单元格会打破整齐的矩形网格,一行有 5 列、另一行只有 3 列,中间跨列合并了。处理办法是先按最大列数构建网格,再把那些“没有线但是有文本”的跨列区域识别出来,标记 colspan。
- 跨页表格会被分成上下两个视觉块,需要根据表头行是否重复来判断是否属于同一表格,然后拼接。
表格输出的结构化格式我定为table: {header: [...], rows: [{cells: [...]}]}。有了这个结构化表示,Agent 才能直接把它翻译成实验配置字典,而不是从 markdown 表格里二次解析。
做完表格重建,表格结构 ACC 从 0.37 跳到 0.83,总分离 90 分也不远了。
3.4 结构化字段映射,Agent终于能“按图索骥”
分数到了 85 以后,继续提高版面模型精度很慢,我转向了字段映射优化。这一步要让解析结果不仅仅是“版面顺序对了”,而是能让 Agent 快速定位“方法在第二章”“超参在表 3”“伪代码在这块区域”。
字段映射我用的是“版面标签 + 启发式”混合方案。标题、作者、摘要这些头部区域,版面模型已经给了框,再叠加一个轻量序列分类器来打标签,比单纯正则匹配鲁棒得多。章节标题的层级,根据字号、加粗、编号模式来判定 level,然后维护一个“章节栈”,遇到新的标题就 push,遇到低层级标题就 pop。这样最后能输出一棵完整的章节树。
图注和图的关系也在这里处理。版面模型检测出图片框和图注框,如果图注框的位置紧贴图片框下沿或上沿,并且内容以“图数字”开头,就把两者关联起来。这样 Agent 在阅读正文时,提到“图 2 的网络结构”,系统可以立刻把对应图片区域和它的上下文一起丢给模型。
参考文献条目解析按段落切分,每个“[序号]”开头的段落作为一条引用,再做字段解析。算法伪代码则是检测等宽字体块,保留缩进和换行,等宽字体块内的文本不参与普通正文的段落合并,防止伪代码被重排得面目全非。
3.5 配套工程改造:缓存解析结果与快速回归
在提升解析质量的同时,我还做了一些工程化改造,否则迭代速度根本撑不住。
版面检测模型跑十几页 PDF,纯 CPU 推理一次要几十秒,这在调试阶段完全没法用。我把每个 PDF 的版面检测结果(框坐标、类别、置信度)缓存成 json,后续所有规则实验都直接读缓存,不再调用模型。只有模型版本升级时,才重新跑一次全量缓存生成。
其次,我建了快速回归脚本。任何一次规则改动,都对同一个 16 篇样本的子集跑评分,输出五维度折线变化。没有这个脚本,我可能在某种过度拟合的改动上多花好几天,没法及时发现“表格分数涨了,但公式分数跌了”这种问题。
这一步虽然不直接提分,但它是支撑分数从 85 平稳爬到 92 的重要基础。没有快速反馈,调优就是盲人摸象。
4. 评测集构建、避坑与经验沉淀
4.1 评测样本怎么选,分数才不是自欺欺人
解析器的分数能信多少,取决于评测集长什么样。我见过很多人拿 10 篇同模板同来源的论文测出来 95 分,换一批真实 PDF 立刻崩。我这次的评测集刻意覆盖了以下几种情况:
- 单栏和双栏排版的论文都有,比例大约 3:7;
- 不同出版风格:有的页眉复杂,有的首页带大图,有的参考文献两栏缩进;
- 公式密度不同的论文:纯算法类、实验验证类、综述类;
- 表格形态各异的论文:全网格表、三线表、带多级表头的复杂表;
- 少量扫描版用于 OCR 通道的独立测试,不混入主评测。
样本量我控制在 40 篇左右,全部人工标注。标注流程是两个人独立标,分岐部分由第三人裁决。40 篇看起来不多,但人工把每篇的标题、章节、每个公式边界、每个表格单元格标记出来,工作量已经相当可观。做解析器评测,标注质量比数量重要得多。
一个特别容易忽视的点:评测集要有“版本冻结”机制。选定评测集之后,不允许因为解析器跑不过某一个样本就随意换掉样本,那样等于给评测作弊。要增补样本可以,必须完整跑一次全量回归,看整体分数变化,而不是只看新样本。
4.2 高频问题速查表
调试过程中我整理了一张高频问题速查表,后来成了团队里所有人的避坑手册。这里直接贴出来:
| 现象 | 根因 | 处理办法 |
|---|---|---|
| 段落顺序左右横跳 | 双栏排版未被识别 | 先做版面分栏,再栏内排序 |
| 公式被拆成碎片 | 公式行未合并 | 按字体、基线差异做公式块合并 |
| 公式符号乱码 | 字体私有区编码 | 保留原 Unicode,不强行替换 |
| 表格单元格错位 | 只有线框没有列线 | 退化用文本坐标聚类推断列边界 |
| 合并单元格丢失 | 网格不是完整矩形 | 识别跨列文本区,标记 colspan |
| 图注关联不上图片 | 图注位置与图片分离 | 用位置紧邻关系加关键词匹配 |
| 页眉页脚混入正文 | 版面模型漏检 | 后置规则按日期、页码模式过滤 |
| 跨页表格被切成两块 | 页面级解析只见局部 | 跨页表格拼接,保留重复表头 |
这张表的价值在于省去重复翻代码的时间。绝大多数解析问题都不是玄学,根因无非是“结构没识别出来”或者“两类信息没关联上”,对着表格逐项检查基本能定位。
4.3 几类低性价比优化,优先避开
我这次调优过程里也走了不少弯路,有几类优化属于典型的“低性价比”,后来基本都回滚了,单独列出来给大家避坑。
第一类是“针对评测集写死规则”。有一段时间,发现某篇样本里公式总被误标为正文,我就在规则里根据字体名写死了“遇到某种字体就判定为公式”。测试集当场分数涨了,换到另一批论文后,同样的字体名出现在普通强调段落里,误判更多了。这类规则的适用范围太窄,属于为了一个样本牺牲整个系统的典型操作。
第二类是“美化式后处理”。比如发现公式抽取结果里有一些不常见的 Unicode 字符,就写规则把某个字符统一替换成另一个“看起来更像”的符号。这个操作本质上是拿猜测覆盖原始数据。万一原始字符是某个特殊算子,替换后语义就错了。解析器宁可保留“难看的原字符”,也不要自作聪明地“修正”。
第三类是“在顺序上做极限启发式”。比如为了修复某个左栏带图导致阅读顺序跳转的样本,我加了一个“遇到左栏有图就强制先读右栏”的特判。这种特异性极强的规则只有在特定版式下才有效,一旦碰到其他版式,反而把正常顺序搞坏。阅读顺序应该由版面结构和坐标关系驱动,而不是靠几个零散样例的启发式覆盖。
4.4 解析结果的长尾维护:持续回归的价值
解析器做完 92 分,不代表事情就结束了。论文 PDF 的版式分布极其长尾,总会有新的妖魔鬼怪出现:有的论文把附录排成三栏,有的把表格旋转 90 度变成横向页,有的公式编号和公式本体之间隔着大段空白。这些情况,评测集里往往没有,实际跑起来却可能遇到。
长期维护的关键是持续回归。我把已有 PDF 解析结果全部持久化,每新增一类版式样本,就触发一次全量回归,观察五维度分数的变化。如果某一项下降超过 2 个百分点,就说明新改动引入了回归,必须回溯定位。这样做的目的是让解析器“稳定地 92 分”,而不是“上周 92 分,这周换了一批样本变成 88 分”。
版本管理上,我给每份解析结果打上“解析器版本号”。同一个 PDF 在不同版本下的输出可以并行保留,方便对比单字段级 diff。这个做法在调试阶段帮助极大,能精确回答“到底哪次改动改变了哪个区域的结构”。
把整套流程跑完,我最想分享的一个体会是:解析系统的分数提升,靠的不是某一个灵光一现的模型,而是“评测集定义清楚 → 基线量化 → 逐模块归因 → 小步迭代 → 全量回归”这个循环。54 分的时候我觉得问题很复杂,回过头看,是当时连问题出在哪一层都没分清。现在遇到一个新 PDF 解析任务,我会先花三天把评测和问题归因做扎实,再谈用什么模型和规则。这个习惯,比任何单点的解析技巧都值钱。