agno 多页 PDF 文档结构化提取实战:从 RecipeBook 到发票、合同与报表的 Pydantic 数据管线
2026/9/10 13:03:52 网站建设 项目流程

agno 多页 PDF 文档结构化提取实战:从 RecipeBook 到发票、合同与报表的 Pydantic 数据管线

【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno

多页 PDF(发票、合同、化验单、对账单)能否直接变成类型安全的 Pydantic 对象?agno 在cookbook/data_labeling/_16_document_extraction/中给出了完整答案:只需定义 Pydantic schema,把 PDF 作为File输入交给带output_schema的 Agent,即可用一次模型调用完成整份文档的字段抽取、嵌套明细行(line items)抽取和逐字段置信度标注。本文以公开的泰国食谱 PDF 为样例,逐文件拆解三个递进示例,并结合 agno 源码说明Fileoutput_schemaRunOutput的底层工作机制,让你能直接迁移到发票、合同、报表等生产标注场景。

这一节要解决什么问题

人工标注结构化文档字段成本高、易漏项;而通用 LLM 直接抽取又缺乏输出约束与可验证的类型结构。_16_document_extraction/把「多页 PDF → 类型化对象」变成一条可复用的标注基元(primitive):用output_schema把模型输出强制对齐到 Pydantic 模型,字段缺失时置空而非臆造,嵌套子对象用List[子模型]表达。它在生产标注中的典型对应是:

  • 把发票 / 收据 / 对账单字段抽成数据库行;
  • 抽取合同条款进入人工复核队列;
  • 对 PDF 语料库构建结构化索引。

如果只需要文档类型标签(是发票还是合同),应改用_15_document_classification/;若要在该基元之上叠加多 Agent 质检流水线(标注者—复核者—裁决者),见_18_quality_review/

运行方式与前置条件

从仓库根目录创建并激活演示虚拟环境(详见cookbook/data_labeling/README.md):

./scripts/demo_setup.sh source .venvs/demo/bin/activate

随后直接运行三个示例:

python cookbook/data_labeling/_16_document_extraction/basic.py python cookbook/data_labeling/_16_document_extraction/with_line_items.py python cookbook/data_labeling/_16_document_extraction/with_confidence.py

前置条件:需要GOOGLE_API_KEY。data_labeling 整个 cookbook 默认使用 Gemini 3.5 Flash(原生多模态,可直接接收 PDF 输入);README 中的参数表确认该 key 是每个 cookbook 的默认配置(ANTHROPIC_API_KEYOPENAI_API_KEY等仅在_18_quality_review_05_text_pairwise_preference的陪审团示例中使用,本目录无需设置)。

示例一:basic.py —— 顶层文档元数据抽取

basic.py演示最小可运行形态:把整份 PDF 的文档级元数据抽成类型化对象。核心由四部分组成:

1. 定义 Pydantic schema(输出契约)

from typing import Optional from pydantic import BaseModel, Field class RecipeBook(BaseModel): title: Optional[str] = Field(None, description="Book or document title") cuisine: Optional[str] = Field(None, description="Cuisine or culinary tradition") language: Optional[str] = Field(None, description="Language of the document") recipe_count: Optional[int] = Field( None, description="Number of distinct recipes in the document" )

所有字段声明为Optional并给None默认值——字段缺失时输出 null 而非幻觉值,这是文档抽取的第一原则。Field(description=...)里的描述会作为对模型的约束提示,务必写清楚字段语义。

2. 指令(instructions)约束抽取行为

instructions = """\ Extract document-level metadata from the attached PDF. Use exactly what the document shows. If a field is not present, leave it null. """

关键语义:严格依据文档内容、禁止臆造、缺失置空。

3. 创建 Agent 并绑定输出 schema

from agno.agent import Agent, RunOutput from agno.media import File agent = Agent( model="google:gemini-3.5-flash", instructions=instructions, output_schema=RecipeBook, )

output_schema是结构化输出的核心参数。从 agno 源码看(libs/agno/agno/agent/agent.py),它接受Type[BaseModel]Dict[str, Any],Agent 会把 Pydantic schema 注入模型调用,强制返回符合该结构的 JSON,并将结果反序列化为 Pydantic 实例。与之相关的还有output_model(指定解析模型)、structured_outputs(是否强制结构化输出)等参数,本示例走的是最直接的一条路径。

4. 运行:通过File传入 PDF

if __name__ == "__main__": url = "https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf" run: RunOutput = agent.run("Extract document metadata.", files=[File(url=url)]) pprint({"url": url, "result": run.content})

File类(定义于libs/agno/agno/media/media.py)支持四种内容来源:urlfilepathcontent(原始字节)以及external(模型提供方原生文件对象),校验器要求至少提供其一。url指向远程文件时,get_content_bytes()内部经 HTTP 拉取并读取 MIME 类型。run.contentRunOutput(见libs/agno/agno/run/agent.py)中结构化输出后的对象,配合rich.pretty.pprint直接打印。

示例二:with_line_items.py —— 嵌套明细行抽取

