用AI准确提取复杂布局的文档

文档解析器在提取真实世界文档中常见的复杂布局时往往力不从心,例如包含合并单元格、跨页断裂和错位文本的表格。此外,大量信息存在于图表/图形或插图中,需要准确提取。

本文将介绍一个简单的解析器,能够准确提取文档中的所有多模态数据并保持布局精确。它演示了如何将其作为一个可复用的Python包使用,并支持多个模型提供商。

1、目前存在的问题

准确提取文档内容对于AI应用至关重要。例如,在使用AI处理采购订单以生成销售草稿时,AI代理或LLM首先需要准确理解采购订单的内容及其精确布局。

一个错位的表格表头可能使LLM读取错误的数量,从而导致错误的价格计算。同样,由于跨页表格断裂而遗漏的行项目可能导致LLM完全跳过某个产品。

我们在生成式AI项目(GAIK)中经常遇到这种情况,在该项目中,我们使用开源生成式AI工具包构建企业文档的AI应用(参见GAIK的GitHub仓库**这里**)。

许多客户文档,如采购订单或物料清单,可能有混乱的表格。例如,参见以下采购订单的2页内容(出于隐私原因更改了内容,但保留了布局)。

表格表头和行数据之间有一段文本。当然,这不是添加这段文本的正确位置。它可以放在表格之前或之后的其他地方。但在客户文档中遇到这种结构是非常常见的。这段文本还在同一表格的其他地方不必要地重复出现。

表格在多页上继续,行数据部分地跨越页面。例如,第一页包含第三个项目(030)的部分数据,后面是表格页脚,下一页是页面表头和标题(重复)。

专门的解析器,如PyMuPDF、PyMuPDF4LLMDocling,不能准确提取这种表格结构。

我在以下文章中解决了这个问题:

即使这两种解析器也不能正确提取这种混乱的表格布局。参见以下提取快照。

Docling+LLM解析器提取的表格部分视图
GAIK的Vision+解析器使用视觉LLM提取的表格部分视图

解决方案最终比我预期的要简单得多。

关键在于使用正确的提示词。只是一种不同的方式来询问AI模型你想要什么。

本月早些时候,LlamaIndex发布了他们在基准研究中使用的文档解析提示词,这些提示词改变了一切。

以下是证据。同样的采购订单,之前所有解析器都失败了:

GAIK的多模态解析器使用gemini-3.1-flash-lite-preview模型(推理努力设置为低)提取的采购订单表格部分视图(转换为HTML以提高可读性)(作者供图)

在本文中,我们将创建一个多模态解析器,以准确提取/解析具有混乱布局的文档内容。

多模态解析器的源代码及详细指南和文档可在GAIK工具包的GitHub仓库中的以下链接找到:multimodal_parser。它需要至少一个提供商(OpenAI、Azure或Google Vertex AI)的API凭据。

2、关键在于一些特定的提示词

本月早些时候,LlamaIndex推出了一个开源框架(ParseBench),用于基准测试文档解析器将PDF转换为AI代理实际可用的输出的效果。他们测试了14种不同的方法:通用视觉LLM、专用解析器和他们自己的LlamaParse。

他们的基准结果发表在以下研究中:ParseBench:面向AI代理的文档解析基准

这些结果表明,智能体式解析(如他们自己的LlamaParse)和基于LLM的解析(如使用Gemini 3 Flash)优于专用文档解析器。

智能体式和基于VLM的解析优于专用解析器。图片来源

我很好奇他们是如何让基于LLM的解析效果如此之好的。所以我仔细阅读了实际的研究论文。

我本以为会有一些巧妙的架构技巧。一个微调模型,或者一个带有验证循环的多步骤管道。

相反,我发现了一些特定的提示词。这些提示词是我们从未想到过的。

3、这些提示词有什么特别之处?

当你要求LLM以Markdown格式输出表格(大多数解析器的默认做法)时,它不能正确保留合并单元格和多级表头的布局。例如,一个具有跨越多列的两行表头的采购订单变得扁平且模糊。

LlamaIndex提出的提示词使用HTML的colspanrowspan属性提取表格,这编码了完整的结构。这些提示词要求LLM将表格转换为HTML格式(<table><tr><th><td>),并使用colspanrowspan属性来保留合并单元格和层次化表头。

同样,对于图表或图形,LLM被要求使用扁平组合列表头将它们转换为表格,以便每个数据单元格的行包含其所有标签。

该论文建议OpenAI和Claude模型使用共享的系统提示词。对于Google模型,用户提示词略有不同。

