Skip to content

Pydantic AI Tutorial: Build Type-Safe AI Agents (2026)

Pydantic AI is a Python agent framework that brings FastAPI’s developer experience to GenAI — type-safe, model-agnostic, and built for production from day one. If you’ve ever been frustrated by LangChain’s complexity or wanted structured outputs without fighting JSON parsing, Pydantic AI is the framework to try. It’s built by the same team behind Pydantic, the most downloaded Python validation library.

Who this is for:

  • Junior engineers: You want a clean, intuitive way to build your first AI agent without learning a massive framework
  • Senior engineers: You need type-safe, testable agents with dependency injection and structured outputs for production

Building AI agents in 2026 means choosing between frameworks that each have painful trade-offs:

ProblemLangChainRaw API CallsPydantic AI
Structured output from LLMsOutputParser + retry logicManual JSON parsing + validationDeclare a Pydantic model, done
Switching between GPT-4o and ClaudeDifferent chain configsDifferent API clients + schemasChange one string: 'openai:gpt-4o''anthropic:claude-sonnet-4-20250514'
Testing agents without API callsMock the entire chainMock HTTP callsBuilt-in TestModel — zero API calls, deterministic output
Type safetyRuntime errors on bad schemasNo validation until productionCompile-time type checking with mypy/pyright
Dependency injectionNot built-inManual wiringFirst-class deps_type parameter

“How do I build a Pydantic AI agent with structured output?” — the answer is surprisingly simple: define a Pydantic model, pass it as output_type, and the framework handles the rest.

The core insight: Pydantic AI treats agent building like API development. You declare inputs, outputs, and dependencies — the framework handles the rest. If you’ve used FastAPI, you’ll feel right at home.


Think of Pydantic AI like FastAPI, but for LLM interactions. In FastAPI, you define an endpoint with typed parameters and a response model. In Pydantic AI, you define an Agent with typed dependencies and an output model.

  1. Agent — The core object. You give it a model name, an output type, and system instructions. It handles the LLM conversation loop, tool calls, and response validation.

  2. Output Types — A Pydantic BaseModel that defines what the agent must return. The framework generates the JSON schema, sends it to the LLM, and validates the response automatically. No manual parsing.

  3. Tools — Python functions decorated with @agent.tool. The framework extracts the function signature, generates a tool schema, and handles the call/response cycle. You write plain Python; the framework does the LLM wiring.

  4. Dependencies — A typed dataclass passed via RunContext. This is how you inject database connections, API clients, or user context into tools — without global state.

Unlike LangChain where you compose chains of abstractions, Pydantic AI keeps you close to plain Python. An agent is a class. A tool is a function. An output is a Pydantic model. No chains, no runnables, no LCEL syntax.


Step-by-Step: Build a Structured-Output Agent

Section titled “Step-by-Step: Build a Structured-Output Agent”

You’ll build a customer support agent that takes a user message and returns a structured response with advice, risk level, and recommended action.

Terminal window
pip install pydantic-ai

This is the Pydantic model the agent must return. Every field is validated:

from pydantic import BaseModel
class SupportResponse(BaseModel):
advice: str
"""The support advice to give the customer."""
risk_level: int
"""Risk level from 1 (low) to 10 (high)."""
block_card: bool
"""Whether to block the customer's card."""
escalate: bool
"""Whether to escalate to a human agent."""

Dependencies are injected into tools via RunContext. Define them as a dataclass:

from dataclasses import dataclass
@dataclass
class SupportDeps:
customer_id: int
customer_name: str
account_balance: float
from pydantic_ai import Agent
support_agent = Agent(
"openai:gpt-4o",
deps_type=SupportDeps,
output_type=SupportResponse,
instructions=(
"You are a bank support agent. Assess the customer's issue, "
"provide clear advice, and rate the risk level. "
"Use the customer's name in your response."
),
)

Tools let the agent access external data. The RunContext parameter gives you typed access to dependencies:

