Guía10 min

Pydantic AI: guía práctica para agentes tipados en Python

Resumen

Guía práctica de Pydantic AI en 2026: instalar pydantic-ai 2.40.0 con Python 3.10+, Agent con output_type validado, tools con @agent.tool y RunContext, deps_type para inyectar clientes sin meter secretos en el prompt, y cuándo no sustituye a LangGraph ni a un coding agent de terminal.

OpenAIGitHub
Tres filas de esquema plano: name, ok y tool

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

Pydantic AI es el SDK de agentes en Python del equipo de Pydantic: un loop tipado donde el modelo, las tools y la salida final hablan el mismo contrato. No es un chat en la terminal como Kimi Code CLI ni un canvas no-code como Dify. Es una librería: defines un Agent, le das un output_type, registras funciones y corres run() / run_sync().

Esta guía cubre el flujo del hub Construir agentes que sí puedes pegar en un repo hoy: instalación, primer agente con salida estructurada, tools con y sin contexto, inyección de dependencias y un checklist para no mezclarlo con orquestadores de grafo. Si vienes de Python puro, empareja con crear un agente desde cero. El curso Instalar un agente cubre el harness de producto; aquí el contrato es el de la librería.

Instalación: un paquete, extras a demanda

PyPI publica pydantic-ai en 2.40.0 (verificado 2026-09-07). Requiere Python 3.10+. El README y docs/install.md del repo coinciden: el metapaquete instala el core más OpenAI, Anthropic, Google, CLI, MCP, evals, Web UI y Logfire.

uv add pydantic-ai

Equivalente con pip:

pip install pydantic-ai

Si ya sabes el proveedor y no quieres el resto, usa el slim:

uv add "pydantic-ai-slim[openai]"

El mismo docs/install.md lista extras reales: anthropic, google, groq, mistral, bedrock, xai, openrouter, temporal, mcp, cli. No inventes un extra: si no está en esa lista, el install falla. En imágenes mínimas (Alpine sin CA store) el HTTP de Pydantic AI verifica TLS contra el trust store del OS, no contra un bundle certifi propio; instala ca-certificates o pasa un http_client configurado.

Primer agente: output_type, no un JSON suelto

La pieza que distingue Pydantic AI de un chat.completions con “por favor responde JSON” es output_type. El constructor registra el schema; el run no termina hasta que el modelo produce datos que Pydantic valida. La documentación de output lo deja explícito: si str no está entre los tipos, el modelo está forzado a structured data o a una output function.

from pydantic import BaseModel

from pydantic_ai import Agent


class CityLocation(BaseModel):
    city: str
    country: str


agent = Agent("google:gemini-3-flash-preview", output_type=CityLocation)
result = agent.run_sync("Where were the olympics held in 2012?")
print(result.output)
# city='London' country='United Kingdom'
print(result.usage)

result.output es CityLocation, no dict. El IDE y el type checker lo ven. Eso es el mismo problema que cubre structured outputs, resuelto aquí en el loop del agente en vez de en un parseo posterior.

Cuando necesitas “o estructura o texto de retry”, la docs usan una lista: output_type=[Box, str]. Unión Foo | Bar funciona en runtime; pyright/mypy a menudo piden parámetros genéricos explícitos en Agent hasta PEP-747.

Flujo de un Agent con output_type validado y tools registradas

Tools: @agent.tool vs @agent.tool_plain

Hay tres registros oficiales (docs/tools.md):

RegistroCuándoContexto
@agent.toolDefault. La tool necesita deps, usage o mensajesPrimer arg: RunContext[Deps]
@agent.tool_plainFunción pura, sin contexto del runSin RunContext
tools=[...] en el constructorFunciones sueltas o instancias ToolSegún la firma

El docstring y la firma son el schema que ve el modelo. Los argumentos se validan antes de entrar a tu código. Eso corta una clase entera de tool-poisoning por tipos: un int no llega como string sucio.

import random

from pydantic_ai import Agent, RunContext

agent = Agent(
    "google:gemini-3-flash-preview",
    deps_type=str,
    instructions=(
        "You're a dice game. Roll the die and see if it matches "
        "the user's guess. Use the player's name."
    ),
)


@agent.tool_plain
def roll_dice() -> str:
    """Roll a six-sided die and return the result."""
    return str(random.randint(1, 6))


@agent.tool
def get_player_name(ctx: RunContext[str]) -> str:
    """Get the player's name."""
    return ctx.deps


result = agent.run_sync("My guess is 4", deps="Anne")
print(result.output)

