AI Engineering Academy · Lección

Salidas estructuradas con Pydantic

Definirá modelos de Pydantic como esquema de salida, los enviará a la API mediante la nueva funcionalidad de salidas estructuradas y deserializará automáticamente las respuestas en objetos de Python tipados.

Lección 2 de 413 pasos

Salidas estructuradas con Pydantic es una lección gratuita de AI Engineering Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Engineering Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Engineering Academy incluye 4 lecciones en total.

¿Por qué usar Pydantic para las respuestas de los LLM?

Pydantic es una biblioteca de validación de datos para Python que define esquemas de datos mediante sugerencias de tipos de Python. Es excelente para validar y deserializar datos de fuentes externas, y las respuestas de los LLM son una de las fuentes externas menos fiables que encontrará. Combinar esquemas de Pydantic con Structured Outputs de OpenAI le proporciona respuestas de un modelo de IA con tipos seguros, validadas y deserializadas automáticamente.

En lugar de escribir data = json.loads(response) y después extraer manualmente los campos y convertir sus tipos, obtiene un objeto Python completamente tipado en el que se garantiza que cada campo tiene el tipo correcto, con autocompletado en su IDE y validación en tiempo de ejecución. Así es como los equipos profesionales de ingeniería de IA gestionan la extracción estructurada.

Definición de un esquema básico de Pydantic

Un modelo de Pydantic es una clase que hereda de BaseModel y cuyos campos se definen mediante anotaciones de tipo de Python. Los tipos de los campos pueden ser primitivas de Python, otros modelos de Pydantic para crear anidamientos o tipos del módulo typing para listas, opcionales y uniones.

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 con Structured Outputs de OpenAI

Pase directamente la clase de su modelo de Pydantic al parámetro response_format de client.beta.chat.completions.parse(). El SDK convierte automáticamente el modelo a JSON Schema, lo envía a la API y deserializa la respuesta de nuevo en un 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 de Pydantic anidados

Los esquemas de Pydantic pueden hacer referencia a otros modelos de Pydantic, lo que permite generar respuestas estructuradas con cualquier nivel de anidamiento. Esto resulta ideal para extraer datos jerárquicos de documentos como contratos, facturas, currículos y expedientes 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')

Validación de campos con validadores de Pydantic

Los validadores de Pydantic permiten añadir lógica de validación personalizada además de la comprobación básica de tipos. Puede validar que una puntuación de confianza esté entre 0 y 1, que un precio no sea negativo o que una cadena de fecha tenga el formato correcto. Cuando el LLM devuelve un valor que no supera la validación, Pydantic genera un ValidationError que puede capturar y gestionar.

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}')

Extracción de listas de objetos

Un patrón habitual consiste en extraer varias instancias de la misma entidad de un documento: todas las líneas de una factura, todos los elementos de acción de la transcripción de una reunión o todas las entidades de un artículo de noticias. Para gestionar este caso de forma sencilla, envuelva su modelo en un modelo contenedor con un campo de lista.

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 opcionales y valores predeterminados

Los documentos del mundo real están incompletos. Es posible que un currículum no incluya un número de teléfono, que una factura no tenga número de factura o que una reseña de producto no mencione el nombre del producto. Diseñe sus modelos de Pydantic para gestionar los datos faltantes correctamente mediante campos Optional con valores predeterminados adecuados.

Un campo anotado como Optional[str] = None indica tanto a Pydantic como al LLM que dicho campo puede estar ausente. El modelo devolverá null en JSON para los campos que no pueda extraer, y Pydantic lo deserializará como None de Python. Así podrá gestionarlo correctamente en los pasos posteriores sin excepciones KeyError.

Conversión de modelos de Pydantic a JSON Schema

El modelo de Pydantic que define se convierte automáticamente a JSON Schema al pasarlo a la API. Puede inspeccionar este esquema para comprender exactamente qué impondrá la API, lo que resulta útil para depurar casos en los que el modelo no devuelve la estructura 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 enforce

Gestión de fallos de extracción

Incluso con Structured Outputs, la extracción puede fallar de dos maneras: el modelo se niega a responder (devuelve un rechazo) o el documento realmente no contiene la información solicitada, por lo que el modelo devuelve valores null para campos obligatorios, lo que provoca un error de validación de Pydantic porque los campos obligatorios no pueden ser null.

El enfoque más seguro consiste en hacer que todos los campos sean Optional con valores predeterminados, aceptar valores null para los datos faltantes y aplicar su propia validación de lógica de negocio después de la extracción. De este modo, separa la extracción (obtener datos del texto) de la validación (comprobar que los datos cumplen sus 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)

Uso de Pydantic con la biblioteca instructor

La biblioteca instructor es un paquete de terceros popular que modifica el cliente de OpenAI para admitir la extracción basada en Pydantic con reintentos automáticos cuando falla la validación. Si el modelo devuelve una respuesta que no supera su validación de Pydantic, instructor vuelve a intentarlo automáticamente con un prompt que incluye el error de validación, lo que permite al modelo corregirse.

Esto resulta especialmente útil en procesos de extracción por lotes en los que no puede revisar cada resultado manualmente y desea que el sistema se autocorrija sin intervención 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}')

Uniones discriminadas y esquemas dinámicos

Pydantic admite uniones discriminadas: un esquema cuya estructura depende del valor de un campo discriminador. Esto resulta útil cuando distintos tipos de documentos comparten una base común, pero tienen campos adicionales diferentes. Por ejemplo, un informe de gastos puede incluir un recibo de vuelo (con origen y destino) o un recibo de hotel (con fechas de entrada y salida).

Mediante tipos Union con un campo discriminador Literal, puede definir un único esquema de extracción que gestione varias variantes de documentos, y el modelo seleccionará el subtipo correcto según el contenido del documento. Pydantic valida automáticamente el subtipo correcto basándose en el valor del 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')

Comprobación rápida

Compruebe su comprensión de los conceptos de ingeniería de IA de esta lección.

Resumen de la lección

En esta lección ha aprendido que las subclases de Pydantic BaseModel definen esquemas de extracción fuertemente tipados que Structured Outputs de OpenAI impone en el nivel de la API, que los modelos anidados, los campos Optional y los tipos List gestionan estructuras complejas de documentos del mundo real y que la biblioteca instructor añade reintentos automáticos cuando falla la validación para crear procesos de extracción por lotes sólidos. A continuación, crearemos un proceso completo de extracción de información para fuentes de texto no estructurado.

Gratis para empezar

Aprende Python con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
30
Lecciones
120

Preguntas frecuentes

¿La lección «Salidas estructuradas con Pydantic» es gratis?

Sí — el texto completo de «Salidas estructuradas con Pydantic» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Engineering Academy, actualiza a CoddyKit PRO. El curso de AI Engineering Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Salidas estructuradas con Pydantic»?

Definirá modelos de Pydantic como esquema de salida, los enviará a la API mediante la nueva funcionalidad de salidas estructuradas y deserializará automáticamente las respuestas en objetos de Python… Practicas AI Engineering Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Engineering Academy?

No se requiere experiencia previa. AI Engineering Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Salidas estructuradas con Pydantic»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Engineering Academy?

Sí. Cada lección de AI Engineering Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Modo JSON y response_format
  2. Salidas estructuradas con Pydantic
  3. Extracción de datos de texto no estructurado
  4. Validación y reintento de salidas incorrectas
← Volver a AI Engineering Academy