from pydantic_ai import RunContext
@support_agent.tool
async def get_recent_transactions(ctx: RunContext[SupportDeps]) -> str:
"""Retrieve the customer's recent transactions."""
# In production, this would query your database
return f"Last 3 transactions for customer {ctx.deps.customer_id}: $50 grocery, $200 electronics, $15 coffee"
@support_agent.tool
async def check_fraud_alerts(ctx: RunContext[SupportDeps]) -> str:
"""Check if there are any fraud alerts on the account."""
return f"No fraud alerts for customer {ctx.deps.customer_id}"
import asyncio
async def main():
deps = SupportDeps(
customer_id=123,
customer_name="Alice",
account_balance=1500.00
)
result = await support_agent.run(
"I just noticed a $200 charge I didn't make!",
deps=deps
)
# result.output is a validated SupportResponse
print(f"Advice: {result.output.advice}")
print(f"Risk: {result.output.risk_level}")
print(f"Block card: {result.output.block_card}")
print(f"Escalate: {result.output.escalate}")
print(f"Tokens used: {result.usage()}")
asyncio.run(main())

The output is a fully validated SupportResponse object — not a string, not a dict, but a type-safe Pydantic model.


Pydantic AI processes requests through four sequential stages: input assembly, the agent orchestration loop, tool execution, and Pydantic model validation of the final output.

Pydantic AI Agent Execution Flow

From user prompt to validated structured output

InputUser message + deps
User prompt string
Typed dependencies (RunContext)
Agent CoreOrchestration loop
Inject system instructions
Send to LLM with tool schemas
Parse response (text or tool call)
Tool ExecutionIf LLM requests tools
Match tool by name
Inject RunContext with deps
Return result to LLM
Output ValidationPydantic model validation
Parse LLM response as output_type
Validate all fields
Return typed result
Idle

Pydantic AI Architecture

Clean separation between your code and framework internals

Your Agent Code
Agent, tools, output models, dependencies
Pydantic AI Framework
Agent loop, tool dispatch, retry logic
Model Interface
Unified API for OpenAI, Anthropic, Gemini, Ollama
Pydantic Validation
Schema generation, response parsing, type checking
LLM Provider APIs
OpenAI, Anthropic, Google, local models
Idle

The key architectural insight: Pydantic AI separates what your agent does (your code at the top) from how it communicates with LLMs (the framework layers below). Switching from GPT-4o to Claude is a one-line change because the model interface abstracts provider differences.


These three patterns demonstrate Pydantic AI’s most distinctive capabilities: model-agnostic switching, deterministic CI testing with TestModel, and dynamic system instructions from dependencies.

One of Pydantic AI’s strongest features — switch models without changing any agent code:

# Same agent, different models
agent_gpt = Agent("openai:gpt-4o", output_type=SupportResponse, ...)
agent_claude = Agent("anthropic:claude-sonnet-4-20250514", output_type=SupportResponse, ...)
agent_gemini = Agent("gemini-2.0-flash", output_type=SupportResponse, ...)
agent_local = Agent("ollama:llama3.2", output_type=SupportResponse, ...)

Pydantic AI includes a TestModel that returns deterministic responses — no API keys needed:

from pydantic_ai.models.test import TestModel
async def test_support_agent():
"""Test the agent without making any LLM API calls."""
with support_agent.override(model=TestModel()):
deps = SupportDeps(customer_id=1, customer_name="Test", account_balance=100)
result = await support_agent.run("Test message", deps=deps)
# TestModel returns valid instances of your output type
assert isinstance(result.output, SupportResponse)
assert 1 <= result.output.risk_level <= 10

This is a game-changer for CI/CD pipelines. Your agent tests run in milliseconds, cost nothing, and never flake due to API timeouts.

Add context-aware instructions that read from dependencies:

@support_agent.instructions
async def add_context(ctx: RunContext[SupportDeps]) -> str:
return (
f"The customer's name is {ctx.deps.customer_name}. "
f"Their account balance is ${ctx.deps.account_balance:.2f}."
)

The instruction function runs before every agent call, injecting live context into the system prompt.


Pydantic AI trades ecosystem breadth for type safety and testability — the right choice depends on whether you need pre-built RAG components or clean, validated agent outputs.

Pydantic AI vs LangChain

Pydantic AI
Type-safe, minimal, FastAPI-inspired
  • Type-safe structured outputs — validated automatically
  • Built-in TestModel for deterministic testing
  • Dependency injection via RunContext
  • Model-agnostic — one-line model switching
  • Smaller ecosystem — fewer pre-built integrations
  • No built-in RAG, vector store, or retriever components
  • Newer framework — less community content and tutorials
