使用 Pydantic 生成结构化输出
将 Pydantic 模型定义为输出架构,通过新的结构化输出功能将其传递给 API,并自动将响应反序列化为带类型的 Python 对象。
使用 Pydantic 生成结构化输出 是 CoddyKit 上的免费 AI Engineering Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Engineering Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Engineering Academy 课程共包含 4 节课。
为什么将 Pydantic 用于 LLM 输出?
Pydantic 是一个 Python 数据验证库,使用 Python 类型提示定义数据 Schema。它擅长验证和反序列化外部来源的数据,而 LLM 输出正是您会遇到的最不可靠的外部数据来源之一。将 Pydantic Schema 与 OpenAI 的结构化输出结合使用,可以从 AI 模型获得类型安全、经过验证且自动反序列化的响应。
您不必编写 data = json.loads(response),再手动提取字段并转换类型,而是可以获得一个带完整类型的 Python 对象,其中每个字段都保证具有正确类型,同时 IDE 还可以提供自动补全和运行时验证。这是专业 AI 工程团队处理结构化提取的方式。
定义基本的 Pydantic Schema
Pydantic 模型是一个继承自 BaseModel 的类,其字段通过 Python 类型注解定义。字段类型可以是 Python 基本类型、用于嵌套的其他 Pydantic 模型,也可以是 typing 模块中用于列表、可选类型和联合类型的类型。
from pydantic import BaseModel, Field
from typing import Optional, List
from enum import Enum
class Sentiment(str, Enum):
positive = 'positive'
negative = 'negative'
neutral = 'neutral'
class ReviewAnalysis(BaseModel):
sentiment: Sentiment
confidence: float = Field(ge=0.0, le=1.0, description='Confidence score 0-1')
key_themes: List[str] = Field(description='Main topics mentioned in the review')
summary: str = Field(max_length=200, description='One-sentence summary')
product_name: Optional[str] = Field(default=None, description='Product mentioned, if any')
would_recommend: Optional[bool] = None
# Pydantic validates types and constraints at instantiation
example = ReviewAnalysis(
sentiment=Sentiment.positive,
confidence=0.95,
key_themes=['fast delivery', 'good quality'],
summary='Customer loves the product and quick shipping.',
product_name='Wireless Headphones',
would_recommend=True
)
print(example.model_dump_json(indent=2))将 Pydantic 与 OpenAI 结构化输出结合使用
将您的 Pydantic 模型类直接传递给 client.beta.chat.completions.parse() 的 response_format 参数。SDK 会自动将模型转换为 JSON Schema,将其发送到 API,然后把响应反序列化为带类型的 Python 对象。
import openai
from pydantic import BaseModel
from typing import List, Optional
from enum import Enum
client = openai.OpenAI()
class Sentiment(str, Enum):
positive = 'positive'
negative = 'negative'
neutral = 'neutral'
class ReviewAnalysis(BaseModel):
sentiment: Sentiment
confidence: float
key_themes: List[str]
summary: str
would_recommend: Optional[bool]
review_text = '''
I bought this laptop for my design work and I am blown away. It handles Photoshop
like a dream, the screen colors are beautiful, and it has not slowed down once in
three months. Battery life could be better but overall highly recommend!
'''
result = client.beta.chat.completions.parse(
model='gpt-4o-mini',
messages=[
{'role': 'system', 'content': 'Analyze the customer review and extract structured information.'},
{'role': 'user', 'content': review_text}
],
response_format=ReviewAnalysis
)
analysis = result.choices[0].message.parsed
print(f'Sentiment: {analysis.sentiment.value}')
print(f'Confidence: {analysis.confidence}')
print(f'Themes: {analysis.key_themes}')
print(f'Recommend: {analysis.would_recommend}')嵌套的 Pydantic 模型
Pydantic Schema 可以引用其他 Pydantic 模型,从而支持任意层级的嵌套结构化输出。这非常适合从合同、发票、简历和医疗记录等文档中提取层次化数据。
from pydantic import BaseModel
from typing import List, Optional
class Address(BaseModel):
street: Optional[str]
city: str
country: str
postal_code: Optional[str]
class ContactInfo(BaseModel):
email: Optional[str]
phone: Optional[str]
address: Optional[Address]
class Person(BaseModel):
full_name: str
age: Optional[int]
job_title: Optional[str]
contact: ContactInfo
skills: List[str]
# When you pass Person to response_format, the API generates:
# {
# "full_name": "...",
# "contact": {
# "email": "...",
# "address": { "city": "...", "country": "..." }
# },
# "skills": ["...", "..."]
# }
print('Nested model defined - pass to response_format for extraction')使用 Pydantic 验证器验证字段
Pydantic 验证器允许您在简单类型检查之外添加自定义验证逻辑。您可以验证置信度分数是否介于 0 和 1 之间、价格是否为非负数,或日期字符串是否采用正确格式。当 LLM 返回未通过验证的值时,Pydantic 会引发 ValidationError,您可以捕获并处理该异常。
from pydantic import BaseModel, Field, field_validator
from typing import Optional
import re
class ExtractedContact(BaseModel):
name: str
email: Optional[str] = None
phone: Optional[str] = None
confidence: float = Field(ge=0.0, le=1.0)
@field_validator('email')
@classmethod
def validate_email(cls, v):
if v is not None:
# Basic email format check
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', v):
raise ValueError(f'Invalid email format: {v}')
return v
@field_validator('phone')
@classmethod
def normalize_phone(cls, v):
if v is not None:
# Remove non-digit characters for normalization
digits = re.sub(r'[^0-9+]', '', v)
return digits
return v
try:
contact = ExtractedContact(name='Alice', email='not-an-email', confidence=0.9)
except Exception as e:
print(f'Validation error: {e}')提取对象列表
一种常见模式是从文档中提取同一实体的多个实例,例如发票中的所有行项目、会议记录中的所有行动项,或新闻文章中的所有实体。将您的模型包装在包含列表字段的容器模型中,即可简洁地处理这种情况。
import openai
from pydantic import BaseModel
from typing import List
client = openai.OpenAI()
class ActionItem(BaseModel):
task: str
assignee: str
due_date: str # or use datetime with proper parsing
priority: str # high / medium / low
class MeetingNotes(BaseModel):
meeting_title: str
action_items: List[ActionItem]
key_decisions: List[str]
meeting_transcript = '''
Q3 Planning Meeting - June 2025
Decision: Launch new feature in July.
Decision: Extend free trial to 30 days.
Action: Alice to finalize designs by June 30th - High priority.
Action: Bob to write API docs by July 5th - Medium priority.
Action: Carol to set up staging environment by June 28th - High priority.
'''
result = client.beta.chat.completions.parse(
model='gpt-4o-mini',
messages=[
{'role': 'system', 'content': 'Extract structured data from meeting notes.'},
{'role': 'user', 'content': meeting_transcript}
],
response_format=MeetingNotes
)
notes = result.choices[0].message.parsed
for item in notes.action_items:
print(f'[{item.priority.upper()}] {item.task} -> {item.assignee} by {item.due_date}')可选字段和默认值
现实世界的文档往往并不完整。简历可能没有列出电话号码,发票可能没有发票编号,产品评论也可能没有提及产品名称。请使用带有适当默认值的 Optional 字段设计 Pydantic 模型,以便优雅地处理缺失数据。
标注为 Optional[str] = None 的字段会同时告知 Pydantic 和 LLM:该字段可以不存在。对于无法提取的字段,模型会在 JSON 中返回 null,Pydantic 会将其反序列化为 Python 的 None,这样您就能在后续处理中妥善处理它,而不会出现 KeyError 异常。
将 Pydantic 模型转换为 JSON Schema
将您定义的 Pydantic 模型传递给 API 时,它会自动转换为 JSON Schema。您可以检查该 Schema,以准确了解 API 将强制执行哪些内容;当模型没有返回您期望的结构时,这对调试非常有帮助。
from pydantic import BaseModel, Field
from typing import List, Optional
import json
class ProductExtraction(BaseModel):
name: str = Field(description='Product name as mentioned in the text')
price_usd: Optional[float] = Field(default=None, description='Price in USD')
features: List[str] = Field(default_factory=list)
in_stock: bool = Field(description='Whether the product is currently available')
# See the JSON Schema that will be sent to the API
schema = ProductExtraction.model_json_schema()
print(json.dumps(schema, indent=2))
# This shows exactly what constraints the API will enforce处理提取失败
即使使用结构化输出,提取仍可能以两种方式失败:模型拒绝响应(返回拒答),或者文档确实不包含所请求的信息,因此模型为必填字段返回 null,从而触发 Pydantic 验证错误,因为必填字段不能为 null。
最安全的做法是为所有字段使用带默认值的 Optional,接受缺失数据的 null 值,并在提取后应用您自己的业务逻辑验证。这样可以将提取职责(从文本中获取数据)与验证职责(检查数据是否满足您的要求)分开。
import openai
from pydantic import BaseModel, ValidationError
from typing import Optional
client = openai.OpenAI()
class ContactExtraction(BaseModel):
name: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
try:
result = client.beta.chat.completions.parse(
model='gpt-4o-mini',
messages=[
{'role': 'system', 'content': 'Extract contact information.'},
{'role': 'user', 'content': 'I would like to discuss partnership opportunities.'}
],
response_format=ContactExtraction
)
msg = result.choices[0].message
if msg.refusal:
print('Refused:', msg.refusal)
else:
contact = msg.parsed
if not any([contact.name, contact.email, contact.phone]):
print('No contact information found in text')
else:
print(contact.model_dump())
except ValidationError as e:
print('Validation failed:', e)使用 Pydantic 和 instructor 库
instructor 库是一种流行的第三方软件包,它会修改 OpenAI 客户端,使其支持基于 Pydantic 的提取,并在验证失败时自动重试。如果模型返回的输出未通过您的 Pydantic 验证,instructor 会自动使用包含验证错误的提示词重试,从而让模型有机会自行纠正。
这对于批量提取流程尤其有用,因为您无法手动检查每个结果,同时又希望系统无需人工干预即可自行纠正。
# pip install instructor
import instructor
import openai
from pydantic import BaseModel, Field
from typing import Optional
# Patch the OpenAI client with instructor
client = instructor.from_openai(openai.OpenAI())
class ProductInfo(BaseModel):
name: str
price_usd: float = Field(gt=0, description='Price must be positive')
brand: Optional[str] = None
# instructor automatically retries if Pydantic validation fails
product = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{'role': 'user', 'content': 'The Sony WH-1000XM5 headphones cost $279.99 at Best Buy.'}
],
response_model=ProductInfo, # instructor-specific parameter
max_retries=3
)
print(f'{product.name}: ${product.price_usd} by {product.brand}')判别联合类型和动态 Schema
Pydantic 支持判别联合类型——这是一种结构取决于判别字段值的 Schema。当不同文档类型共享一个公共基础结构,但具有不同的附加字段时,这种方式非常有用。例如,费用报告可能包含航班收据(含出发地和到达地),也可能包含酒店收据(含入住和退房日期)。
通过将 Union 类型与 Literal 判别字段结合使用,您可以定义一个能够处理多种文档变体的提取 Schema,模型会根据文档内容选择正确的子类型。Pydantic 会根据判别字段的值,自动针对正确的子类型进行验证。
from pydantic import BaseModel
from typing import Union, Literal, Optional
class FlightExpense(BaseModel):
expense_type: Literal['flight']
airline: str
departure_city: str
arrival_city: str
amount_usd: float
class HotelExpense(BaseModel):
expense_type: Literal['hotel']
hotel_name: str
check_in: str
check_out: str
amount_usd: float
class MealExpense(BaseModel):
expense_type: Literal['meal']
restaurant: Optional[str]
amount_usd: float
class ExpenseReport(BaseModel):
submitter: str
expenses: list[Union[FlightExpense, HotelExpense, MealExpense]]
total_usd: float
print('Discriminated union schema - model selects correct subtype per item')快速检查
测试您对本课 AI 工程概念的理解。
课程回顾
本课中您学到了:Pydantic BaseModel 子类定义强类型提取 Schema,OpenAI 的结构化输出会在 API 层面强制执行这些 Schema;嵌套模型、Optional 字段和 List 类型可以处理复杂的现实文档结构;以及instructor 库为可靠的批量提取流程增加了验证失败时的自动重试功能。接下来,我们将为非结构化文本来源构建完整的信息提取流程。
用 AI 导师学习 Python — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 30
- 课程
- 120
常见问题解答
「使用 Pydantic 生成结构化输出」课时是免费的吗?
是的 — 「使用 Pydantic 生成结构化输出」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Engineering Academy 课程的其余内容,请升级到 CoddyKit PRO。 AI Engineering Academy 课程共包含 4 节课。
「使用 Pydantic 生成结构化输出」这节课中我会学到什么?
将 Pydantic 模型定义为输出架构,通过新的结构化输出功能将其传递给 API,并自动将响应反序列化为带类型的 Python 对象。 你通过在浏览器中直接运行的动手代码来练习 AI Engineering Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Engineering Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Engineering Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 Pydantic 生成结构化输出」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Engineering Academy 课中编写并运行代码吗?
能。每节 AI Engineering Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- JSON 模式与 response_format
- 使用 Pydantic 生成结构化输出
- 从非结构化文本中提取数据
- 验证并重试错误输出