Una persona escribe: «Quiero dos plazas para el taller de Fotografía nocturna». Nuestra aplicación necesita dos datos para preparar una ficha: el nombre del taller y el número de plazas. Una respuesta como «parece que quiere asistir con otra persona» puede entenderse, pero obliga al programa a interpretar otra vez un texto.

La solución consiste en definir una clase con los campos y las reglas que admitimos, utilizarla para pedir esa estructura al modelo y comprobar los datos recibidos. Con Pydantic podemos expresar esa definición en Python y obtener un objeto cuyos campos pueda leer nuestra aplicación.

El recorrido completo es este:

  1. Definimos una ficha con nombre del taller y plazas solicitadas.
  2. Enviamos al modelo el mensaje y la estructura de esa ficha.
  3. El modelo devuelve los datos; el SDK los valida con Pydantic y construye el objeto.
  4. La aplicación lee sus campos para presentar la propuesta de inscripción.

Vamos a recorrerlo con ese único mensaje. Los resultados del modelo serán ilustrativos; ninguna plaza quedará reservada.

Qué datos queremos recibir

Llamaremos workshop_name al campo que guarda el nombre del taller y requested_seats al que guarda las plazas solicitadas. Son nombres que elegimos al programar. Sus valores proceden del mensaje:

Parte del mensaje Campo donde guardamos el dato Valor
«taller de Fotografía nocturna» workshop_name "Fotografía nocturna"
«dos plazas» requested_seats 2

workshop_name es la etiqueta del campo; Fotografía nocturna es el nombre concreto que ha mencionado la persona. Si pidiese el taller de Cerámica básica, cambiaría el valor, pero el campo seguiría llamándose igual.

Queremos una respuesta como esta en JSON, un formato de texto que organiza datos mediante nombres y valores:

{
  "workshop_name": "Fotografía nocturna",
  "requested_seats": 2
}

El nombre del taller es un texto, escrito entre comillas. La cantidad es un número. Esta es la forma de la ficha que vamos a definir.

Una clase describe la ficha que aceptamos

Pydantic es una biblioteca que comprueba datos durante la ejecución de un programa. Creamos una clase que hereda de su BaseModel, declaramos las propiedades y añadimos su configuración:

from pydantic import BaseModel, ConfigDict, Field


class WorkshopRequest(BaseModel):
    """Represent the workshop and seats requested in a message."""

    model_config = ConfigDict(extra="forbid", strict=True)

    workshop_name: str = Field(min_length=1)
    requested_seats: int = Field(ge=1)

La clase WorkshopRequest representa nuestra ficha. La guardamos en su propio módulo, workshop_request.py; el código que llama al proveedor irá separado.

Las dos propiedades concretan qué admitimos:

  • workshop_name: str exige un texto. min_length=1 impide que sea una cadena vacía.
  • requested_seats: int exige un entero. ge=1 indica que debe ser mayor o igual que uno.

Ambas deben aparecer, porque no hemos definido valores por defecto. La configuración completa las reglas: extra="forbid" rechaza propiedades ajenas a la ficha y strict=True evita convertir automáticamente el texto "2" en el número 2.

Estas reglas pertenecen a nuestro programa. La clase no llama a una IA ni interpreta por sí sola la frase «dos plazas». Define qué datos aceptaremos después de esa interpretación. La documentación de campos de Pydantic explica cómo declarar sus tipos y restricciones.

Pedir al modelo que utilice esa estructura

No basta con escribir «responde en JSON»: podríamos recibir campos distintos, una cantidad escrita como texto o una explicación alrededor del resultado. Las salidas estructuradas permiten enviar al proveedor un esquema con los campos y tipos esperados.

El SDK de Anthropic —la biblioteca que conecta Python con su API— acepta nuestra clase mediante output_format=WorkshopRequest. A partir de ella prepara un esquema en JSON Schema, el formato que describe la estructura. El proveedor recibe esa descripción, no el código de la clase para ejecutarlo.

El programa necesita los paquetes anthropic y pydantic, una credencial configurada en ANTHROPIC_API_KEY y un modelo compatible en ANTHROPIC_MODEL. Las instrucciones explican la tarea; la clase define el formato que esperamos:

import os

from anthropic import Anthropic
from pydantic import ValidationError

from workshop_request import WorkshopRequest

SYSTEM_PROMPT = """Extract the workshop name and number of requested seats.
Use only the supplied message. Do not confirm enrollment."""

message = "Quiero dos plazas para el taller de Fotografía nocturna."