toolsets= agrupa tools tuyas, de un servidor MCP o de un tercero en un combined toolset. No copies 40 endpoints de OpenAPI al prompt: envuelve intención, como en API existente como tools.

Si la función es el resultado final (no debe volver al modelo), usa una output function, no una tool. Mezclar las dos es el bug típico: el modelo “llama y se queda esperando” un valor que ya era la respuesta.

Dependencias: el secreto no va en instructions

deps_type en el constructor es el tipo, no la instancia. En run() / run_sync() pasas el valor. docs/dependencies.md recomienda un dataclass cuando hay más de un objeto: API key, httpx.AsyncClient, id de tenant.

from dataclasses import dataclass

import httpx

from pydantic_ai import Agent


@dataclass
class MyDeps:
    api_key: str
    http_client: httpx.AsyncClient


agent = Agent("openai:gpt-5-mini", deps_type=MyDeps)

Las tools leen ctx.deps. El modelo no ve el dataclass entero: ve el schema de la tool. Así evitas el antipatrón de pegar tokens en el system prompt, el mismo que secretos fuera del contexto marca como fallo. En tests, inyectas un fake de DatabaseConn y no mockeas al LLM para probar el handler.

Qué no es Pydantic AI

No sustituye un coding agent de terminal. El README vende pydantic-ai-harness (Coder(), filesystem, shell, planning) como capa aparte: uv add pydantic-ai pydantic-ai-harness. Esta guía cubre el SDK; el harness es otro paquete y otro contrato.

No sustituye un orquestador de grafo cuando el flujo es un DAG con ramas humanas. Para eso sigue existiendo la comparativa LangGraph vs CrewAI vs SDK. Pydantic Graph existe en el mismo ecosistema, pero el default sano es un Agent con tools, no un grafo de 12 nodos para un clasificador.

Durable execution (Temporal, DBOS, Prefect) se engancha como capability (TemporalDurability() extra pydantic-ai[temporal]). Eso sobrevive restarts; no es el checkpoint casero de ejecución durable. Si ya operas Temporal, úsalo. Si no, no lo instales “por si acaso”.

Inyección de deps y frontera entre Agent, tools y runtime durable

Checklist operativo

  1. Fija Python 3.10+ y pinea pydantic-ai==2.40.0 (o el último que hayas verificado en PyPI) en el lockfile.
  2. Empieza con uv add pydantic-ai. Pasa a slim cuando el image de producción se queje de peso.
  3. Declara output_type con un BaseModel. No parsees JSON a mano “para ir más rápido”.
  4. Tools con I/O externo: @agent.tool + RunContext. Puras: @agent.tool_plain.
  5. Secretos y clientes HTTP viven en deps, nunca en instructions.
  6. Cambia de proveedor con el string del modelo (openai:…, google:…, anthropic:…). No forks del Agent por vendor.
  7. Observabilidad: Logfire es opcional y viene en el metapaquete, no en slim. Un backend OTel cualquiera también sirve.
  8. Si el agente debe durar horas, añade la capability durable del motor que ya operas. No metas Temporal de adorno.

FAQ

¿Sirve para un bot de Telegram? Sí como cerebro: el webhook valida, construye deps y llama await agent.run(...). El canal sigue siendo tu código; Pydantic AI no reemplaza el ack de 3 s.

¿Puedo usar Ollama? El README lista Ollama entre los modelos del mismo API. El extra slim concreto lo confirmas en docs/install.md antes de asumir que pydantic-ai-slim[ollama] existe.

¿En qué se diferencia de LangChain? Un Agent + types, no una cadena de abstractions. Si tu dolor es “el JSON sale mal” o “la tool recibió un string donde iba un int”, empiezas aquí. Si tu dolor es un grafo multiagente con estado persistente, mide LangGraph.

¿El modelo del string tiene que existir hoy? El identificador lo resuelve el provider en runtime. Usa un id que tu cuenta tenga; los ejemplos de docs (google:gemini-3-flash-preview, openai:gpt-5-mini) son los de docs/tools.md y docs/output.md al 2026-09-07, no un alias interno tuyo.

Cuándo sí / cuándo no

Usa Pydantic AI cuando el agente es código Python de producto: extrae un objeto, llama 5–12 tools, inyecta un pool y tiene que type-checkear. No lo uses como reemplazo de Cursor/Claude Code para editar este repo, ni como atajo para saltarte evals: Pydantic Evals existe, pero el dataset lo pones tú.