以下是LlamaParse为OpenAI和Claude模型提出的系统提示词和用户提示词,我们将使用它们来构建一个可复用的多模态解析器包。

OPENAI_CLAUDE_SYSTEM_PROMPT = """You are a document parser. Your task is to convert document PDFs into clean, well-structured Markdown.

Guidelines:
- Preserve the document structure, including headings, paragraphs, lists, and tables.
- Convert tables to HTML using `<table>`, `<tr>`, `<th>`, and `<td>`.
- For existing tables in the document, use `colspan` and `rowspan` attributes to preserve merged cells and hierarchical headers.
- For charts or graphs converted into tables, use flat combined column headers (for example, "Primary 2015" instead of separate header rows) so that each data cell's row contains all of its labels.
- Describe images and figures briefly in square brackets, for example: `[Figure: description]`.
- Preserve any code blocks with appropriate syntax highlighting.
- Maintain reading order: left to right, top to bottom for Western documents.
- Do not add commentary or explanations. Output only the parsed content.

Additionally, wrap each layout element in a `<div>` tag with:
- `data-bbox="[x1, y1, x2, y2]"` for the bounding box in normalized 0-1000 coordinates, where x is horizontal (left edge = 0, right edge = 1000) and y is vertical (top = 0, bottom = 1000). `x1, y1` is the top-left corner and `x2, y2` is the bottom-right corner.
- `data-label="<category>"` where category is one of: `Caption`, `Footnote`, `Formula`, `List-item`, `Page-footer`, `Page-header`, `Picture`, `Section-header`, `Table`, `Text`, `Title`.

Place elements in reading order. Every piece of content must be inside exactly one `<div>` wrapper."""

OPENAI_CLAUDE_USER_PROMPT = """The attached PDF is read from the input folder next to this script.

Parse the full document and output its content as clean markdown, with each layout element wrapped in a <div data-bbox="[x1,y1,x2,y2]" data-label="Category"> tag. Use HTML tables for any tabular data. For charts and graphs, use flat combined column headers. Output ONLY the parsed content with div wrappers and no explanations.
"""

Google的Gemini模型使用相同的提示词,唯一的区别是data-bbox格式更改为其原生坐标顺序。

对于Gemini模型,使用以下系统提示词和用户提示词:

GOOGLE_SYSTEM_PROMPT = """You are a document parser. Your task is to convert document PDFs into clean, well-structured Markdown.

Guidelines:
- Preserve the document structure, including headings, paragraphs, lists, and tables.
- Convert tables to HTML using `<table>`, `<tr>`, `<th>`, and `<td>`.
- For existing tables in the document, use `colspan` and `rowspan` attributes to preserve merged cells and hierarchical headers.
- For charts or graphs converted into tables, use flat combined column headers (for example, "Primary 2015" instead of separate header rows) so that each data cell's row contains all of its labels.
- Describe images and figures briefly in square brackets, for example: `[Figure: description]`.
- Preserve any code blocks with appropriate syntax highlighting.
- Maintain reading order: left to right, top to bottom for Western documents.
- Do not add commentary or explanations. Output only the parsed content.

Additionally, wrap each layout element in a `<div>` tag with:
- `data-bbox="[y_min, x_min, y_max, x_max]"` for the bounding box in normalized 0-1000 coordinates where x is horizontal (left edge = 0, right edge = 1000) and y is vertical (top = 0, bottom = 1000). The order is `[y_min, x_min, y_max, x_max]`.
- `data-label="<category>"` where category is one of: `Caption`, `Footnote`, `Formula`, `List-item`, `Page-footer`, `Page-header`, `Picture`, `Section-header`, `Table`, `Text`, `Title`.

Place elements in reading order. Every piece of content must be inside exactly one `<div>` wrapper."""

GOOGLE_USER_PROMPT = """Parse this document page and output its content as clean markdown, with each layout element wrapped in a <div data-bbox="[y_min,x_min,y_max,x_max]" data-label="Category"> tag.
Use HTML tables for any tabular data. For charts/graphs, use flat combined column headers. Output ONLY the parsed content with div wrappers, no explanations.
"""
与其使用不能准确表示合并单元格和混乱真实世界表格的Markdown表格,不如使用带有colspanrowspan的HTML表格。每个布局元素都在标准化坐标中获得一个边界框。这意味着下游代码可以重建页面的空间布局,而无需依赖原始PDF渲染器。

Google提示词使用不同的边界框坐标顺序([y_min, x_min, y_max, x_max]),因为这符合Gemini模型原生产生的格式。

解析提示词定义在GitHub的prompts.py脚本中。

