Crafting Type-Safe LLM Agents: A Step-by-Step Guide with Pydantic AI

Introduction

If you've ever wrestled with raw, unstructured text responses from large language models (LLMs), you know the frustration of trying to extract reliable data. Pydantic AI offers a Pythonic solution by letting you build agents that return validated, structured outputs using familiar Pydantic models. Instead of parsing unpredictable strings, you get type-safe objects with automatic validation—a pattern that will feel instantly comfortable if you've used FastAPI or Pydantic before. In this guide, you'll learn to construct such agents step by step, from defining schemas to handling retries and selecting the best LLM provider.

Crafting Type-Safe LLM Agents: A Step-by-Step Guide with Pydantic AI
Source: realpython.com

What You Need

Step 1: Define Your Structured Output Model

Start by creating a BaseModel subclass that specifies exactly what data you want your LLM agent to return. This model acts as your schema, guaranteeing type safety and automatic validation.

from pydantic import BaseModel

class StockInfo(BaseModel):
    symbol: str
    price: float
    change_percent: float

Every field must be annotated with a Python type. Pydantic AI will enforce that the LLM's output conforms to this structure. If the model returns malformed data (e.g., a string where a float is expected), the agent can automatically retry (see Step 4).

Step 2: Register Tools with the @agent.tool Decorator

Tools are Python functions that your LLM agent can invoke to perform actions—like looking up stock prices or fetching weather data. Register them using the @agent.tool decorator:

from pydantic_ai import Agent

agent = Agent(model='gemini-1.5-pro', result_type=StockInfo)

@agent.tool
def get_stock_price(ctx, symbol: str) -> dict:
    """Retrieve the current price and change for a given stock symbol."""
    # Your API call or database query goes here
    return {'price': 150.25, 'change_percent': 1.34}

The decorator makes the function callable by the LLM. The docstring is crucial: it describes the tool's purpose and helps the LLM decide when to use it. The first argument ctx provides context about the conversation.

Step 3: Inject Dependencies with deps_type

Instead of relying on global state (which can cause issues in production), Pydantic AI supports dependency injection. Define a dependency class and pass it via deps_type:

class MyDeps(BaseModel):
    db_connection: str
    api_key: str

agent = Agent(
    model='gemini-1.5-pro',
    result_type=StockInfo,
    deps_type=MyDeps
)

@agent.tool
def get_stock_price(ctx, symbol: str) -> dict:
    # Use ctx.deps.db_connection and ctx.deps.api_key
    ...

This keeps your code clean, testable, and thread‑safe. When running the agent, you provide the actual dependencies:

deps = MyDeps(db_connection='sqlite:///stocks.db', api_key='sk-12345')
result = agent.run('What is the price of AAPL?', deps=deps)

Step 4: Enable Validation Retries for Reliability

LLMs sometimes return outputs that don't match your schema—missing fields or wrong types. Pydantic AI can automatically retry the query when validation fails:

Crafting Type-Safe LLM Agents: A Step-by-Step Guide with Pydantic AI
Source: realpython.com
agent = Agent(
    model='gemini-1.5-pro',
    result_type=StockInfo,
    retries=3  # default is 1
)

Each retry sends a new request to the LLM with details about the validation error, giving it a chance to correct its output. While this increases reliability, be aware that it also increases API costs. Adjust the retries parameter based on your tolerance for failures and budget.

Step 5: Choose the Right LLM Provider

Not all LLMs handle structured output equally well. For the best results with Pydantic AI, use one of these providers:

Other providers (local models, older APIs) may have limited or no structured output capabilities. Always test your agent with the chosen model to ensure it can reliably produce the desired schema.

Tips for Success

Tags:

Recommended

Discover More

The Silent Revolution: How Programming Changed and What Stayed the SameHow to Access and Compile the Earliest DOS Source Code Released by Microsoft4 Emerging Filmmakers Reveal How the iPhone 17 Pro Max Transforms StorytellingMicroVM Isolation: How Docker Sandboxes Secure AI AgentsMaximizing Your Pixel Watch 4: The Complete Guide to the Official USB-C Charger