Pydantic AI 2026: Why Senior Python Devs Are Switching from LangChain

A complete guide to Pydantic AI in 2026: type-safe agents, structured outputs, dependency injection, tools, streaming, multi-agent systems, and how it stacks up against LangChain, LangGraph, and Instructor.

class Invoice(BaseModel): total: float vendor: str agent = Agent( 'openai:gpt-4o', result_type= Invoice ) // types validated
100%
Type-safe tool I/O
6+
Model providers
1
Agent class to learn
2024
First released

In This Guide

  1. What Is Pydantic AI?
  2. Why Pydantic AI in 2026
  3. Core Concepts
  4. Installation & First Agent
  5. Tools, Dependencies, & Type-Safe Function Calling
  6. Multi-Agent Systems & Streaming
  7. Pydantic AI vs LangChain vs LangGraph vs Instructor
  8. Testing & Evals with Pydantic AI
  9. Best Practices & When to Use It

Key Takeaways

01

What Is Pydantic AI?

Pydantic AI is an open-source Python agent framework built by the Pydantic team — the same group that built Pydantic itself, the validation library that quietly powers FastAPI, the OpenAI Python SDK, the Anthropic SDK, and a substantial fraction of every Python LLM app shipped in the last three years. It is a small, opinionated library for building production-grade LLM agents with full type safety on inputs, tool calls, and structured outputs.

The thesis is simple. If Pydantic models already define the shape of your API requests, your database rows, and your function signatures, those same models should define the shape of what your LLM produces. An agent run is just a function call with a return type — only the function happens to be an LLM and the return type happens to be parsed out of unstructured text. Pydantic AI makes that round-trip type-safe end to end.

The library entered public release in late 2024 and has iterated steadily since. Samuel Colvin, the creator of Pydantic, leads the project. By April 2026 it has become a serious option in the Python agent ecosystem — not the most expansive framework, not the flashiest, but the one a working Python developer is most likely to read and immediately understand.

100%
Type-safe contract on tool inputs, tool outputs, and final agent results — validated by Pydantic on every call
Mypy and Pyright catch contract violations at edit time; runtime validation catches the rest
02

Why Pydantic AI in 2026

Pydantic AI is positioning itself as "the FastAPI of AI" — a framework that takes a sprawling, ad-hoc problem space and gives Python developers a small set of well-shaped primitives that feel like the rest of the language.

The comparison is intentional. FastAPI did not invent the Python web framework — Flask and Django had been around for a decade. What FastAPI did was take Pydantic models, async, and type hints and weld them into a developer experience that felt obvious in hindsight. You wrote a function with typed parameters, and FastAPI handled validation, OpenAPI docs, and routing. Pydantic AI applies the same instinct to LLM agents.

The friction it removes is real. Most LLM bugs in production fall into a small set of categories: the model returned a string when you expected JSON, the JSON parsed but a required field was missing, a tool was called with arguments of the wrong type, a downstream function got handed None when it expected a string. Every one of those failure modes lives in the gap between the LLM's loose probabilistic output and the strict deterministic typing your application needs. Pydantic AI closes the gap by making that conversion the framework's job, not yours.

The 2026 trend Pydantic AI is riding: agents are moving from notebook prototypes to production services, and production services demand schemas, contracts, and type-safe boundaries. The teams shipping working agents in 2026 are not the ones who chained ten LangChain primitives in a notebook — they are the ones who treat the agent as one more typed function in a Python service. Pydantic AI is built for that team.

03

Core Concepts

Pydantic AI has a small surface area. Five concepts cover most of what you will write.

The Agent Class

An Agent is the central object. You create one with a model identifier, an optional system prompt, an optional dependency type, and an optional result type. The agent is the thing you call to run the model. A single Agent instance can be reused across many runs, which makes it cheap to create at module level and pass around your service.

Result Types

Pass a Pydantic model as result_type= and the agent's job is to produce an instance of that model. Pydantic AI handles the prompting, the function-calling protocol, the parsing, and the validation. If the model returns something that does not match the schema, Pydantic AI re-prompts with a validation error, up to a configurable retry limit. What you get back is a typed object — your IDE knows its fields, mypy checks them, and your downstream code does not have to defensively unpack a dictionary.

Tools

Tools are Python functions the agent can call. Decorate them with @agent.tool (with context) or @agent.tool_plain (no context). Pydantic AI inspects the function signature and turns it into a JSON Schema the LLM can target. Arguments are validated against that schema before your function ever runs. If the model hallucinates a non-existent argument or the wrong type, Pydantic AI rejects the call with a structured error and re-prompts.

Dependency Injection via RunContext

Tools that need external resources — a database connection, an HTTP client, an authenticated user, a request ID — receive them through RunContext. You declare a deps_type on the agent, pass an instance of that type to agent.run(..., deps=...), and every tool decorated with @agent.tool can read ctx.deps with full typing. No globals, no thread-local hacks, no monkey-patching.

Streaming

Use agent.run_stream to stream output as it is produced. For unstructured text this is a token stream. For structured output this is partial validation — Pydantic AI streams the JSON as it arrives, validates partial-models on the fly, and yields you progressively-complete instances. That last capability is unusual and worth remembering — it is one of the things that makes Pydantic AI feel different from older frameworks.

04

Installation & First Agent

Installation is one line. The first agent is about ten.

Step 1: Install

install.sh
bash
# Core install
pip install pydantic-ai

# Or pin a single provider's deps
pip install "pydantic-ai-slim[openai]"
pip install "pydantic-ai-slim[anthropic]"
pip install "pydantic-ai-slim[groq]"

The pydantic-ai meta-package pulls in clients for every supported provider. The pydantic-ai-slim variant is for production deployments where you want to install only the providers you actually use.

Step 2: A First Agent with Structured Output

first_agent.py
python
from pydantic import BaseModel, Field
from pydantic_ai import Agent


class Invoice(BaseModel):
    vendor: str = Field(description="Legal name of the vendor")
    invoice_number: str
    total_usd: float = Field(ge=0)
    line_items: list[str]


agent = Agent(
    'openai:gpt-4o',
    result_type=Invoice,
    system_prompt=(
        "Extract the structured invoice from the user's message. "
        "If a field is missing, infer the most likely value."
    ),
)

result = agent.run_sync(
    "Invoice 4471 from Acme Tooling LLC, $1,240.00 total, "
    "covering one steel jig and two days of labor."
)

print(result.data)
# Invoice(vendor='Acme Tooling LLC', invoice_number='4471',
#         total_usd=1240.0, line_items=['steel jig', 'two days of labor'])

print(type(result.data))
# <class '__main__.Invoice'>

Three things to notice. First, you never wrote a JSON-parsing line — Pydantic AI handles the function-calling protocol with the model and parses the result for you. Second, the return value is a real Invoice instance with full IDE autocomplete and mypy support. Third, the Field(ge=0) constraint means a hallucinated negative total is caught at validation time, before your downstream code ever sees it.

Step 3: Async and Streaming Variants

The agent has three run methods. agent.run_sync(prompt) is the synchronous version shown above. agent.run(prompt) is the async coroutine version for use inside async services. agent.run_stream(prompt) returns a context manager whose async iterator yields partial results as the model produces them. All three accept the same arguments and return the same shape of result — you pick the one that fits your runtime.

05

Tools, Dependencies, & Type-Safe Function Calling

Tools are how an agent does anything beyond producing text. Pydantic AI's tool model is decorator-based, dependency-injected, and fully typed.

A Tool With Dependencies

support_agent.py
python
from dataclasses import dataclass
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext


# 1. Declare what the agent has access to at runtime.
@dataclass
class SupportDeps:
    customer_id: str
    db: "Database"   # your async DB client


# 2. Declare what the agent must produce.
class SupportReply(BaseModel):
    message: str
    escalate: bool
    refund_cents: int = 0


