Skip to content
Learn Agentic AI13 min read0 views

Pydantic AI: Type-Safe Agent Framework with First-Class Data Validation

Explore PydanticAI's approach to building agents with typed tool parameters, structured result types, dependency injection, and the data validation guarantees that Pydantic is known for.

Why Type Safety Matters for Agents

Agent frameworks typically treat tool inputs and outputs as loosely typed dictionaries or strings. This works in demos but causes subtle bugs in production: a tool expects a date string in ISO format but receives a natural language date, or an agent returns a response missing required fields that downstream code depends on.

PydanticAI, built by the team behind Pydantic, brings the same type validation philosophy to agent development. Every tool parameter, dependency, and result type is validated at runtime using Pydantic models. If data does not match the expected schema, the framework catches it before it causes problems downstream.

Core Concepts

PydanticAI agents are built from four primitives: agents (the LLM wrapper), tools (functions the agent can call), dependencies (injectable context), and result types (structured output schemas).

from pydantic_ai import Agent

# Simplest possible agent
agent = Agent(
    "openai:gpt-4o",
    system_prompt="You are a helpful assistant.",
)

result = agent.run_sync("What is the capital of France?")
print(result.data)  # "The capital of France is Paris."

The agent wraps an LLM and provides a clean interface for synchronous, async, and streaming execution. But the real power comes from typed tools and results.

Typed Tools with Validation

Tools in PydanticAI have fully typed parameters that are validated before execution:

from pydantic_ai import Agent, RunContext
from pydantic import BaseModel
from datetime import date

class FlightSearch(BaseModel):
    origin: str
    destination: str
    departure_date: date
    passengers: int = 1

agent = Agent(
    "openai:gpt-4o",
    system_prompt="You help users search for flights.",
)

@agent.tool
async def search_flights(ctx: RunContext[None], search: FlightSearch) -> str:
    """Search for available flights."""
    # search.departure_date is guaranteed to be a valid date object
    # search.passengers is guaranteed to be an integer
    return f"Found 3 flights from {search.origin} to {search.destination} on {search.departure_date}"

When the LLM generates a tool call, PydanticAI validates the arguments against the FlightSearch model. If the LLM passes an invalid date or a negative passenger count, the validation error is sent back to the LLM to fix — not silently passed to your code.

Dependency Injection

PydanticAI has a built-in dependency injection system that lets you pass runtime context to tools without global state:

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
import httpx

@dataclass
class FlightDeps:
    api_client: httpx.AsyncClient
    user_id: str
    preferred_airline: str | None = None

agent = Agent(
    "openai:gpt-4o",
    system_prompt="You help users search for flights.",
    deps_type=FlightDeps,
)

@agent.tool
async def search_flights(ctx: RunContext[FlightDeps], search: FlightSearch) -> str:
    """Search for available flights."""
    # Access dependencies through ctx.deps
    response = await ctx.deps.api_client.get(
        "https://api.flights.example/search",
        params={
            "origin": search.origin,
            "dest": search.destination,
            "date": str(search.departure_date),
            "airline": ctx.deps.preferred_airline,
        },
    )
    return response.text

# Run with specific dependencies
async with httpx.AsyncClient() as client:
    deps = FlightDeps(
        api_client=client,
        user_id="user_123",
        preferred_airline="United",
    )
    result = await agent.run(
        "Find flights from SFO to JFK next Friday",
        deps=deps,
    )

The deps_type parameter is a generic type that flows through to RunContext. Your IDE provides autocomplete on ctx.deps, and the type checker validates that you pass the correct dependency object at runtime.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

Structured Result Types

Instead of parsing free-form LLM text, PydanticAI can enforce structured output:

from pydantic import BaseModel

class FlightRecommendation(BaseModel):
    airline: str
    flight_number: str
    departure_time: str
    price_usd: float
    reasoning: str

agent = Agent(
    "openai:gpt-4o",
    result_type=FlightRecommendation,
    system_prompt="Recommend the best flight option.",
)

result = agent.run_sync("Find me the cheapest flight from SFO to JFK tomorrow")
recommendation = result.data  # This is a FlightRecommendation instance
print(f"{recommendation.airline} {recommendation.flight_number}: ${recommendation.price_usd}")

The framework instructs the LLM to return JSON matching the Pydantic model schema and validates the response. If validation fails, PydanticAI retries with the validation error included in the prompt, giving the LLM a chance to correct its output.

Multi-Model Support

PydanticAI is model-agnostic. It supports OpenAI, Anthropic, Google Gemini, Groq, Mistral, and Ollama through a consistent interface:

# Switch models by changing a string
openai_agent = Agent("openai:gpt-4o", system_prompt="...")
anthropic_agent = Agent("anthropic:claude-sonnet-4-20250514", system_prompt="...")
gemini_agent = Agent("google-gla:gemini-2.0-flash", system_prompt="...")

All type validation, dependency injection, and result parsing work identically across providers.

When to Choose PydanticAI

PydanticAI is the strongest choice when data integrity matters. If your agents feed structured data into downstream systems, APIs, or databases, the type validation prevents an entire class of runtime errors. The dependency injection system also makes it excellent for agents that need access to authenticated HTTP clients, database connections, or user-specific configuration.

For simple chatbots or agents that return free-form text, the type safety overhead may not be worth it. But for any agent that produces structured output or interacts with typed APIs, PydanticAI eliminates the parsing and validation code you would otherwise write manually.

FAQ

How does PydanticAI handle LLM responses that fail validation?

PydanticAI sends the validation error back to the LLM with a prompt asking it to fix the output. It retries up to a configurable number of times. In practice, modern LLMs correct most validation errors on the first retry.

Can PydanticAI agents call other agents?

Yes. You can call one agent from within another agent's tool function. The inner agent can have its own dependencies and result type. This is a clean way to compose specialized agents.

How does PydanticAI compare to using Instructor?

Instructor focuses narrowly on structured LLM outputs using Pydantic models. PydanticAI is a full agent framework — it adds tools, dependency injection, conversation management, and multi-turn interactions on top of the structured output concept.


#PydanticAI #TypeSafety #AgentFrameworks #Python #DataValidation #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.