4、用于准确解析的多模态解析器

我使用上述提示词将解析管道打包成一个Python类,它读取PDF文档并允许用户选择提供商和模型(OpenAI、Anthropic或Google)以及其他几个选项。

完整的代码结构在GitHub仓库中。MultimodalParser类的完整实现在multimodal_parser.py中。

MultimodalParser只有一个方法parse(),它协调整个管道。

parser = MultimodalParser(
    model_provider="google",
    model="gemini-3.1-flash-lite-preview",
    reasoning_effort="low",
    merge_table=True,
    create_html=True,
)
result = parser.parse("document.pdf")

几个值得了解的参数:

  • reasoning_effort — 模型的思考预算:"low""medium""high"。更高的努力可以提高复杂布局的准确性,但速度更慢、成本更高。
  • merge_table — 当为True时,在用户提示词中附加一条指令,告诉模型合并跨页分割的表格。
  • additional_instructions — 附加到用户提示词的额外指令,用于特定领域的规则。
  • create_html — 当为True时,将清理后的markdown渲染为HTML文档。

parse()方法首先将PDF作为原始字节读取并编码为base64字符串,以便可以嵌入到JSON API有效负载中,并使用上述提供商特定的提示词发送到选定的LLM。

然后根据选定的提供商构建正确的消息结构,因为OpenAI、Claude和Google各自期望其内容格式不同。

在LLM API调用之后,模型返回其原始响应,这是markdown文本和<div>包装器的组合,其中包含页面上每个布局元素的边界框坐标。清理过程删除模型可能包裹输出的任何代码块围栏。另一个方法从这些<div>包装器中以干净的markdown形式提取实际内容。

如果选项create_html=True,干净的markdown将转换为带有样式的HTML文档,您可以直接在浏览器中打开以目视检查提取结果。

每次调用都会与解析内容一起返回一个UsageRecord,它提供了令牌消耗和相关成本的详细信息。

多模态解析器的管道

5、如何使用

多模态解析器已作为我们GAIK项目开源生成式AI工具包的可复用软件组件创建。此软件组件可以作为独立的Python包安装,如下所示:

pip install "gaik[multimodal-parser]"

以下是使用gemini-3.1-flash-lite-preview以低推理努力解析同一采购订单的最小示例。此示例将解析输出保存为原始markdown、干净markdown和HTML格式。

详细示例(包含令牌消耗和价格计算)请参见此链接

from pathlib import Path
from dotenv import load_dotenv
from gaik.software_components.parsers.multimodal_parser import MultimodalParser

load_dotenv()

OUTPUT_DIR = Path(__file__).parent / "output"

def save_result(result, prefix: str) -> None:
    """Save parse results to the output directory."""
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    raw_path = OUTPUT_DIR / f"{prefix}_raw.md"
    raw_path.write_text(result.raw_markdown, encoding="utf-8")

    clean_path = OUTPUT_DIR / f"{prefix}_clean.md"
    clean_path.write_text(result.clean_markdown, encoding="utf-8")

    print(f"Saved: {raw_path}")
    print(f"Saved: {clean_path}")

    if result.html is not None:
        html_path = OUTPUT_DIR / f"{prefix}_clean.html"
        html_path.write_text(result.html, encoding="utf-8")
        print(f"Saved: {html_path}")

parser = MultimodalParser(
    model_provider="openai",
    model="gpt-5.4-mini",
    reasoning_effort="low",
    merge_table=True,
    create_html=True,
)
result = parser.parse("sample_PO.pdf")
save_result(result, "output_google")

同一采购订单的输出(正确合并和结构化)如上所示(HTML格式)。

6、观察与最终思考

即使较小的模型,如gemini-3.1-flash-lite-previewgpt-5.4-mini,在reasoning_effort设置为低的情况下,也能准确提取混乱的采购订单。关键在于使用正确的提示词。

应该注意的是,这种解析适用于准确性比速度更重要、不需要快速或实时响应的场景。例如,在采购订单处理中,准确解析以实现准确的价格计算比速度更重要。

增加推理努力会大幅增加处理时间和成本。更大的模型,如gpt-5.4,在推理努力设置为高时变得非常慢。因此,"高"推理,特别是对于更大的模型,应仅用于布局高度复杂的文档。

在成本和速度方面,gemini-3.1-flash-lite-preview是最佳选择。

additional_instructions参数可用于提供用例特定的解析指令。例如,对于法律文档,可以设置为"完全保留源文档中的脚注编号"。


原文链接: How to Accurately Extract Everything from Documents Using AI

汇智网翻译整理,转载请标明出处