agno 文本抽取(Text Extraction):用 Pydantic Schema 把自由文本变成结构化数据
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
导读
cookbook/data_labeling/_03_text_extraction/是 agno 数据标注系列中的「结构化抽取」工作流:它把一段自由文本(邮件签名、会议记录、非结构化用户输入)转换为由你定义 schema 的 Pydantic 对象,输出字段、嵌套层级与校验规则完全可控。读完本文,你将掌握三种实战形态——扁平单记录抽取、逐字段置信度包装、嵌套子对象列表抽取,并理解output_schema在 agno Agent 底层的工作机制,可直接复用到联系人入库、行动项提取、票据字段解析等生产标注场景。
为什么文本抽取是生产环境最常见的标注形态
在数据标注任务谱系里,分类(_01_text_classification/)解决「这段文本属于哪个标签」,span 标注(_04_text_span_labeling/)解决「实体出现在哪些字符位置」,而文本抽取解决的是第三种需求:把散落在文本中的字段值取出来,装进一个类型化的对象。如 README 所述,文本抽取「是最常见的标注形态」(the most common labeling shape in production today),因为业务系统几乎总是需要以行、以字段为单位消费数据——抽出来的结果可以直接落库、驱动下游流程,而不只是一个孤立的标签。
该工作流位于 agno 数据标注 Cookbook 的文本模态分组中,与图像抽取(_07_image_extraction/)、音频抽取(_12_audio_extraction/)、文档抽取(_16_document_extraction/)属于同一套「输入 → 类型化对象」模式,只是输入模态不同。整个cookbook/data_labeling/目录的布局约定是每个子目录一个主题、一个可端到端运行的basic.py加若干有任务含义的变体,本节即遵循该约定:basic.py是基线,with_confidence.py与nested.py是两种生产级变体。
典型适用场景
按 README 列出的场景,文本抽取适合以下需求:
- 从邮件签名抽取联系方式:姓名、邮箱、电话、公司、职位,直接喂给 CRM 或联系人系统;
- 从会议记录抽取行动项:谁负责、承诺做什么、何时截止,生成待办列表;
- 把非结构化用户输入提升为数据库行:客服工单、表单备注等自由文本,抽取字段后写入结构化存储。
选型判断:如果只需要一个单一标签,应使用_01_text_classification/;如果需要被提及实体的字符位置(NER 或 PII 检测),应使用_04_text_span_labeling/。文本抽取只关心「值」,不关心「位置」。
运行环境与准备
所有示例默认使用 Google Gemini 模型,需要设置GOOGLE_API_KEY环境变量。按 data_labeling 总 README 的说明,可在仓库根目录执行:
./scripts/demo_setup.sh source .venvs/demo/bin/activate然后按 README 中的命令依次运行三个示例:
python cookbook/data_labeling/_03_text_extraction/basic.py python cookbook/data_labeling/_03_text_extraction/with_confidence.py python cookbook/data_labeling/_03_text_extraction/nested.py三个脚本均使用google:gemini-3.5-flash模型,并通过rich.pretty.pprint打印输入与抽取结果,输出一目了然。TEST_LOG.md显示该目录已针对gemini-3.5-flash、agno 2.7.4 实测通过。
形态一:扁平单记录抽取(basic.py)
basic.py 演示最基础的形态:一段文本 → 一个扁平的类型化对象。核心结构分三步:定义 schema、写指令、创建 Agent。
1. 用 Pydantic 定义输出 schema
from typing import Optional from pydantic import BaseModel, Field class Contact(BaseModel): name: Optional[str] = Field(None, description="Full name as written") email: Optional[str] = Field(None, description="Email address") phone: Optional[str] = Field(None, description="Phone number, raw format") company: Optional[str] = Field(None, description="Company or organization") title: Optional[str] = Field(None, description="Job title")关键点:所有字段均为Optional,因为输入文本中某些字段可能缺失;Field的description会随 schema 一并传递给模型,作为抽取语义的补充提示(例如phone说明「保持原始格式」)。这就是 README 所说「输出是一个 Pydantic 对象,schema 由你控制」——schema 即契约,模型输出会被强制解析为该结构。
2. 用指令约束抽取行为
instructions = """\ Extract contact information from the input. Use exactly what the text shows - do not normalize or reformat. If a field is missing, leave it null. Do not guess. """这条指令是「忠实抽取」的关键:不做归一化、不改写格式、缺失字段留空、禁止猜测。它把 LLM 的自由生成收敛为机械的字段抄录,这正是标注/抽取类任务与普通对话的本质区别。
3. 创建 Agent 并运行
from agno.agent import Agent, RunOutput agent = Agent( model="google:gemini-3.5-flash", instructions=instructions, output_schema=Contact, ) samples = [ "Hi - Sarah Johnson, VP of Marketing at Acme Corp. " "sarah@acme.com / +1-555-0102.", "regards, Mike (engineering@startup.io)", ] for text in samples: run: RunOutput = agent.run(text) pprint({"input": text, "result": run.content})Agent.run()返回 RunOutput,其content即解析后的Contact实例。TEST_LOG.md的实测结果印证了指令的约束力:样本 1 五个字段全部原样抽取(name='Sarah Johnson'、email='sarah@acme.com'、phone='+1-555-0102'、company='Acme Corp.'、title='VP of Marketing');样本 2 只抽到 name 与 email,phone、company、title 按要求留为None。
形态二:逐字段置信度(with_confidence.py)
with_confidence.py 解决生产中的一个真实痛点:下游需要知道每个字段值有多可信,以便把低置信字段路由到人工审核队列或更强的模型。
共享的 ConfidentField 包装器
from typing import Literal, Optional from pydantic import BaseModel, Field class ConfidentField(BaseModel): value: Optional[str] = None confidence: Literal["high", "medium", "low"] = Field( ..., description="Confidence in the extracted value" ) class Contact(BaseModel): name: ConfidentField email: ConfidentField phone: ConfidentField company: ConfidentField title: ConfidentFieldConfidentField把「值」与「置信度」打包成可复用的结构:value保存原样文本,confidence用Literal["high", "medium", "low"]强制模型在三个等级中选择。Contact的每个字段都换成该包装器,抽取结果就从{"name": "Sarah Johnson"}变为{"name": {"value": "Sarah Johnson", "confidence": "high"}}。
置信度判定标准
指令中对三个等级给出了明确判定规则:
instructions = """\ Extract contact information from the input. For each field: - value: what the text shows; null if the field is missing - confidence: high if explicit and unambiguous; medium if implied or partially formatted; low if guessed or ambiguous Use exactly what the text shows. Do not normalize or paraphrase. """high:字段值在文本中明确出现且无歧义;medium:隐含给出或格式不完整(如只有昵称没有全名);low:需要猜测或文本本身含糊。
TEST_LOG.md的实测展示了该机制的实用价值:完整签名样本五个字段全部high且值原样;而"ping @mike on the eng team"这种残缺输入,模型给出 name=('mike', high)、title=('eng team', medium)、email/phone/company=(None, low)——虽然个别判定偏宽松(如把 'eng team' 当职位),但置信度分布足以支撑「低置信字段送人工复核」的路由逻辑。
形态三:嵌套子对象抽取(nested.py)
nested.py 演示列表与嵌套结构:从一段文本中抽取出多个结构相同的子对象。README 指出这正是行项目(line items)、会议出席者(attendees)、行动项(action items)、引文(citations)等场景的通用形状。
嵌套 schema 定义
from typing import List, Optional from pydantic import BaseModel, Field class ActionItem(BaseModel): owner: str = Field(..., description="Person responsible, as named in the meeting") description: str = Field(..., description="What they committed to do") due_date: Optional[str] = Field(None, description="ISO yyyy-mm-dd if mentioned") class Meeting(BaseModel): action_items: List[ActionItem]外层Meeting通过List[ActionItem]声明「多个子对象」,内层ActionItem定义单个行动项的字段:owner为必填(...),due_date可选且要求 ISO 日期格式。
指令中的抽取判定规则
instructions = """\ Extract action items from the meeting transcript. An action item is a commitment a named person made during the meeting. Only include items that are clearly assigned to a specific person; ignore vague group asks. If a due date is not mentioned, leave it null. """注意指令如何定义「行动项」的纳入标准:必须是指名道姓的人做出的承诺,模糊的群体请求(vague group asks)要忽略,截止日期未提及则留空。这正是 LLM 抽取任务的精要——用自然语言把业务规则编码进指令,模型按规则筛选而非全量抄录。
运行与实测结果
transcript = """\ Mike: I'll send out the updated roadmap by Friday. Sarah: Great. And I'll set up the kickoff with the design team next week. Jess: We should probably get budget approval at some point. Mike: Yeah. Let me draft the budget memo by end of next week so we can send it to finance. """ run: RunOutput = agent.run(transcript) pprint(run.content)TEST_LOG.md记录该示例实测通过:正确抽取出三条行动项——Mike('Send out the updated roadmap')、Sarah('Set up the kickoff with the design team')、Mike('Draft the budget memo');Jess 的 'budget approval at some point' 因无明确负责人被正确排除。该轮所有due_date均为None,因为文本中只有相对时间('by Friday'、'end of next week'),模型按指令「未提及则留空」没有自行推断成 ISO 日期——这说明「忠实抽取 + 不猜测」的指令在嵌套场景同样生效。
深入理解 output_schema 的底层机制
三个示例的共同核心是Agent(output_schema=...)。在 agno 源码 agent.py 中,该参数定义如下:
# Provide a response model to get the response in the implied format. # You can use a Pydantic model or a JSON fitting the provider's expected schema. output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None它既接受 Pydantic 模型类,也接受符合模型厂商期望 schema 的 JSON 字典,并在参数旁有一组配套开关:
parse_response: bool = True——为真时,模型响应会被转换为output_schema对应的类型;为假时以 JSON 字符串返回;structured_outputs: Optional[bool]——若模型支持(如 OpenAIChat),可启用厂商强制的结构化输出约束;use_json_mode: bool = False——为真时不再向模型传入 Pydantic schema,而是把 schema 的 JSON 描述写进系统消息,改用 JSON 模式返回。
从源码结构可以推断:output_schema走的是「schema 随请求下发 → 模型按 schema 产出 JSON → 客户端解析校验为 Pydantic 对象」的链路,Field(description=...)中的描述会实际参与提示构造,这也是为什么本目录所有示例都强调用description精确刻画每个字段的语义。对于需要二次精修的复杂抽取,还可以组合parser_model、output_model等参数(同为 agent.py 中定义的 Agent 参数),此处不展开。
三种形态如何选型
| 形态 | 输入 → 输出 | 适用场景 | 代表文件 |
|---|---|---|---|
| 扁平单记录 | 文本 → 一个扁平 Pydantic 对象 | 邮件签名联系人、表单字段入库 | basic.py |
| 逐字段置信度 | 文本 → 每字段带 value + high/medium/low | 需要把低置信字段路由给人工或更强模型 | with_confidence.py |
| 嵌套子对象列表 | 文本 → 包含 List[子对象] 的 Pydantic 对象 | 行动项、行项目、出席者、引文 | nested.py |
三条共同实践法则贯穿始终:schema 即契约(先定义 Pydantic 模型,模型输出被强制解析);指令定义抽取规则(忠实抄录、不归一化、缺失留空、不猜测);可选字段必须 Optional(缺失时模型返回None而非编造)。这套模式可直接移植到文档抽取、票据字段解析等生产标注管线中,是 agno 数据标注系列里复用价值最高的一环。
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考