VS
LangChain
Batteries-included, chain-based, ecosystem-rich
  • Massive ecosystem — 700+ integrations
  • Built-in RAG, vector stores, retrievers
  • Large community — Stack Overflow, Discord, tutorials
  • Complex abstraction layers — hard to debug
  • No built-in dependency injection
  • Structured output requires OutputParser + retry logic
Verdict: Use Pydantic AI for type-safe agents with structured outputs and testability. Use LangChain when you need pre-built RAG components and a large integration ecosystem.
Use case
Choosing an agent framework for production Python apps

Other gotchas:

  • Output validation retries — If the LLM returns invalid JSON, Pydantic AI retries automatically. But if your output model is too complex (deeply nested, many optional fields), retries can consume extra tokens. Keep output models flat when possible.
  • Async by defaultagent.run() is async. For scripts and notebooks, use agent.run_sync(). Forgetting this causes RuntimeError: cannot be called from a running event loop.
  • Tool signature matters — Pydantic AI generates tool schemas from your function signatures. Unclear parameter names or missing docstrings produce bad tool descriptions, causing the LLM to misuse tools.

These four questions cover the concepts interviewers use to assess whether you understand Pydantic AI’s design philosophy versus LangChain’s ecosystem approach.

Q1: “What is Pydantic AI and how is it different from LangChain?”

Section titled “Q1: “What is Pydantic AI and how is it different from LangChain?””

What they’re testing: Can you articulate the architectural philosophy difference?

Strong answer: “Pydantic AI is a minimal agent framework built by the Pydantic team. It focuses on three things: type-safe structured outputs via Pydantic models, dependency injection via RunContext, and model-agnostic agents. LangChain is an ecosystem with hundreds of integrations — retrievers, vector stores, document loaders. Pydantic AI is lean by design; LangChain is batteries-included.”

Weak answer: “Pydantic AI is newer and simpler.”

Q2: “How does Pydantic AI handle structured outputs?”

Section titled “Q2: “How does Pydantic AI handle structured outputs?””

What they’re testing: Do you understand the structured output mechanism vs. parsing JSON strings?

Strong answer: “You pass a Pydantic BaseModel as the output_type parameter. The framework sends the JSON schema to the LLM, the LLM responds with structured JSON, and Pydantic validates it automatically. If validation fails, the framework retries with the error message. You never parse JSON strings manually.”

Q3: “How would you test a Pydantic AI agent in CI without API keys?”

Section titled “Q3: “How would you test a Pydantic AI agent in CI without API keys?””

What they’re testing: Production readiness — can you test AI code reliably?

Strong answer: “Pydantic AI has a built-in TestModel that returns valid instances of your output type without calling any LLM API. You use agent.override(model=TestModel()) in tests. This gives you deterministic, fast, free tests that run in CI without API credentials.”

Q4: “When would you choose Pydantic AI over LangChain?”

Section titled “Q4: “When would you choose Pydantic AI over LangChain?””

What they’re testing: Can you match the framework to the problem?

Strong answer: “I’d choose Pydantic AI when I need structured outputs with validation, when testability is a priority, when I want to swap models easily, or when the team values type safety. I’d stick with LangChain if I need pre-built RAG components, vector store integrations, or the LangGraph state machine for complex agent orchestration.”


Here’s how teams use Pydantic AI in production:

Structured API backends: The most common pattern is using Pydantic AI agents behind a FastAPI endpoint. The agent’s output_type becomes the API response model — one Pydantic model serves both the LLM validation and the HTTP response schema.

Multi-model routing: Teams define one agent with the same tools and output type, then create multiple instances with different models. A router sends simple queries to a cheaper model (GPT-4o mini) and complex queries to a stronger model (Claude Sonnet). Since Pydantic AI is model-agnostic, this takes 3 lines of code.

Observability: Pydantic AI integrates with Logfire (also from the Pydantic team) for tracing. Every agent call, tool execution, and retry becomes a traceable span. For teams already using other observability tools, Pydantic AI also works with OpenTelemetry.

Cost control: The result.usage() method returns exact token counts and costs. Teams log this per-request and set alerts on cost anomalies. Since output models are validated, you don’t burn tokens on retry loops from malformed responses (a common LangChain problem).

