表单是企业最关键的一类文档之一,因为它承载着来自客户和顾客的宝贵输入数据。除了处理大量文本本身带来的复杂性(包括成本、性能和延迟)之外,表单还为任何文档处理流水线带来了一系列独特的挑战。表单承载的结构比标准 Markdown 所能表示的更多,包括文本框、复选框,以及拥有紧邻其右侧控件的标签。因此,系统必须专门围绕这些属性来设计。输出必须在同一表单类型的各份文档之间保持一致,同时还要吸收同一表单在不同年份之间出现的差异。幻觉在这里造成的危害几乎比任何其他地方都更大。表单具有精确的结构,稍有偏差就会彻底改变含义。一个复选框是勾选还是未勾选,可能会翻转智能体所采取的整个下游动作。在这篇文章中,我们将阐述为什么表单需要特殊处理、LlamaParse 如何将表单表示为 JSON,以及当我们让 VLM 做同样的工作时所看到的失败模式。
为什么表单需要专门构建的解析器
解析表单有很多种方法,但它们在价格和性能上差异很大。虽然 VLM 是一个可以拿来试验的合理工具(只需传入一张页面截图并请求输出),但它在成本、可靠性和性能方面都伴随着挑战。VLM 是为通用推理而优化的,而不是为文档或表单处理而优化的,而我们的自身结果表明,增加推理并不真正能转化为更好的解析结果。
LlamaParse 是专门为文档和表单理解而构建的,它以极低的成本击败了通用 VLM。在底层,针对表单内容提取和边界框检测的优化流水线经过调优和组合,以达到最佳效果。
表示表单的结构
表单可以表示为一棵由字段和分区组成的树。字段可能有一个id、一个label,以及一个value。它们的field属性标识输入类型,而一个section将相关字段分组到items中。以下是我们表单 schema 中元素的示例:
- id:印在表单上的简短标识符,例如
1,12a或e。 - label:描述该字段是什么的文本。
- field:字段的类型
- checkbox:一个带有布尔值的框:
true勾选时,以及false未勾选时。 - text:用于输入文本的字段,包括数字、日期和地址。
- signature:一个签名框,已签名或未签名
- checkbox:一个带有布尔值的框:
- value:字段的值。文本字段为文本;复选框或签名为 true/false
表单对象可以用下面简单的 Pydantic 类来表示。根据表单的实际复杂程度,该结构可以有任意层级的嵌套。
以 Pydantic 类查看最小 schema:
fromtypingimportLiteral,Optional,Union
frompydanticimportBaseModel,Field
classFormField(BaseModel):
"""表单上的一项:文本框、复选框、签名或一组选项。"""
field:Literal["text","checkbox",...]
id:Optional[str]=Field(None,description="印在表单上的标识符,例如 '1'、'12a'、'e'")
label:Optional[str]=Field(None,description="印在字段旁边的标题")
value:Optional[Union[str,bool]]=Field(
None,description="文本字段的逐字文本;复选框或签名为 true/false")
classFormSection(BaseModel):
"""表单上打印出的分组,例如 'Part III' 或 'Sign Here'。"""
type:Literal["section"]="section"
id:Optional[str]=None
label:Optional[str]=None
items:list["FormNode"]
FormNode=Union[FormField,FormSection] 图 1 展示了这些部分如何在 W-2 上组合在一起。Box 1 变成一个文本字段,其 id 为1,label 为Wages, tips, other compensation,value 为230303.03。Box 13 保持为一个包含三个复选框选项的分组。Box 9 为空,但它仍出现在输出中。
查看 JSON 输出
[
{
"type":"field",
"field":"text",
"id":"a",
"label":"Employee's social security number",
"value":"827-37-3673"
}
,
{"type":"field"
,"field":"text"
,"id":"1"
,"label":"Wages, tips, other compensation"
,"value":
"230303.03"
}
,{"type":
"field","field":
"text","id":
"9","isEmpty"
:
true
},{"type"
:"field","field"
:"multi_select","id"
:"13","valueItems
"
:[{"type"
:"field","field"
:"checkbox","label"
:"Statutory employee","value
"
:
false},{"type
":"field","field
":"checkbox","label
":"Retirement plan","
value
"
:false},{"
type":"field","
field":"checkbox","
label":"Third-party sick pay",
"
value
"
:
true}]},{
"type":"section",
"id":"12a"
,
"items":[{
"type":"field",
"field":"Code",
"value":"H"
},
{
"type":"field",
"field":"text",
"value":"8699"
}
]
}
] 图 1。W-2 来源与表单表示。来源页面显示在左侧;表单表示json数组显示在右侧。
虽然把各个字段单独识别正确是第一步,但输出还必须保留它们之间的相互关系。图 2 展示了两个打印区域,各自带有独立的 "Date" 字段。一个扁平的元素列表会丢失每个字段来自哪个区域,因此两个 Date 字段将变得无法区分。LlamaParse 则改为将字段分组到各个区域中,把每个日期字段与其正确的父区域关联起来。
图 2。展示了 Form 1040 的各个区域:"Sign Here" 和 "Paid Preparer Use Only"。每个区域都包含自己的 "Date" 字段。
扁平列表
{signature,"Your signature"},{text,"Date"},{text,"Phone no."},
{text,"Preparer's name"},{text,"Date"}// 54 个节点,0 个区域
分组到各个区域
{section,"Sign Here", items:[signature,Date,occupation,Phone]},
{section,"Paid Preparer Use Only", items:[name,Date,PTIN,Phone]}
// 28 个节点,5 个区域 虽然复选框状态是表单上最关键的信息之一,但对 VLM 来说却是一项具有挑战性的任务,因为它要读取整个页面并一次性预测其所有内容。为了克服我们在 VLM 上观察到的局限,我们构建了一个小型复选框状态分类器,它会逐个查看每个框来预测状态,并纠正模型最初的预测。
图 3 展示了一个被勾选的框,而最初的 VLM 解析返回为false;我们的分类器对其进行了纠正,并以高置信度将其读取为已勾选。
图 3。对 W-9 上的复选框状态进行纠正。"Other" 复选框被标记了,尽管最初的 VLM 解析记录为false;复选框状态模型将其纠正为true。
之前{"field":"checkbox","label":"Other (see instructions)","value":false}
// 状态模型读取到该框已勾选 @ 0.94
之后{"field":"checkbox","label":"Other (see instructions)","value":true}
// 值已纠正,框未被改动 寻找这些框
边界框是你关心的对象周围的矩形,在表单上意味着一个文本字段或复选框。边界框对下游应用和验证至关重要,因为它们让审核者和用户能够对照原始文档进行检查。
图 4。Form 1040 上的边界框。蓝色框标记文本字段,绿色框标记复选框。
VLM 在这项任务上表现尤其差,会漏掉大量边界框。使用专门构建的确定性模型会带来远为出色的结果。在 LlamaIndex,我们从零开始训练了一个表单边界框检测模型,以大幅提升这项任务的性能。
图 5 展示了一个观察到的失败案例:在这次运行中,原始 VLM 只返回了两个框,而 LlamaParse 检测到了所有字段边界框。
图 5。在一个数字表单上观察到的一次 VLM 检测失败。VLM 的整页输出包含两个错位的框——一个在空白页边距上,一个在印刷的箭头上(左);同一页上 LlamaParse 输出的裁剪图显示了在商品成本部分检测到的录入单元格(右)。
即使 VLM 设法输出了大量边界框,将这些检测到的框与页面上真实的框对齐对 VLM 来说仍然是一个挑战。
图 6。扫描版 W-4 上的边界框对齐。VLM 的框在各录入行之间漂移(左),而 LlamaParse 在 23 个录入单元格上各放置了一个框(右)。
还有一些开源权重模型,例如FFDNet,它们是针对数字表单构建和评测的,在准确率上可以胜过 VLM。但这些开源模型是在空白数字表单上训练和基准测试的,缺乏对已填写数据或扫描件的稳健性。图 7 展示了它们在已填写的 W-9 上的失败,而图 8 展示了它们在扫描的、手填的 1040 上的失败。
图 7。已填写 W-9 上的边界框检测。开源权重检测器漏掉了已勾选的框,并将框从录入值处偏移(左);LlamaParse 在同一页上定位了字段和控件(右)。
图 8。扫描的、手填的 Form 1040 上的边界框检测。开源权重检测器在一个空字段上返回了一个框(左);LlamaParse 在同一页上检测到了全部 51 个已填写字段(右)。
将框归属于字段
归因是让 AI 文档处理可审计的关键。一旦提取出边界框,就必须将它们链接回对应的表单元素。
图 9。第 8c 行检测到的矩形被归因到管辖它的标签“债务取消”。
通用 VLM 可以尝试进行这种归因,但可靠性有限。将边界框匹配到正确的内容需要理解表单中每个组件的含义。随着表单变得更密集,复杂度也会增加。不幸的是,即使文本被完美读取,匹配也可能静默失败。图 10 展示了一个 VLM 归因失败:模型为第 8b 行返回了正确的值,70870,但把它的边界框附加到了第 8a 行。
图 10。一个静默的归因错误,以及同样几行被正确处理的情况。左:通用 VLM 正确转录了第 8b 行的值,但把它的边界框附加到了第 8a 行。右:LlamaParse 正确地将该值归因到第 8b 行。
使用 LlamaParse 解析表单
表单带来了一系列独特的挑战。正确解析它们需要一个专门构建的解决方案,能够表示表单的结构、检测边界框,并将每个框归因到正确的元素,从而可以追踪其来源。在 LlamaIndex,我们开发了一个表单解析解决方案,以极低的成本超越了 VLM。
要在你自己的表单上试用它,只需在解析请求中添加一个选项:
result=client.parsing.parse(
file_id=file.id,
tier="agentic",
processing_options={"forms":"enrich"},
expand=["forms"],
)
nodes=result.forms.pages[0]。forms[0]。json_ Enriched forms 运行在cost_effective、agentic和agentic_plus层级上,每个表单页面增加 10 个 credits(对于不包含表单的页面增加 0 个 credits)。
试用一下
Enriched Forms Beta 通过 API 向所有客户开放。一个选项即可开启表单处理,页面会以 JSON 树的形式返回(稍后会更详细地描述):
fromllama_cloudimportLlamaCloud
client=LlamaCloud()# 从环境中读取 LLAMA_CLOUD_API_KEY
file=client.files.create(file="w2-2024.pdf",purpose="parse")
result=client.parsing.parse(
file_id=file.id,
tier="agentic",
version="latest",
processing_options={"forms":"enrich"},# 运行表单处理
expand=["forms"],
)
forpageinresult.forms.pages:
forforminpage.forms:
fornodeinform.json_:# FormNode 树,按阅读顺序
print(node) W-2 的第一个字段返回结果如下——即打印出的值,以及它被读取的框:
{
"type":"field",
"field":"text",
"id":"1",
"label":"Wages, tips, other compensation",
"value":"230303.03",
"bbox":[{"x":356.6,"y":143.2,"w":96.4,"h":14.0}]
} 有关如何入门的更多信息,请尝试我们的某个 cookbook: