Most agent frameworks hand a language model a string and hope a dictionary comes back. Pydantic AI takes the opposite approach: you declare the shape of the answer first, and the framework holds the model to it. If the response doesn’t fit your schema, it never reaches your business logic.
That single design decision explains why the library has picked up traction so quickly among Python developers who already write FastAPI services. It is built by the team behind Pydantic itself, so it inherits the same instincts about validation, error messages and developer ergonomics that made Pydantic the default data layer for half the Python ecosystem.
What a Pydantic AI agent actually is
An agent in Pydantic AI is a small object that bundles four things: a model, a system prompt, a set of tools, and an output type. You construct it once, then call run() or run_sync() with a prompt. The library handles the request, tool calls, retries and parsing.
The output type is the interesting part. On a recent project I needed to triage support emails. Instead of asking for JSON and writing a fragile parser, the schema looked roughly like this: a severity field restricted to three literal values, a customer identifier, a one-sentence summary, and a boolean flag for whether a human should take over. When the model returned “urgent” instead of “high”, validation failed immediately with a readable error rather than three hours later in a dashboard. For a deeper walkthrough of that pattern, there’s a good write-up on type-safe AI agents in Python built the FastAPI way.
Validation failures become retries, not crashes
Here’s the piece people miss. Pydantic AI doesn’t just throw a ValidationError at you. It catches it, packages the error message, and sends it back to the model as feedback, then asks again. You control this with the retries parameter, which defaults to 1. In practice, setting retries to 2 or 3 cleans up nearly all malformed output from mid-tier models without any prompt engineering at all.
This is the difference between a 60-line while-loop that occasionally hallucinates field names and a component you can put behind an HTTP endpoint. If you’ve ever built the loop version, you know exactly where it breaks.
Dependency injection you already understand
Tools in Pydantic AI can be plain functions, or they can receive a RunContext that carries whatever dependencies you declared when creating the agent. Declare the dependency type as a database connection, an HTTP client, or a config object, and every tool gets it passed in without a global variable in sight.
That matters for testing. You can swap a real Postgres connection for a stub in a test and the agent code doesn’t change. It also matters for multi-tenancy: pass a user-scoped client per request and there’s no chance of one customer’s tool call touching another customer’s data.
Docstrings are prompts
Tool descriptions are generated from the function signature and docstring, so a sloppy docstring becomes a sloppy tool. Write them the way you’d explain the task to a colleague: what the tool does, what each argument means, and when not to use it. Developers routinely spend an hour tuning a system prompt while ignoring the four-word docstring that decides whether the model ever calls the right function.
Testing agents without burning tokens
This is where Pydantic AI earns its place in a serious codebase. It ships two test doubles. TestModel generates output that satisfies your schema, so you can assert that your downstream code handles every field correctly. FunctionModel lets you script exact responses, including tool calls, so you can test retry paths and multi-step flows deterministically.
A suite of forty tests that exercise agent behaviour, tool selection and validation retries runs in seconds with no API key and no bill. Compare that to the usual approach of mocking the SDK and hoping the real thing behaves the same.
- TestModel for schema conformance and downstream logic.
- FunctionModel for scripted tool-call sequences and error paths.
- message history inspection via the messages returned from a run, useful for asserting a tool was called with the right arguments.
- pydantic-evals for scored evaluations against a dataset once you move past unit tests.
How it sits next to LlamaIndex and Google ADK
No framework is right for everything, and it’s worth being honest about the trade-offs. Pydantic AI gives you almost nothing for retrieval. There’s no built-in vector store integration and no document pipeline. If your problem is mostly “reason over 40,000 internal PDFs”, a framework that treats indexing as a first-class concern will save you weeks, which is the territory covered in LlamaIndex agents and reasoning over your own data.
On the deployment side, if you’re standardising on Vertex AI and Gemini, the Google Agent Development Kit wires into that stack more directly than anything else will.
Pydantic AI’s sweet spot is the typed backend service: a FastAPI app where an LLM is one step in a workflow, the output feeds a database, and someone will page you at 2am if it breaks. It also handles multi-step control flow through pydantic-graph, a small state machine library, when a single agent call isn’t enough.
What production actually looks like
Two things come up on every real deployment. The first is observability. Pydantic AI integrates with Logfire out of the box, and you get a trace per run showing the prompt, every tool call, token counts and latency. Turning that on takes about three lines and is the single highest-value thing you can do before shipping.
The second is streaming. run_stream() gives you incremental output and structured validation at the end, which is what you want for a chat interface that needs to feel responsive without abandoning type safety. Once the agent is stable, wrapping it behind a clean HTTP interface is the next step, and the patterns in AI agent APIs explained cover the auth, rate limiting and error handling that agents specifically need.
Where it will frustrate you
Memory is not built in. Conversation persistence is your problem, usually a table with a message history column. Long-running background agents aren’t native either. And the abstraction over providers is good but not universal; unusual model features sometimes need a custom provider class. None of these are dealbreakers, but budget for them.
A realistic first week
Start narrower than feels comfortable. One task, one output model, one or two tools. The sequence that works:
- Write the output Pydantic model first, with tight types and Literal fields wherever the answer is constrained.
- Add one tool that reads real data, and write its docstring as carefully as the system prompt.
- Set retries to 2 and log every validation failure; those failures are your best source of prompt improvements.
- Write ten tests with TestModel before you write ten more prompts.
- Turn on tracing, then run it against twenty real inputs collected from actual users.
That fifth step is where most projects either accelerate or quietly stall. Twenty real inputs will show you the edge cases that no amount of schema design anticipates. If you want a broader map of the tooling around this stage of a build, the AI agent starter kit is a reasonable companion read.
The reason Pydantic AI agents hold up is boring and reliable: the model is allowed to be creative, but the boundary between the model and your code is a validator. Every field either parses or it doesn’t. Every tool call either has the right arguments or it doesn’t. You stop debugging prose and start debugging types, and that is a much better use of a Tuesday afternoon.