For more on how Pydantic AI fits into the broader agent framework landscape, see our comparison guide.


  • Pydantic AI brings FastAPI’s developer experience to AI agents — type-safe, model-agnostic, and production-first
  • Structured outputs are first-class — declare a Pydantic model, pass it as output_type, get validated objects back
  • Dependency injection via RunContext replaces global state and makes agents testable
  • TestModel enables deterministic testing — no API keys, no cost, no flaky tests in CI
  • Model-agnostic — switch between OpenAI, Anthropic, Google, or local models with one string change
  • Intentionally minimal — no built-in RAG, vector stores, or document loaders. Bring your own or combine with LangChain components
  • Best for: Teams that value type safety, testability, and clean architecture over a large pre-built ecosystem

Last updated: February 2026 | Pydantic AI v1.x / Python 3.10+

Frequently Asked Questions

What is Pydantic AI and how does it compare to LangChain?

Pydantic AI is a Python agent framework built by the Pydantic team that brings FastAPI's developer experience to GenAI — type-safe, model-agnostic, and production-focused. Compared to LangChain, Pydantic AI has fewer abstractions, uses Pydantic models for validated structured outputs, supports dependency injection for testable code, and works with any LLM provider. It is designed for engineers who want clean, typed agents without a massive framework.

How does Pydantic AI handle structured outputs?

Define a Pydantic BaseModel as the output_type when creating an agent. The framework ensures the LLM returns JSON matching your model's schema, with automatic validation and retry on malformed responses. This guarantees type-safe outputs — every field has the correct type, required fields are present, and the output can be used directly in downstream code without manual parsing or validation.

What is dependency injection in Pydantic AI?

Pydantic AI uses dependency injection to pass runtime context (database connections, user sessions, API clients) to agent tools without global state. Define a deps_type dataclass, pass dependencies when running the agent, and tools access them via RunContext. This makes agents testable (inject mock dependencies in tests) and avoids the global state patterns that make other frameworks hard to test.

When should I use Pydantic AI vs LangGraph?

Use Pydantic AI for straightforward agents with structured outputs, dependency injection, and type safety — especially when you want minimal framework overhead and testable code. Use LangGraph when you need complex state machines with cycles, conditional routing, human-in-the-loop interrupts, and multi-agent orchestration. Pydantic AI is simpler and more Pythonic; LangGraph is more powerful for complex workflows.

How do I get started with Pydantic AI?

Install with pip install pydantic-ai, then create an Agent with a model name, an output_type (a Pydantic BaseModel), and system instructions. Add tools as decorated Python functions with RunContext for dependency access. Call agent.run() with a user message and dependencies. The framework handles the LLM conversation loop, tool calls, and output validation automatically.

What are agent tools in Pydantic AI?

Agent tools are Python functions decorated with @agent.tool that give the agent access to external data and capabilities. Pydantic AI extracts the function signature and docstring to generate a tool schema for the LLM. Tools receive a RunContext parameter with typed access to dependencies, so they can query databases or call APIs without global state. The framework handles the tool call and response cycle automatically.

How do I test Pydantic AI agents without API keys?

Pydantic AI includes a built-in TestModel that returns deterministic, valid instances of your output type without calling any LLM API. Use agent.override(model=TestModel()) in your tests. This enables fast, free, and reliable CI/CD testing — no API keys needed, no flaky tests from API timeouts, and results are deterministic across runs.

Is Pydantic AI model-agnostic?

Yes, Pydantic AI supports OpenAI, Anthropic, Google Gemini, and local models via Ollama through a unified interface. Switching between models is a one-line change — you only modify the model name string. Your agent code, tools, output types, and dependencies remain identical regardless of the provider. See our agentic frameworks comparison for how it stacks up.

What are the limitations of Pydantic AI?

Pydantic AI is intentionally minimal and does not include pre-built RAG components, vector store integrations, or document loaders that LangChain provides. Complex output models with deeply nested structures can cause extra token usage during validation retries. The framework is async by default, and the ecosystem is newer with less community content compared to LangChain.

How is Pydantic AI used in production?

Common production patterns include structured API backends where the agent output_type doubles as the FastAPI response model, multi-model routing that sends simple queries to cheaper models and complex queries to stronger ones, and observability via Logfire or OpenTelemetry for tracing every agent call and tool execution. Teams also use agent architectures that combine Pydantic AI with RAG pipelines for knowledge retrieval.