从 PDF 和手写表单里提取结构化数据

纸质问卷扫描件、手写记录、发票、附件里的表格——先定义好你要的字段,再让模型按这个结构填。一份发票折算下来只有 821 个 token。

2026/08/06约 6 分钟HiBridge 编译
译文本文是英文原文的中文翻译,原作者与原文链接如下。
原作者
Google
原文标题
Gemini API: Automated Invoice and Form Data Extraction with Gemini API & Pydantic
原发布平台
Google Gemini Cookbook
原发布日期
2026/08/06

译者说明:本文译自 Google Gemini Cookbook,原文为可运行的 Jupyter Notebook(Apache 2.0 许可)。 此处翻译说明性内容,代码原样保留。原文示例用的是发票和手写表单——后者对这一行更有意义。

本文演示如何把 PDF 文件转换成 Gemini API 能读的形式,并按你定义的结构提取数据。

原文准备了两份示例 PDF:一份基础发票,一份带手写内容的表单。

一、上传文件

Gemini 模型能处理图像和视频,可以用 base64 字符串,也可以用 files API。上传之后,你可以在调用里直接引用文件的 URI。

Python
invoice_pdf = client.files.upload(
    file="invoice.pdf",
    config={'display_name': 'invoice'}
)

File API 的限制:每个项目最多存 20 GB,单个文件上限 2 GB。文件保存 48 小时, 在这期间可以用你的 API key 访问,但不能下载。文件上传免费。

上传之后可以查它折算成多少 token。这不仅帮你理解正在处理的上下文规模,也帮你控制成本:

Python
file_size = client.models.count_tokens(model=model_id, contents=invoice_pdf)
print(f'File: {invoice_pdf.display_name} equals to {file_size.total_tokens} tokens')
# File: invoice equals to 821 tokens

一份发票 = 821 个 token。 这个数量级值得记住——它意味着几百份表单的处理成本是可以估算的。

二、用 Pydantic 定义你要的结构

结构化输出保证 Gemini 始终按预定义格式(比如 JSON Schema)生成响应。这意味着你对输出有更多控制,因为它保证返回一个符合你定义的合法 JSON 对象。

Gemini 支持三种定义 schema 的方式:

  • 一个 Python 类型,就像你在类型标注里写的那样
  • 一个 Pydantic BaseModel
  • 一个等价于 genai.types.Schema / Pydantic 模型的字典

先看一个纯文本的例子:

Python
from pydantic import BaseModel, Field

class Topic(BaseModel):
    name: str = Field(description="The name of the topic")

class Person(BaseModel):
    first_name: str = Field(description="The first name of the person")
    last_name: str = Field(description="The last name of the person")
    age: int = Field(description="The age of the person, if not provided please return 0")
    work_topics: list[Topic] = Field(description="The fields of interest of the person, if not provided please return an empty list")

这里有个值得抄走的写法Field(description=...) 里不只写字段是什么, 还写了「如果没提供该返回什么」(年龄返回 0,兴趣领域返回空列表)。 不写这一句,模型缺数据时会自己编一个。

三、把两者接起来

现在把 File API 和结构化输出组合,写一个通用方法——接收本地文件路径和一个 Pydantic 模型,返回结构化数据:

Python
def extract_structured_data(file_path: str, model: BaseModel):
    # 上传文件到 File API
    file = client.files.upload(
        file=file_path,
        config={'display_name': file_path.split('/')[-1].split('.')[0]}
    )
    # 用 Gemini API 生成结构化响应
    prompt = f"Extract the structured data from the following PDF file"
    response = client.models.generate_content(
        model=model_id,
        contents=[prompt, file],
        config={'response_mime_type': 'application/json', 'response_schema': model}
    )
    # 转换成 Pydantic 模型并返回
    return response.parsed

注意提示词有多短——「从下面这份 PDF 里提取结构化数据」,就这一句。结构的全部信息都在 schema 里,不在提示词里。

原文的最后一句说明很实用:

在我们的例子里,每份 PDF 彼此不同,所以要为每份 PDF 定义各自的 Pydantic 模型。 如果你有一批很相似的 PDF,而且要提取同样的信息,那么可以对它们全部使用同一个模型。


译后附记:这一行的四个直接场景

原文讲发票,但这套流程真正省时间的地方在别处:

场景 你定义的字段
纸质问卷 / 手写记录扫描件 题号、选项、开放题原文、字迹存疑标记
客户给的附件表格(PDF 里的表,复制出来全乱) 表格的每一列
竞品的 PDF 报告 数据点、口径说明、来源脚注、页码
报销与项目票据 日期、金额、供应商、项目号

手写那一栏是这套方法最值钱的地方。 手写问卷过去只能靠人录入,现在可以先让模型出一版,人只核对「字迹存疑」的那些。

三条从原文里抄走的做法:

① 每个字段都写一句说明,并说清「没有时返回什么」。 这是 Field(description=...) 的用法,也是防止它编数据的第一道闸。

② 加一个「置信度」或「存疑」字段。 schema 里多一列 uncertain: bool,让它自己标出哪些没看清。人只复核被标记的那些,这是投入产出比最高的一步。

③ 一批相似材料共用一个 schema。 定义一次,跑一百份,结果天然对齐成一张表。这正是「结构化」相对于「让它自由总结」的全部价值。

不写代码的话,用结构化输出那篇里的表头式提示词,能拿到大部分效果——只是没有「保证」。


出处

本文译自 Google Gemini Cookbook,原文 Automated Invoice and Form Data Extraction with Gemini API & Pydantic, 以 Apache License 2.0 发布。译文与译后附记由 HiBridge 撰写。