# 3. Wire them into the Agent.
support_agent = Agent(
    'anthropic:claude-sonnet-4-6',
    deps_type=SupportDeps,
    result_type=SupportReply,
    system_prompt=(
        "You are a calm, accurate support agent. Use the customer "
        "lookup tool before issuing any refund. Never refund more "
        "than the customer's last order amount."
    ),
)


@support_agent.tool
async def customer_balance(ctx: RunContext[SupportDeps]) -> float:
    """Return the customer's current account balance in USD."""
    return await ctx.deps.db.balance_for(ctx.deps.customer_id)


@support_agent.tool
async def last_order_total(ctx: RunContext[SupportDeps]) -> float:
    """Return the dollar total of the customer's most recent order."""
    return await ctx.deps.db.last_order_total(ctx.deps.customer_id)


async def handle_ticket(customer_id: str, message: str, db) -> SupportReply:
    deps = SupportDeps(customer_id=customer_id, db=db)
    result = await support_agent.run(message, deps=deps)
    return result.data

Four design moves to notice. The deps_type means tools can read ctx.deps.db with full typing — your editor autocompletes balance_for. The result_type means the surrounding service does not have to guess at what the agent returned — it gets a typed SupportReply. Tool docstrings are shipped to the LLM as tool descriptions, so they double as documentation and as instructions to the model. And because tools are just decorated Python functions, they are unit-testable without ever invoking an LLM.

Pro Tip: Use RunContext for everything that should not be a global

The dependency-injection pattern is the single feature that separates throwaway agents from production agents. Database connections, HTTP clients, the current user, request-scoped tracing IDs, feature flags, tenant context — anything that varies per request goes into your deps dataclass. The agent stays a module-level singleton; the per-request state flows in through RunContext. This is the same pattern FastAPI taught a generation of Python developers, and Pydantic AI lets you keep using it inside the agent.

Tool Validation, Retries, and Errors

If a tool raises a ModelRetry exception, Pydantic AI feeds the message back to the model and asks it to try again. This is how you handle "the model called the tool with a customer ID that does not exist" — you raise ModelRetry("No customer found with that ID. Ask the user to confirm.") and the agent will course-correct. Other exceptions propagate normally and end the run.

06

Multi-Agent Systems & Streaming

Pydantic AI supports multi-agent workflows by composition. Agents call other agents the same way they call tools — through a typed function that happens to invoke a different model.

Delegating Between Agents

The pattern is straightforward. A "router" or "orchestrator" agent has a tool that calls a specialist agent. The specialist returns a Pydantic model. The orchestrator decides what to do with it. Because each agent is just a Python object, there is no special framework concept for delegation — it is regular function composition. A research agent might delegate to a search agent and a summarizer agent; a support agent might delegate to a refund-policy agent. Each handoff is a typed boundary.

Streaming Structured Output

streaming.py
python
import asyncio
from pydantic import BaseModel
from pydantic_ai import Agent


class Outline(BaseModel):
    title: str
    sections: list[str]


writer = Agent('google-gla:gemini-1.5-pro', result_type=Outline)


async def stream_outline():
    async with writer.run_stream("Outline a guide to FastAPI") as result:
        async for partial in result.stream():
            # `partial` is a partial Outline — fields populate as JSON arrives
            print(partial)


asyncio.run(stream_outline())

The interesting part is what partial contains at each step. Pydantic AI parses the JSON stream incrementally and validates each partial model. Early in the stream you might see Outline(title='Building APIs with FastAPI', sections=[]); a few hundred tokens later you might see Outline(title='Building APIs with FastAPI', sections=['Setup', 'Routes']); finally the full structure. This is genuinely useful for UIs that want to render fields as they arrive without waiting for a complete response.

Model-Agnostic by Design

The model identifier is a string. 'openai:gpt-4o', 'anthropic:claude-sonnet-4-6', 'google-gla:gemini-1.5-pro', 'groq:llama-3.3-70b-versatile', 'mistral:mistral-large-latest', 'cohere:command-r-plus', and any OpenAI-compatible endpoint via OpenAIModel with a custom base URL — Ollama, vLLM, LiteLLM, OpenRouter — all sit behind the same Agent interface. Switching providers is usually a one-line change to the model string. Your tools, your dependencies, and your result types do not move.

