Building Production-Ready AI Agents with Pydantic AI
Overview: Beyond the Chatbot
Building AI applications often involves wrestling with unpredictable text outputs. While Large Language Models (LLMs) like
Prerequisites
To follow this tutorial, you should have a solid grasp of Python 3.10+, specifically asynchronous programming with asyncio. You should also be familiar with Type Hinting and the basic concepts of Pydantic data validation. Finally, you will need an OpenAI API Key to power the agent's reasoning.

Key Libraries & Tools
- Pydantic AI: A framework for building robust AI agents with structured validation.
- Pydantic: Used for defining data models and validating agent outputs.
- OpenAI GPT-4: The foundational model used for reasoning and natural language processing.
- Asyncio: Python's standard library for writing concurrent code using the async/await syntax.
Code Walkthrough: The Triage Agent
1. Defining Dependencies and Models
First, we establish the scaffolding. We define what the agent needs to know (dependencies) and what it must return (output model).
from pydantic import BaseModel, Field
from dataclasses import dataclass
@dataclass
class TriageDependencies:
patient_id: int
db_conn: "DatabaseConnection"
class TriageOutput(BaseModel):
response_text: str = Field(description="Message to the patient")
escalate: bool = Field(description="Whether to escalate to a human")
urgency: int = Field(ge=1, le=10, description="Urgency level")
2. Initializing the Agent
We initialize the Agent class by specifying the model, dependencies, and the expected output type.
from pydantic_ai import Agent
triage_agent = Agent(
'openai:gpt-4o',
deps_type=TriageDependencies,
result_type=TriageOutput,
system_prompt="You are a triage assistant assessing patient urgency."
)
3. Injecting Context and Tools
Dynamic prompts and tools allow the agent to fetch real-time data. The @triage_agent.system_prompt decorator lets you pull patient-specific info, while @triage_agent.tool gives the LLM the ability to "call" functions like fetching vitals.
@triage_agent.system_prompt
async def add_patient_name(ctx: RunContext[TriageDependencies]) -> str:
name = await ctx.deps.db_conn.get_patient_name(ctx.deps.patient_id)
return f"The patient's name is {name}."
@triage_agent.tool
async def get_vitals(ctx: RunContext[TriageDependencies]) -> str:
return await ctx.deps.db_conn.get_latest_vitals(ctx.deps.patient_id)
Syntax Notes: RunContext
The RunContext is a pivotal generic type in Pydantic AI. It carries your custom dependencies through the agent's lifecycle, ensuring that your tools and dynamic prompts always have access to your database or API clients without relying on global variables.
Practical Examples
This pattern is ideal for Financial Risk Assessment, where an agent must pull a credit score and return a structured 'approve/deny' decision, or Automated Customer Support, where the agent queries a shipment database to provide precise tracking updates rather than generic hallucinations.
Tips & Gotchas
- Parenthesis Pitfalls: Code completion tools often struggle with the nested structure of agent definitions; double-check your closing brackets.
- Graph Complexity: While Pydantic AI supports complex graph-based workflows, start with a single agent. Only move to nodes and edges if your logic is too complex for tools and dynamic prompts.