12013 页只 OCR 2 页:Agent 文档处理的两遍式设计
原文:LlamaIndex Blog - 《Just-in-Time Agentic OCR》(https://www.llamaindex.ai/blog/just-in-time-agentic-ocr)
引子:一个很常见的两难
用户上传几十份 PDF,问一句「排除并购影响,2022 年哪个业务板块拖累了公司整体增长」。你要么把所有页面都丢给视觉模型做 OCR,等得久、花得贵;要么只用免费的文本抽取,结果是扫描件一片空白、双栏排版被交错、表格被拆成按列顺序堆叠的数字。
LlamaIndex 的 Jerry Liu 在 2026 年 9 月这篇《Just-in-Time Agentic OCR》里,把主流 Agent 框架已经在默认采用的做法总结成了一个模式:先廉价地粗读全部文件,再用视觉模型只精读「被检索到的那几页」,他称之为 just-in-time Agentic OCR。
本文按原文拆解这套两遍式设计:它为什么省、省在哪、怎么判断哪些页面值得精读、以及它在什么规模下会失效。
一、两遍式到底分几步
原文的流程可以拆成三个动作。
第一遍是粗读(skim)。对上传的每个文件跑一次廉价的、不依赖视觉模型的文本抽取。结果并不完美,但足够用来做关键词检索,也足够在上面建一个轻量语义索引。
中间是检索(retrieval)。在粗读产物上做搜索,关键词或语义都可以,目的是定位候选文件与候选页面。
第二遍是放大(zoom in)。只对相关页面取一次 VLM 质量的读取。原文给了两条路径:把页面截图交给 Agent 自己的视觉模型(文中举例是 Cowork 里的 Opus 5),或者调用本身就带视觉能力、且直接返回结构化结果的文档解析器。
原文还给了两种把第二遍继续压便宜的办法。
一是只对文档子集跑 VLM OCR。如果检索把答案指向一本约 250 页年报的第 24 到 25 页,那就只解析这两页,延迟和成本同时下降。
二是后台仍然对全量文件跑一遍 VLM OCR 并缓存结果。用户先拿到两遍式循环给出的快速答案,缓存则在后台补齐,避免同一批页面被反复解析。
二、一个真实例子:84 份 SEC 文件、12013 页
原文用 Patronus AI 的 FinanceBench 举例。它的开源部分包含 150 道题,覆盖 84 份公开文件(10-K、10-Q、8-K 与财报发布),合计 12013 页,规模上很接近一次真实尽调的数据室。
其中一道题是:如果排除并购影响,2022 年哪个业务板块拖累了 3M 的整体增长?
两遍式循环在这道题上的表现如下,数字来自作者在本机的实测。
第一步,粗读。用 LiteParse 的纯文本模式解析全部 84 份文件共 12013 页,耗时 32 秒。原文的说法是,第一遍基本上是免费的。
第二步,检索。在输出上做一次关键词检索,命中 3M 的 2022 年 10-K,落在标题为 Worldwide Sales Change By Business Segment 的表格下方几行,位置是 252 页中的第 25 页。
第三步,放大。第 25 页上有三张财务表。直接跑 pdftotext 的输出会把表格按列倾倒,先是 Organic sales,接着五个板块名,再接着五个数字,无法判断哪个数字属于哪一行。LiteParse 在这里表现更好,因为它保留了文字的 x/y 坐标。第 24 页更难:一张 14 列、三层堆叠表头的表格,在任何纯文本表示里都是有歧义的。把这两页交给 LlamaParse 的 agentic 模式后,得到了带 rowspan 与 colspan 表头的 HTML 表格,答案是 Consumer,有机口径下滑 0.9%。
整道题里,Agent 只对 12013 页中的 2 页跑了 VLM OCR,这一步耗时几秒。这个对比值得记住:廉价的第一遍覆盖全部 12013 页,昂贵的第二遍只打了 2 页。
三、怎么判断哪些页面必须走 VLM
上面之所以能只打 2 页,前提是 Agent 知道哪几页需要精读。原文介绍了两种信号。
第一种是 LiteParse 的逐页复杂度命令。它会按页打印是否需要 OCR 的判定以及理由,理由分为扫描件、无文本层、文本稀疏(典型是大表格)、内嵌图像、乱码这几类,并且能识别疑似表格与多栏排版的页面。在 84 份文件上,它标出了 12013 页中的 2605 页,占 21.7%,其中绝大多数是文本稀疏;另外还有 253 页含内嵌图像、75 页完全没有文本层。
第二种是 LlamaParse 暴露成 MCP 工具的复杂度评估接口,把每一页映射到一个解析档位。在 3M 那份 10-K 上,它把大约三分之二的页面路由给了 LiteParse,剩下约 35% 交给 VLM 档。
对写 Agent 的人来说,这类逐页信号的价值在于:它把「要不要花这笔视觉解析的钱」变成了一个在调用之前就能做出的、可以解释、可以预估成本的判断,而不是让模型现场凭感觉猜。
四、这套模式什么时候不该用
原文把边界划得很清楚:两遍式是给临时的数据室场景准备的,也就是用户上传 10 到 100 份文档,然后在其上提问。它不适用于 1000 到 100 万份以上的离线数据管线。
原因在于容错来源不同。84 份文档的规模下,即使第一遍把某张表弄坏了、或者漏掉了扫描页,检索确实可能漏掉它,但 Agent 完全可以不只用检索。它可以遍历全部文件,像人在尽调时那样边搜边读,代价只是延迟变高。到了百万份量级,Agent 只能看到检索返回的东西,检索质量直接受制于它依赖的文本表示质量。这时扫描件、表单、手写、图表的解析准确度,就成了检索准确度的上限。
原文给出的经验规则是两条。约 10 到 100 份文档、临时提问,走两遍式:廉价粗读,检索,然后只对相关页做 just-in-time 的 VLM OCR,可选的是后台补全量。1000 到 100 万份以上、离线管线,则先做全量 VLM OCR,再建索引。
原文还列出两种应当直接前置全量 VLM OCR 的情形。
一是面向 RAG 的大规模离线批处理。解析成本会被未来成千上万次查询摊薄,而且每一页都值这个价钱。
二是字段抽取类场景,比如发票录入、理赔录入、身份核验。这类场景准确率极其重要,一次误读会变成下游系统里的错误数字,因此需要每个字段都带边界框和置信度供人工审计。而且这里没有「以后再放大」的余地,因为每份文档本来就要全部处理。
这两种情形的算账方式与两遍式不同:大约每页一美分,一次付清。
五、开箱即用的方案有什么问题
原文对当前框架默认的两遍式实现提了三点批评,很值得一线开发者对照自查。
第一,前沿模型不是最好的 OCR 模型,而且大规模下太贵。前沿模型的后训练目标是推理、数学和代码,文档 OCR 并不是各家实验室在优化的榜单。原文引用了 LlamaIndex 自己的 ParseBench 结果:2078 页人工校验的企业文档、16.9 万条测试规则,覆盖表格、图表、内容忠实度、语义格式与视觉定位。在测试过的模型里,Fable 5.1 综合 78.9 分、每页 16 美分;Gemini 3.1 Pro 69.1 分、8.5 美分;GPT-5.5 67.8 分、13 美分;Opus 4.8 63.7 分、7.3 美分;LlamaParse agentic 87.0 分、1.25 美分。原文注明尚未评测 Opus 5。
第二,更严重的问题是没有视觉定位。Opus 4.8 的视觉定位得分是 18.7,非思考模式的 Haiku 4.5 与 GPT-5 Mini 低于 7,而 LlamaParse 是 84。没有边界框和置信度,输出就无从审计;Agent 读错一个表格单元格,这个错误会顺着后续工作流一路传播下去。
第三,免费的 OSS 工具作为第一遍不够通用。pypdf、pdftotext 这类工具只把数字 PDF 的文本层拉出来,扫描页返回空,多栏排版被交错(文本层存的是绘制顺序而不是阅读顺序),表格被按列倾倒。而真实客户的文件堆里大约只有一半是 PDF,其余是 Word、PowerPoint、Excel 和邮件导出,这些工具根本处理不了。
还有一个容易被忽略的浪费:Agent 会写大量一次性代码,去重造解析器本该直接返回的东西,比如把页面渲染成图片、裁剪插图、在 pandas 里重建表格、猜单元格边界。这些代码每次会话都要重新推导,既花钱又费时,而且不可复用、不可复现。
需要说明的是,上述评分与单价均出自 LlamaIndex 官方博客口径,属于厂商自测结果;相关解析器与模型的版本未验证最新版本,选型前请以官方文档为准,并用自己的数据复测。
六、落到自己 Agent 里的最小骨架
下面这段是自拟的两遍式骨架,不是官方 API,只用来把上面的机制对齐到代码结构。参数名以你实际使用的解析器文档为准。
# 自拟示意代码,非官方 API,仅用于说明两遍式结构fromdataclassesimportdataclassfrompathlibimportPath@dataclassclassPageRef:file:Path page:intdeftwo_pass_answer(files,question,parser,vlm):# 第一遍:廉价粗读,产物只用于检索skimmed=parser.parse_many(files,ocr=False)# 检索:在粗读产物上定位候选文件与候选页hits=skimmed.search(question,top_k=5)# 逐页复杂度信号:决定哪些页值得走 VLMtargets=[]forhinhits:verdict=parser.page_complexity(h.file)# 逐页返回是否需要 OCR 及理由pages=[pforpinh.pagesifverdict.needs_ocr(p)]targets+=[PageRef(h.file,p)forpinpages]# 第二遍:只对目标页取 VLM 质量的结构化读取# 返回带边界框与置信度的结果,供后续审计zoomed=[vlm.parse_page(t.file,page=t.page)fortintargets]# 可选:后台对全量文件补跑 VLM 并缓存,避免重复解析同一批页面returnskimmed,zoomed几个参数与返回值值得对应看一遍。ocr 参数设为 False 是这次调用最关键的开关,它把第一遍压到接近零成本。检索的返回数量上限决定了第二遍要打多少页,是成本的主要控制点。逐页复杂度判定返回的是判定结果加理由,也就是扫描件、无文本层、文本稀疏、内嵌图像、乱码这几类。按页解析是放大这一步的最小单位,也是成本真正发生的地方。函数最后返回两份产物:粗读结果用于后续继续检索,精读结果带边界框和置信度,用于给出可审计的答案。
七、可以带到下一个项目的几点
先分清场景规模再选架构。临时数据室走两遍式,离线大库前置全量 VLM OCR,两者的成本模型完全不同,搞混了要么慢要么贵。
把「哪些页面需要视觉解析」做成显式信号,而不是让模型凭感觉决定。逐页复杂度判定是可以解释、可以测试、可以预估成本的。
解析结果要带边界框和置信度。没有定位信息的输出,出错时只能整篇重跑,无法定位到具体某个单元格。
第一遍选型不要只图免费。多栏排版、表格和 Office 格式的支持度,直接决定了检索的上限。
警惕 Agent 现场写的一次性解析代码。能由解析器一次调用返回的结构,比如表格、边界框、置信度,不要每次会话都重新推导一遍。
原文的判断是,just-in-time OCR 会成为临时数据室场景的默认做法,框架也会逐步把这个模式显式化:廉价且感知版式的第一遍、能按页定位的 VLM OCR、逐页复杂度信号,以及后台补全。