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

By

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

  • Python 3.9+ installed on your system
  • Pydantic AI library (pip install pydantic-ai)
  • An API key for at least one supported LLM provider: Google Gemini, OpenAI, or Anthropic
  • Basic familiarity with Python type hints and Pydantic models
  • A code editor of your choice

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:

  • Google Gemini – Excellent support for structured outputs natively.
  • OpenAI – Strong performance with function calling and response_format.
  • Anthropic – Great for complex instructions and consistent formatting.

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

  • Write clear docstrings for your tools. The LLM uses them to understand what each tool does. Include examples of expected inputs and outputs when possible.
  • Start with simple models. If your schema is too complex, the LLM may struggle. Gradually add fields as you verify correctness.
  • Monitor your API costs. Each retry uses another request. Use the minimum number of retries that still gives acceptable reliability (start with 2).
  • Use environment variables for API keys. Never hard‑code credentials into your dependency definitions.
  • Test with varied queries. Validate that your agent handles edge cases—like unknown stock symbols or malformed queries—gracefully.
  • Keep your dependency injection lightweight. Only pass what the tools actually need to avoid overhead.

Related Articles

Recommended

Discover More

Retailers Hide Prices Until Checkout, Study Reveals Consumer Frustration and WorkaroundsGoogle Home Automation Changes: What's Really Ending and What Isn't10 Critical Insights Into the PAN-OS Captive Portal Zero-Day (CVE-2026-0300)Apple Discontinues Top-Tier Mac Studio Memory Configurations Amid Supply Shortages and AI Demand SurgeSPIFFE: The Identity Backbone for Autonomous AI and Non-Human Entities