从 PDF 和手写表单里提取结构化数据
纸质问卷扫描件、手写记录、发票、附件里的表格——先定义好你要的字段,再让模型按这个结构填。一份发票折算下来只有 821 个 token。
- 原作者
- 原文标题
- Gemini API: Automated Invoice and Form Data Extraction with Gemini API & Pydantic
- 原发布平台
- Google Gemini Cookbook
- 原发布日期
- 2025/02/06
译者说明:本文译自 Google Gemini Cookbook,原文为可运行的 Jupyter Notebook(Apache 2.0 许可)。 此处翻译说明性内容,代码原样保留。原文示例用的是发票和手写表单——后者对这一行更有意义。
关于日期:Cookbook 是持续更新的仓库,页面上没有发布日期。本文的日期取自该 notebook 在 GitHub 上的首次提交日期,也就是这份内容第一次被生产出来的时间。
本文演示如何把 PDF 文件转换成 Gemini API 能读的形式,并按你定义的结构提取数据。
原文准备了两份示例 PDF:一份基础发票,一份带手写内容的表单。
一、上传文件
Gemini 模型能处理图像和视频,可以用 base64 字符串,也可以用 files API。上传之后,你可以在调用里直接引用文件的 URI。
invoice_pdf = client.files.upload(
file="invoice.pdf",
config={'display_name': 'invoice'}
)
File API 的限制:每个项目最多存 20 GB,单个文件上限 2 GB。文件保存 48 小时, 在这期间可以用你的 API key 访问,但不能下载。文件上传免费。
上传之后可以查它折算成多少 token。这不仅帮你理解正在处理的上下文规模,也帮你控制成本:
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 模型的字典
先看一个纯文本的例子:
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 模型,返回结构化数据:
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 撰写。