Building Type-Safe LLM Agents with PydanticAI
PydanticAI is an agent framework from the team behind Pydantic, designed to bring the same developer experience that FastAPI brought to web APIs into the world of generative AI. This tutorial walks through its core concepts: type-safe agents, structured outputs, dependency injection, and tools, ending with a complete customer-support agent you can adapt to your own projects.
What PydanticAI Is and Why It Exists
The Pydantic team built PydanticAI because much of the Python AI ecosystem either reinvented validation poorly or wrapped LLM calls in loosely typed dictionaries. PydanticAI takes a different stance: lean on the type system, validate everything, and keep the API small enough to reason about.
A few design principles shape the framework:
- Type-safe by design. Agents declare their dependency type and output type as generic parameters. Static checkers like mypy and Pyright understand them, and your editor autocompletes accordingly.
- Model-agnostic. OpenAI, Anthropic, Google (Gemini), Groq, Mistral, Ollama, and others are supported behind a single
Agentinterface. Switching models is usually a one-line change. - FastAPI-like ergonomics. Dependency injection, decorators for tools and validators, and a clean separation between defining an agent and running it will feel familiar if you have used FastAPI.
- Built on Pydantic. Structured outputs are plain Pydantic models, so you get validation, JSON schema generation, and serialization for free.
- Production observability. First-class integration with Logfire gives you tracing and debugging without bolting on a separate framework.
PydanticAI is intentionally not a do-everything orchestration platform. It focuses on the agent loop, leaving you free to compose agents with your own application code.
Installation
PydanticAI requires Python 3.9 or newer. Install it with pip:
pip install pydantic-ai
The base package pulls in support for the major model providers. If you want a slimmer install, you can select only the providers you need:
pip install "pydantic-ai-slim[openai]"
pip install "pydantic-ai-slim[anthropic]"
pip install "pydantic-ai-slim[google]"
If you plan to use observability, install the Logfire extra as well:
pip install "pydantic-ai[logfire]"
Most providers read their API key from an environment variable:
export OPENAIAPIKEY="sk-..."
export ANTHROPICAPIKEY="sk-ant-..."
export GEMINIAPIKEY="..."
Your First Agent
An Agent is the central object. You give it a model identifier and an optional system prompt, then run it.
from pydanticai import Agent
agent = Agent(
"openai:gpt-4o",
system
prompt="You are a concise assistant. Answer in one or two sentences.",
)
result = agent.runsync("What is the capital of Indonesia?")
print(result.output)
Jakarta is the capital of Indonesia.
The model string follows the pattern provider:model-name. Swapping providers is a one-line change:
agent = Agent("anthropic:claude-3-5-sonnet-latest")
agent = Agent("google-gla:gemini-1.5-flash")
You can also pass a model instance directly if you need custom configuration such as a base URL or timeout. The string form is convenient for the common case.
Three Ways to Run an Agent
PydanticAI offers three run methods depending on your execution context:
import asyncio
Synchronous - convenient in scripts and notebooks
result = agent.runsync("Hello")
print(result.output)
Asynchronous - for async applications and concurrency
async def main():
result = await agent.run("Hello")
print(result.output)
asyncio.run(main())
Streaming - receive output incrementally as it is generated
async def streamexample():
async with agent.runstream("Tell me a short story") as response:
async for chunk in response.streamtext(delta=True):
print(chunk, end="", flush=True)
asyncio.run(streamexample())
All three return (or yield) the same rich result object, which carries the output, the message history, and token usage.