生产中最常见的抽取形态不是平铺字段,而是「文档元数据 + 一组同类子对象」:发票行项目、账单流水、合同条款、菜谱步骤皆属此列。with_line_items.py用嵌套 Pydantic 模型表达这一形状:

from typing import List, Optional from pydantic import BaseModel, Field class Recipe(BaseModel): name: str = Field(..., description="Recipe name as printed") course: Optional[str] = Field(None, description="Appetizer, main, dessert, etc.") prep_time_minutes: Optional[int] = None class RecipeBook(BaseModel): title: Optional[str] = None cuisine: Optional[str] = None recipes: List[Recipe] = Field(default_factory=list)

关键设计点:

  • 子对象Recipename必填(Field(...):明细行的核心标识必须存在,其余属性可空;
  • 父对象用List[Recipe] = Field(default_factory=list):缺失时得到空列表而非报错,避免因一份文档没有可抽取条目而崩溃;
  • 指令中明确「不要发明菜谱、不要改写名称」:对抗幻觉是明细行抽取的重点,因为行数多、逐条核对成本高。

运行部分与 basic.py 几乎一致,只是提示词改为抽取「书籍元数据 + 每一条菜谱」。当迁移到发票场景时,Recipe等价于LineItem(description, quantity, unit_price, amount)RecipeBook等价于Invoice(vendor, invoice_number, issue_date, line_items)

示例三:with_confidence.py —— 逐字段置信度标注

当输入 PDF 质量参差(扫描件、传真件、多语言混排)时,把低置信字段路由给人工复核是生产标注的刚需。with_confidence.py展示了标准做法:把每个字段包进一个「值 + 置信度」容器。

from typing import Literal, Optional from pydantic import BaseModel Confidence = Literal["high", "medium", "low"] class ConfidentField(BaseModel): value: Optional[str] = None confidence: Confidence class RecipeBook(BaseModel): title: ConfidentField cuisine: ConfidentField language: ConfidentField # Held as a string so per-field confidence applies cleanly to the count. recipe_count: ConfidentField

设计要点:

  • Confidence = Literal["high", "medium", "low"]用字面量类型把置信度限定为三档,模型不能输出档位之外的值;
  • 字段数量为整数却仍用ConfidentField(值保持字符串)承载,是因为逐字段置信度标注需要统一作用到「计数」上;
  • 指令中给出了三档的判定标准:
- high - explicit in the document - medium - inferred from structure or context - low - guessed, partly obscured, or ambiguous

并要求「Be conservative. Mark unsure fields low.」——即不确定时一律标低置信,保证下游筛选可靠。

该模式与_18_quality_review/的质检流水线天然衔接:confidence == "low"的字段可以直接路由到复核队列。

测试验证与运行效果

TEST_LOG.md记录了 2026-07-18 针对gemini-3.5-flash、agno 2.7.4 的实测结果,三个脚本全部 PASS:

脚本实测输出单次调用规模
basic.pyRecipeBook(title='Thai SELECT Cookbook', cuisine='Thai', language='English', recipe_count=10)7449 tokens,约 3.9s
with_confidence.py四个字段全部以high置信度填充(title、cuisine、language、recipe_count)约 6.0s
with_line_items.py抽取 10 条菜谱,如Pad Thai Goong Sod(prep 15)、Tom Kha Gai(prep 10)、Gluai Buat Chi(prep 10);course字段全为 null约 9.4s

注意with_line_items.pycourse全为 null:测试日志指出这与文档本身未标注菜系分类一致——模型严格遵守了「文档有什么就抽取什么」的约束,而不是从菜名去推断,这正是指令设计奏效的证据。

迁移到生产文档:把 RecipeBook 换成你的领域 Schema

三个示例共用同一份公开 PDF(https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf)以保证开箱即跑。生产替换只需两步(README 亦说明了这一点):

  1. 换输入:把File(url=...)换成File(filepath="/path/to/invoice.pdf")(或 base64content),指向你自己的发票 / 合同 / 报表 PDF;
  2. 换 Schema:把RecipeBook换成领域模型,例如:
class LineItem(BaseModel): description: str = Field(..., description="Line item description as printed") quantity: Optional[float] = None unit_price: Optional[float] = None amount: Optional[float] = None class Invoice(BaseModel): vendor: Optional[str] = None invoice_number: Optional[str] = None issue_date: Optional[str] = None line_items: List[LineItem] = Field(default_factory=list)

指令与output_schema的组合保持不变。若文档质量不稳定,叠加with_confidence.pyConfidentField包装并路由低置信字段到人工复核,即可形成一套可直接上线的 PDF 字段标注管线。

关联与扩展

  • 需要文档类型标签(而非字段抽取)时,参见_15_document_classification/
  • 在该抽取基元之上做多 Agent 质量复核(labeler → reviewer → adjudicator),参见_18_quality_review/
  • 更多抽取、分类、排序、跨度标注示例,参见cookbook/data_labeling/README.md的完整工作流索引;
  • File支持的全部内容来源与 MIME 类型约束见libs/agno/agno/media/media.pyoutput_schema参数说明见libs/agno/agno/agent/agent.py,结构化返回值结构见libs/agno/agno/run/agent.py

【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询