← Volver a Aprende con CDIA

Casos prácticosCaso: agentes tipados con pydantic-ai

IntermedioCasos prácticos8 min de lectura

Caso: agentes tipados con pydantic-ai

La validación de Pydantic aplicada a los LLM: definir la forma exacta de la salida con output_type y recibir un objeto ya validado, más herramientas con un decorador para un agente completo.

Si vienes del mundo de Python moderno, seguramente ya usas Pydantic: esa librería que valida datos contra un esquema tipado y te grita apenas algo no calza. pydantic-ai lleva esa misma filosofía al terreno de los agentes de IA: en vez de recibir texto suelto de un LLM y cruzar los dedos, defines la forma exacta que quieres y la librería se encarga de que la respuesta la cumpla.

Es la propuesta de los creadores de Pydantic para construir con LLM sin perder la seguridad de tipos que hace agradable a Python. En este caso lo conectamos con Claude, el modelo de Anthropic.

Preparar el terreno

pip install pydantic-ai

Y tu clave de Anthropic en la variable de entorno de siempre:

export ANTHROPIC_API_KEY="tu-clave-aqui"

Un agente en cuatro líneas

La pieza central de la librería es el Agent. En su forma más simple, es un LLM con una instrucción de sistema. El modelo se elige con una cadena proveedor:modelo:

from pydantic_ai import Agent

agente = Agent(
    "anthropic:claude-opus-4-8",
    system_prompt="Eres un asistente conciso. Respondes en una frase.",
)

resultado = agente.run_sync("¿Qué es la validación cruzada?")
print(resultado.output)

run_sync ejecuta la consulta y espera la respuesta; el texto está en .output. Hasta aquí, nada que LangChain no haga. La diferencia aparece ahora.

Lo que lo distingue: salidas tipadas

Aquí es donde pydantic-ai muestra su carácter. Le dices al agente que su salida debe tener cierta forma —un modelo de Pydantic— y te devuelve un objeto ya validado, no un texto que hay que parsear:

from pydantic import BaseModel, Field
from pydantic_ai import Agent

class Ticket(BaseModel):
    categoria: str = Field(description="facturación, soporte técnico o ventas")
    urgencia: int = Field(description="del 1 (baja) al 5 (crítica)")
    resumen: str

# output_type ata la respuesta al esquema: si no calza, la librería reintenta.
clasificador = Agent(
    "anthropic:claude-opus-4-8",
    output_type=Ticket,
    system_prompt="Clasificas correos de clientes en un ticket estructurado.",
)

r = clasificador.run_sync(
    "Llevo dos días sin poder entrar a mi cuenta y tengo una entrega urgente."
)
print(r.output.categoria)  # soporte técnico
print(r.output.urgencia)   # 5
print(type(r.output))      # <class '__main__.Ticket'>

Fíjate en lo que garantiza: r.output es un Ticket de verdad, con urgencia como entero. Si el modelo se sale del esquema, pydantic-ai lo detecta y le pide que corrija, sin que tú programes ese ida y vuelta. Pasas de "el LLM devolvió algo, a ver si lo puedo parsear" a "tengo un objeto tipado, listo para guardar en la base de datos".

Darle herramientas: el primer agente de verdad

Un agente se vuelve interesante cuando puede actuar. En pydantic-ai, una herramienta es una función de Python que decoras: el modelo decide cuándo llamarla, y la librería ejecuta el bucle por ti.

from pydantic_ai import Agent

agente = Agent(
    "anthropic:claude-opus-4-8",
    system_prompt="Ayudas con cálculos. Usa las herramientas para no equivocarte.",
)

@agente.tool_plain
def stock_disponible(producto: str) -> int:
    """Devuelve las unidades en bodega de un producto."""
    inventario = {"teclado": 12, "monitor": 0, "mouse": 47}
    return inventario.get(producto.lower(), 0)

r = agente.run_sync("¿Tenemos monitores en stock? Si no, dime qué sí hay.")
print(r.output)
# El modelo llama a stock_disponible("monitor"), ve que es 0, consulta los otros
# y responde algo como: "No hay monitores, pero sí 12 teclados y 47 mouse."

Lo elegante es lo que no escribiste: no hay bucle a mano, no revisas stop_reason, no armas los mensajes de resultado. Defines la función, la decoras, y la librería orquesta el ciclo del agente —decidir, ejecutar, observar, repetir— por debajo. El docstring de la función le dice al modelo para qué sirve, así que escríbelo con cuidado: es parte del prompt.

LangChain vs. pydantic-ai

No compiten a muerte; tienen acentos distintos. LangChain es un ecosistema enorme, ideal cuando encadenas muchos pasos y conectas muchas fuentes. pydantic-ai es más liviano y pone la seguridad de tipos en el centro: brilla cuando lo que más te importa es que la salida tenga una forma fiable. Si ya vives en el mundo Pydantic, se sentirá como en casa.

Para llevar

pydantic-ai trae la validación tipada de Pydantic al desarrollo con LLM: defines la forma exacta de la salida con output_type y recibes un objeto ya validado, no texto que hay que parsear —y si el modelo se desvía, la librería lo hace corregir—. Con @agente.tool_plain conviertes funciones de Python en herramientas y obtienes un agente completo sin escribir el bucle. Es la opción natural cuando la fiabilidad de los datos manda; su primo pydantic-graph da el siguiente paso para orquestar flujos complejos.