07

Pydantic AI vs LangChain vs LangGraph vs Instructor

The four libraries occupy different positions in the Python LLM stack. None of them is a strict replacement for any other — picking the right one starts with knowing what each was built to solve.

Capability Pydantic AI LangChain LangGraph Instructor
Primary purpose Type-safe agents LLM toolkit / ecosystem Stateful agent graphs Structured output
Surface area Small Very large Medium Tiny
Type safety on results First-class Optional Optional First-class
Tool calling Yes (decorator + Pydantic) Yes Yes No (output-focused)
Multi-agent / graph workflows Composition only Limited Best-in-class No
Dependency injection Built-in (RunContext) Manual State-based N/A
Streaming structured output Partial-model streaming Token streaming Token streaming Partial-model streaming
Observability Logfire / OTel LangSmith LangSmith BYO
Best fit Production agents in Python services Prototyping & ecosystem reach Long-running stateful workflows Just-give-me-typed-output

The decision framework that holds up in practice: reach for Instructor when all you need is "turn this LLM call into a typed object" — no tools, no agentic loop. Reach for Pydantic AI when you need an agent that calls tools, takes dependencies, and returns typed results, and you want it to read like normal Python. Reach for LangGraph when the workflow genuinely is a graph — branching, looping, human-in-the-loop checkpoints, durable state. Reach for LangChain when you need access to its enormous integration library and are willing to accept the surface-area cost.

A common pattern in 2026 is to combine them. Many teams use Pydantic AI for the agent loop, Instructor-style typed extraction for narrow LLM calls inside that loop, and LangGraph only for the orchestration layer when a workflow becomes genuinely state-driven.

08

Testing & Evals with Pydantic AI

Agents are notoriously hard to test because every call costs money, takes seconds, and produces non-deterministic output. Pydantic AI gives you tools to test agent logic without calling a model at all, and to evaluate model behavior systematically when you do.

Unit Testing With TestModel and FunctionModel

Pydantic AI ships test models that conform to the same interface as a real provider model but produce deterministic output you control. TestModel generates plausible structured data for any result type by inspecting the schema — useful for testing the surrounding code without caring what the LLM said. FunctionModel lets you provide a function that takes the messages and returns a response, which lets you simulate any model behavior — calling specific tools, returning specific structured results, raising errors.

test_support_agent.py
python
from pydantic_ai import models
from pydantic_ai.models.test import TestModel

from support_agent import support_agent, SupportDeps


def test_handles_basic_question(fake_db):
    deps = SupportDeps(customer_id="c_42", db=fake_db)

    # Override the agent's model for the duration of the test.
    with support_agent.override(model=TestModel()):
        result = support_agent.run_sync(
            "Where is my refund?", deps=deps,
        )

    assert isinstance(result.data.message, str)
    assert result.data.escalate in (True, False)

The agent.override() pattern makes the test path completely deterministic. No network calls, no token cost, no flakiness from a non-deterministic model. The test exercises your tool wiring, your dependency injection, and your result-type validation — all the parts of the agent that are your code, not the LLM's.

Evals

For the parts that are the LLM, Pydantic AI integrates with eval harnesses through Pydantic Evals — a separate package built by the same team — that lets you run an agent against a labeled dataset and score the results. You define cases (input plus expected output or expected behavior), evaluators (functions that score a single case), and the harness reports aggregate metrics. The pattern is familiar to anyone who has used pytest, just at the dataset level rather than the assertion level.

Logfire and Observability

Pydantic Logfire is the Pydantic team's observability product, and Pydantic AI integrates with it natively. Add logfire.configure() at app startup and every agent run, every tool call, every model call, and every validation error becomes a structured trace span. Because Logfire emits OpenTelemetry, you can also wire Pydantic AI traces into Datadog, Honeycomb, Grafana, or any OTel-compatible backend — the integration is not Logfire-locked. For production agents, this is the difference between "we have an agent in prod" and "we know what our agent is doing in prod."