try:
    with Anthropic(timeout=20.0, max_retries=0) as client:
        response = client.messages.parse(
            model=os.environ["ANTHROPIC_MODEL"],
            system=SYSTEM_PROMPT,
            messages=[{"role": "user", "content": message}],
            output_format=WorkshopRequest,
            max_tokens=256,
        )
except ValidationError as error:
    raise ValueError("The extracted data is invalid.") from error

if response.stop_reason != "end_turn" or response.parsed_output is None:
    raise RuntimeError("The extraction did not complete.")

extracted = response.parsed_output

messages.parse envía la petición y utiliza Pydantic para validar los datos recibidos. Si todo termina correctamente, parsed_output contiene un objeto WorkshopRequest. Lo guardamos en extracted para utilizar sus propiedades.

Podemos añadir estas dos líneas al final:

print(extracted.workshop_name)
print(extracted.requested_seats)

Con la extracción esperada, imprimen:

Fotografía nocturna
2

La aplicación ya tiene un texto y un entero para rellenar su ficha. No necesita buscar la cantidad dentro de una respuesta en prosa. Esa transformación, desde el mensaje original hasta campos utilizables, es la extracción de información que buscábamos.

El programa envía el mensaje y el esquema; el modelo devuelve el nombre y las plazas; Pydantic valida los datos y el programa presenta la ficha.

Abrir el diagrama a tamaño completo.

El tiempo avanza hacia abajo. El modelo interpreta el mensaje; Pydantic se ejecuta en nuestro programa y comprueba los datos. La devolución del objeto permite leer el nombre y las plazas para presentarlos.

Qué ocurre si la respuesta incumple las reglas

Una salida estructurada restringe la forma de la generación según las capacidades del proveedor. El SDK puede adaptar restricciones que el servicio no admite y mantener su comprobación local con Pydantic. La documentación de esta integración recoge sus condiciones.

Si los datos no cumplen la clase, Pydantic lanza ValidationError. Por ejemplo:

Dato recibido Por qué se rechaza
"requested_seats": 0 La cantidad debe ser al menos uno.
"requested_seats": "2" Es texto; hemos exigido un entero sin conversión automática.
Falta workshop_name. La propiedad es obligatoria.
Aparece "enrollment_confirmed": true. Hemos prohibido campos adicionales.

El except del código identifica el fallo de validación y conserva su causa con from error. Los fallos de conexión o autenticación se propagan como errores de la llamada; no se convierten en una ficha.

También comprobamos que la generación haya terminado normalmente y que exista un objeto leído. El bloque with cierra las conexiones y max_retries=0 evita reintentos automáticos. Son detalles de la llamada; las reglas de la ficha siguen concentradas en la clase.

Probar una regla sin utilizar la IA

Podemos comprobar el rechazo de cero plazas con un dato que escribimos nosotros. Esta prueba sirve para verificar nuestra definición de WorkshopRequest, sin depender de lo que decida un modelo.

En un módulo que importe esa clase, ejecutamos:

from workshop_request import WorkshopRequest

invalid_reply = """{
  "workshop_name": "Fotografía nocturna",
  "requested_seats": 0
}"""

WorkshopRequest.model_validate_json(invalid_reply)

invalid_reply es un texto preparado a mano, no una respuesta real de IA. model_validate_json lo lee y comprueba sus valores contra la clase. Como cero incumple ge=1, lanza ValidationError y no devuelve una ficha válida. Si cambiamos cero por dos, esa comprobación acepta los datos y devuelve el objeto.

La validación de JSON de Pydantic se puede usar con datos de cualquier origen. En esta prueba los hemos escrito nosotros; en la llamada anterior procedían del modelo. La comprobación sigue siendo la misma.

Una ficha válida todavía puede contener una extracción equivocada

Si el modelo devuelve tres plazas, el número cumple nuestras reglas: es un entero positivo. Pero la persona había pedido dos. Validar la estructura no demuestra que la extracción sea fiel al mensaje. Para comprobarlo necesitamos contrastar los valores con el texto original, mediante revisión o casos con resultados esperados.

Este contrato exige los dos datos. Para aceptar mensajes sin cantidad tendríamos que representar esa ausencia o pedir una aclaración; no completar el campo inventando un número.

La ficha de nuestro ejemplo debe conservar «Fotografía nocturna» y dos plazas. Sirve para preparar una propuesta revisable. Confirmar la inscripción requeriría además comprobar el taller, la disponibilidad y las reglas de esa operación: ninguno de esos efectos se produce por recibir un objeto bien formado.