Saídas estruturadas com Pydantic
Defina modelos Pydantic como seu esquema de saída, passe-os à API por meio do novo recurso de saídas estruturadas e desserialize automaticamente as respostas em objetos Python tipados.
Saídas estruturadas com Pydantic é uma aula grátis de AI Engineering Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Engineering Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Engineering Academy inclui 4 aulas no total.
Por que usar Pydantic para saídas de LLM?
Pydantic é uma biblioteca Python de validação de dados que define esquemas de dados usando anotações de tipo do Python. Ela é excelente para validar e desserializar dados de fontes externas — e as saídas de LLM são uma das fontes externas menos confiáveis que você encontrará. Combinar esquemas Pydantic com as saídas estruturadas da OpenAI fornece respostas de um modelo de IA com tipos seguros, validadas e desserializadas automaticamente.
Em vez de escrever data = json.loads(response) seguido de extração manual de campos e conversão de tipos, você obtém um objeto Python totalmente tipado, no qual cada campo tem seu tipo correto garantido, além de preenchimento automático no seu IDE e validação em tempo de execução. É assim que equipes profissionais de engenharia de IA lidam com extração estruturada.
Definindo um esquema Pydantic básico
Um modelo Pydantic é uma classe que herda de BaseModel, com campos definidos usando anotações de tipo do Python. Os tipos dos campos podem ser primitivas do Python, outros modelos Pydantic para aninhamento ou tipos do módulo typing para listas, opcionais e uniões.
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 com saídas estruturadas da OpenAI
Passe diretamente a classe do seu modelo Pydantic para o parâmetro response_format de client.beta.chat.completions.parse(). O SDK converte automaticamente o modelo em esquema JSON, envia-o para a API e desserializa a resposta de volta em um objeto Python tipado.
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}')Modelos Pydantic aninhados
Os esquemas Pydantic podem fazer referência a outros modelos Pydantic, permitindo saídas estruturadas com aninhamento arbitrário. Isso é ideal para extrair dados hierárquicos de documentos como contratos, faturas, currículos e prontuários médicos.
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')Validação de campos com validadores Pydantic
Os validadores Pydantic permitem adicionar lógica de validação personalizada além da simples verificação de tipos. Você pode validar se uma pontuação de confiança está entre 0 e 1, se um preço não é negativo ou se uma cadeia de caracteres de data está no formato correto. Quando o LLM retorna um valor que falha na validação, o Pydantic gera um ValidationError que você pode capturar e tratar.
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}')Extraindo listas de objetos
Um padrão comum é extrair várias instâncias da mesma entidade de um documento — todos os itens de linha de uma fatura, todos os itens de ação de uma transcrição de reunião ou todas as entidades de um artigo de notícias. Envolva o seu modelo em um modelo contêiner com um campo de lista para lidar com esse caso de forma simples.
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}')Campos opcionais e valores padrão
Documentos do mundo real são incompletos. Um currículo pode não listar um número de telefone; uma fatura pode não ter um número de fatura; uma avaliação de produto pode não mencionar o nome do produto. Projete os seus modelos Pydantic para lidar normalmente com dados ausentes usando campos Optional com valores padrão apropriados.
Um campo anotado como Optional[str] = None informa tanto ao Pydantic quanto ao LLM que esse campo pode estar ausente. O modelo retornará null no JSON para os campos que não conseguir extrair, e o Pydantic desserializará esse valor como None do Python, permitindo que você o trate adequadamente nas etapas seguintes, sem exceções KeyError.
Convertendo modelos Pydantic em esquema JSON
O modelo Pydantic que você define é convertido automaticamente em um esquema JSON quando passado para a API. Você pode inspecionar esse esquema para entender exatamente o que a API imporá, o que é útil para depurar casos em que o modelo não retorna a estrutura esperada.
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 enforceTratando falhas de extração
Mesmo com saídas estruturadas, a extração pode falhar de duas maneiras: o modelo se recusa a responder (retorna uma recusa) ou o documento realmente não contém as informações solicitadas, fazendo com que o modelo retorne valores nulos para campos obrigatórios — o que aciona um erro de validação do Pydantic, pois campos obrigatórios não podem ser nulos.
A abordagem mais segura é tornar todos os campos Optional com valores padrão, aceitar valores nulos para dados ausentes e aplicar a sua própria validação da lógica de negócio após a extração. Isso separa a responsabilidade da extração (obter dados do texto) da responsabilidade da validação (verificar se os dados atendem aos seus requisitos).
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)Usando Pydantic com a biblioteca instructor
A biblioteca instructor é um pacote popular de terceiros que modifica o cliente da OpenAI para oferecer suporte à extração baseada em Pydantic, com nova tentativa automática em caso de falha na validação. Se o modelo retornar uma saída que falhe na sua validação Pydantic, a biblioteca instructor tentará novamente automaticamente usando um prompt que inclui o erro de validação, dando ao modelo a oportunidade de se corrigir.
Isso é especialmente útil em pipelines de extração em lote, nos quais não é possível revisar manualmente cada resultado e você deseja que o sistema se corrija sem intervenção humana.
# 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}')Uniões discriminadas e esquemas dinâmicos
O Pydantic oferece suporte a uniões discriminadas — um esquema cuja estrutura depende do valor de um campo discriminador. Isso é útil quando diferentes tipos de documentos compartilham uma base comum, mas têm campos adicionais diferentes. Por exemplo, um relatório de despesas pode conter um recibo de voo (com origem e destino) ou um recibo de hotel (com datas de check-in e check-out).
Usando tipos Union com um campo discriminador Literal, você pode definir um único esquema de extração que lida com várias variantes de documentos, com o modelo selecionando o subtipo correto com base no conteúdo do documento. O Pydantic valida automaticamente o subtipo correto com base no valor do discriminador.
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')Verificação rápida
Teste a sua compreensão dos conceitos de Engenharia de IA desta lição.
Recapitulação da lição
Nesta lição, você aprendeu que: subclasses de Pydantic BaseModel definem esquemas de extração fortemente tipados, que as saídas estruturadas da OpenAI impõem no nível da API; modelos aninhados, campos Optional e tipos List lidam com estruturas complexas de documentos do mundo real; e a biblioteca instructor adiciona novas tentativas automáticas após falhas de validação para obter pipelines robustos de extração em lote. A seguir, construiremos um pipeline completo de extração de informações para fontes de texto não estruturadas.
Aprenda Python com um tutor de IA — grátis
Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.
- Cursos
- 30
- Aulas
- 120
Perguntas Frequentes
A aula “Saídas estruturadas com Pydantic” é grátis?
Sim — o texto completo de “Saídas estruturadas com Pydantic” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Engineering Academy, atualize para CoddyKit PRO. O curso de AI Engineering Academy inclui 4 aulas no total.
O que vou aprender em “Saídas estruturadas com Pydantic”?
Defina modelos Pydantic como seu esquema de saída, passe-os à API por meio do novo recurso de saídas estruturadas e desserialize automaticamente as respostas em objetos Python tipados. Você pratica AI Engineering Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar AI Engineering Academy?
Nenhuma experiência prévia é necessária. AI Engineering Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “Saídas estruturadas com Pydantic”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de AI Engineering Academy?
Sim. Cada aula de AI Engineering Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Modo JSON e response_format
- Saídas estruturadas com Pydantic
- Extraindo dados de texto não estruturado
- Validando e repetindo saídas inválidas