09

Best Practices & When to Use It

Pydantic AI is at its best when the workload looks like a typed function call dressed up as an agent. Below is the honest playbook — when to reach for it, when to reach for something else.

Use Pydantic AI When

Reach For Something Else When

The Production Playbook

Six habits that separate Pydantic AI experiments from Pydantic AI in production. One: create the Agent at module level, never per-request — agents are cheap to create but creating them per-request is wasted work. Two: always declare a result_type, even if it is just a single-field model — strings as agent outputs are a code smell. Three: always declare a deps_type, even if you do not use it yet — adding it later is a breaking change to every tool. Four: raise ModelRetry for recoverable tool errors and let normal exceptions propagate for unrecoverable ones — the model will course-correct on the first, fail loudly on the second. Five: wire Logfire from day one — debugging an agent without traces is brutal, and the pricing is friendly to small teams. Six: write TestModel-based unit tests for your tool wiring and Pydantic Evals for your model behavior — the two test types catch different bugs and you need both.

Sources: Pydantic AI Documentation, pydantic/pydantic-ai on GitHub, Pydantic Logfire. API surface verified against pydantic-ai releases through April 2026; model identifiers and pricing are subject to change as providers iterate.

Explore More Guides

Bottom Line
Pydantic AI is the framework to reach for when you want LLM agents that look and behave like the rest of your typed Python service — small surface area, schema-validated I/O, dependency-injected tools, model-agnostic by design. It is not the most expansive framework. It is the one a working Python developer is most likely to read once and immediately ship.
PA
Our Take

Pydantic AI is not yet LangChain-sized — and that is precisely why working Python teams are picking it.

The honest read on adoption in April 2026: LangChain is still the default Python LLM library by raw download volume, by tutorial count, and by integration breadth. If you searched "Python LLM agents" eighteen months ago, every answer pointed to LangChain. That has not collapsed — but it has shifted. Working teams who actually need to ship reliable agents into production services have been quietly moving the agent layer to Pydantic AI, Instructor, or the model providers' native SDKs, while keeping LangChain around for retrievers, vector store wrappers, and integrations they do not want to rebuild. The reason is not that LangChain is bad. It is that LangChain optimized for breadth, and breadth is expensive when the cost is a sprawling abstraction surface that changes underneath you.

Pydantic AI's bet is the opposite — small surface, well-shaped primitives, schema-driven contracts, and the boring discipline of treating the agent like a normal typed function. That bet is paying off in the kind of teams who think about mypy in CI, who run agents inside FastAPI services, and who consider observability a Day-1 problem. Those teams find Pydantic AI immediately legible. A senior Python developer can read the docs in an evening, write a real agent in an afternoon, and have it tested and instrumented by the end of the week. That is genuinely rare in this space.

The fair caution: Pydantic AI is younger and the ecosystem is smaller. There are fewer Stack Overflow answers, fewer YouTube tutorials, and fewer "I built X with Pydantic AI" blog posts than for LangChain. If you hit a weird edge case, the community is smaller and the answer might require reading the source. But the source is small and clean, the maintainers are responsive, and the trajectory is unmistakably upward. For 2026, the right move for most production Python teams is: use Pydantic AI for the agent loop, use Instructor or native APIs for narrow typed extraction, reach for LangGraph only when the workflow is genuinely a graph, and accept LangChain as a useful integration library rather than a framework to live inside.

PA

About the Publisher

Precision AI Academy

Practitioner-focused AI education · tech news, guides, and 145 free courses

Precision AI Academy publishes deep-dives on applied AI engineering for working professionals — including engineers serving federal agencies and Fortune 500 companies. Founded by Bo Peng — Kaggle Top 200 data scientist and former university instructor.

Kaggle Top 200 Federal AI Practitioner 5 U.S. Cities Thu–Fri Cohorts