Pydantic AI 2.0 Essentials: Building Type-Safe AI Applications
Pydantic AI 2.0 is a framework for developing AI applications with type safety and validation built in. It enables structured outputs from Large Language Models (LLMs) and integrates seamlessly with Pydantic models.
What is Pydantic AI?
Pydantic AI is a Python library built on top of Pydantic and designed specifically for working with LLMs. It provides:
- Type-Safe AI Outputs: AI responses are automatically converted to Pydantic models
- Validation: Inputs and outputs are validated against schemas
- Multi-Provider Support: OpenAI, Anthropic, Groq, and local models (Ollama, vLLM)
- Streaming: Real-time streaming of structured data
- Tool Calling: Built-in support for function calling
Why Use Pydantic AI?
Typical Users
Pydantic AI is ideal for:
- Python developers who already use Pydantic and value type safety
- Backend developers who want to integrate structured AI outputs into APIs
- Data engineers who need AI-powered data extraction and validation
- AI application developers who require reliable, validated outputs
Who Benefits?
Who gets the most value from Pydantic AI in a project?
- End users: Receive consistent, validated data instead of unstructured text responses
- Developers: Save time on validation and error handling
- Systems: Integration is simpler since outputs are already typed
- QA teams: Fewer tests needed since validation happens automatically
Project Considerations: When Should You Use Pydantic AI?
Before integrating Pydantic AI into your project, ask yourself these questions:
1. Do you need structured outputs?
- ✅ Yes: Pydantic AI is perfect if you need AI responses in specific formats (JSON, objects, enums)
- ❌ No: If you only need plain text, Pydantic AI might be overkill
2. Are you already using Python and Pydantic?
- ✅ Yes: Pydantic AI integrates seamlessly into your existing ecosystem
- ❌ No: If you don’t use Python, Pydantic AI isn’t suitable (Python-specific)
3. How important is type safety for your project?
- ✅ Critical: Pydantic AI offers compile-time and runtime validation
- ⚠️ Moderate: Pydantic AI can help, but alternatives like LangChain might suffice
- ❌ Not important: If you don’t need strict typing, simpler alternatives are better
4. Do you plan to support multiple providers?
- ✅ Yes: Pydantic AI makes it simple to switch between OpenAI, Anthropic, Groq, and local models
- ❌ No: If you’re locked into one provider, this isn’t a deciding factor
5. How complex is your AI integration?
- ✅ Simple to moderate: Pydantic AI excels at structured outputs and tool calling
- ⚠️ Very complex: For sophisticated multi-agent systems, LangChain or LangGraph might be better suited
Decision Matrix
| Requirement | Pydantic AI | LangChain | Direct API |
|---|---|---|---|
| Structured Outputs | ✅ Optimal | ⚠️ Possible | ❌ Manual |
| Type Safety | ✅ Native | ⚠️ Limited | ❌ None |
| Simplicity | ✅ High | ⚠️ Moderate | ⚠️ Moderate |
| Multi-Agent | ⚠️ Limited | ✅ Strong | ❌ No |
| Provider Agnostic | ✅ Yes | ✅ Yes | ❌ No |
| Learning Curve | 🟢 Low | 🟡 Moderate | 🟡 Moderate |
When NOT to Use Pydantic AI
- You don’t use Python
- You only need plain text without structure
- You require complex multi-agent orchestration (use LangGraph)
- You want minimal dependencies
- Your project is small and simple (direct API is enough)
When to Use Pydantic AI
- You’re already using Python and Pydantic
- You need reliable, validated AI outputs
- Type safety matters to you
- You want the flexibility to switch LLM providers
- You’re building APIs with AI integration
- You want tool calling with validated parameters
Real-World Example: Company with API Using Langdock
Within a company, I use FastAPI with Langdock to create a GDPR-compliant environment. The chat works, chatbots work. So why consider Pydantic AI?
Answer: Probably not in your current scenario.
If your chatbot only returns plain text and doesn’t need to produce structured data, Pydantic AI is unnecessary. Langdock and FastAPI already handle GDPR compliance and chat functionality.
When Pydantic AI could still be useful:
Let’s say we expand the scenario slightly:
# Currently: Just plain text
response = "Hello! I can help you."
# With Pydantic AI: Structured outputs
from pydantic import BaseModel
from pydantic_ai import Agent
class SupportTicket(BaseModel):
category: str # e.g., "technical", "billing", "hr"
priority: str # e.g., "high", "medium", "low"
description: str
assigned_to: str | None = None
# AI analyzes the request and returns structured data
ticket = agent.run_sync(
"My computer won't start, I need help urgently!",
result_type=SupportTicket
)
# SupportTicket(category='technical', priority='high', description='My computer won\'t start...', assigned_to=None)
Concrete example for your company:
- Without Pydantic AI: Chatbot returns text → You must parse it to identify categories
- With Pydantic AI: Chatbot returns a
SupportTicketobject directly → You can save it to your database without parsing
Takeaway for your scenario:
- If you only need chat: No Pydantic AI required
- If you want to extract structured data from chats (tickets, forms, reports): Pydantic AI very useful
Key insight:
A chatbot doesn’t need Pydantic AI. An AI agent usually does. Typical use cases are code assistants, research agents, automations, workflows with multiple tools, or applications that require typed and validated outputs.
Installation
pip install pydantic-ai
For specific providers:
pip install pydantic-ai[openai] # OpenAI
pip install pydantic-ai[anthropic] # Anthropic
pip install pydantic-ai[openai,anthropic] # Both
Basics: Structured Outputs
Simple Example
from pydantic import BaseModel
from pydantic_ai import Agent
class UserResponse(BaseModel):
name: str
age: int
email: str
agent = Agent('openai:gpt-4o')
result = agent.run_sync(
'Create a user profile for a developer',
result_type=UserResponse
)
print(result.data)
# UserResponse(name='Max Mustermann', age=28, email='max@example.com')
With System Prompt
from pydantic_ai import Agent, SystemPrompt
agent = Agent(
'openai:gpt-4o',
system_prompt=SystemPrompt('You are a helpful assistant for developers.')
)
result = agent.run_sync(
'Create a profile for a Python developer',
result_type=UserResponse
)
More Complex Models
Nested Structures
from typing import List
from pydantic import BaseModel
class Skill(BaseModel):
name: str
years_experience: int
level: str # beginner, intermediate, advanced
class DeveloperProfile(BaseModel):
name: str
role: str
skills: List[Skill]
github_url: str | None = None
available_for_hire: bool
agent = Agent('openai:gpt-4o')
result = agent.run_sync(
'Erstelle ein detailliertes Profil für einen Senior Python-Entwickler',
result_type=DeveloperProfile
)
Enum Validation
from enum import Enum
from pydantic import BaseModel
class SkillLevel(str, Enum):
BEGINNER = 'beginner'
INTERMEDIATE = 'intermediate'
ADVANCED = 'advanced'
EXPERT = 'expert'
class Skill(BaseModel):
name: str
level: SkillLevel
Multi-Provider Support
OpenAI
from pydantic_ai import Agent
agent = Agent('openai:gpt-4o')
result = agent.run_sync('Hallo Welt!')
Anthropic
agent = Agent('anthropic:claude-3-5-sonnet-20241022')
result = agent.run_sync('Hallo Welt!')
Local Models (Ollama)
agent = Agent('ollama:llama3.2')
result = agent.run_sync('Hallo Welt!')
Groq
agent = Agent('groq:llama-3.1-70b-versatile')
result = agent.run_sync('Hallo Welt!')
Tool Calling
Simple Tool
from pydantic_ai import Agent, Tool
def get_weather(location: str) -> str:
"""Holt das Wetter für einen Ort."""
# In der Realität: API-Aufruf
return f'In {location} sind es 22°C.'
agent = Agent('openai:gpt-4o', tools=[Tool(get_weather)])
result = agent.run_sync('Wie ist das Wetter in Berlin?')
With Pydantic Models
from pydantic import BaseModel
class WeatherQuery(BaseModel):
location: str
unit: str = 'celsius'
def get_weather(query: WeatherQuery) -> str:
return f'In {query.location} sind es 22°{query.unit}.'
agent = Agent('openai:gpt-4o', tools=[Tool(get_weather)])
Streaming
Text Streaming
agent = Agent('openai:gpt-4o')
async for chunk in agent.run_stream('Erzähle mir eine Geschichte'):
print(chunk.content, end='')
Structured Streaming
async for chunk in agent.run_stream(
'Erstelle ein Benutzerprofil',
result_type=UserResponse
):
if chunk.content:
print(chunk.content, end='')
Error Handling
Validation Errors
from pydantic import ValidationError
try:
result = agent.run_sync(
'Erstelle ein Profil',
result_type=UserResponse
)
except ValidationError as e:
print(f'Validierungsfehler: {e}')
Retry Logic
from pydantic_ai import Agent, RetryPolicy
agent = Agent(
'openai:gpt-4o',
retry_policy=RetryPolicy(max_retries=3)
)
Best Practices
1. Define Clear Models
# ✅ Good
class UserProfile(BaseModel):
name: str
email: str
age: int
# ❌ Bad
class Response(BaseModel):
data: dict # Keine Type-Safety
2. Use System Prompts
agent = Agent(
'openai:gpt-4o',
system_prompt=SystemPrompt(
'Du bist ein technischer Dokumentations-Assistent. '
'Antworte präzise und strukturiert.'
)
)
3. Leverage Validation
from pydantic import field_validator
class UserProfile(BaseModel):
email: str
@field_validator('email')
def validate_email(cls, v):
if '@' not in v:
raise ValueError('Ungültige E-Mail')
return v
4. Optimize Costs
# Smaller models for simple tasks
agent_simple = Agent('openai:gpt-4o-mini')
# Larger models for complex tasks
agent_complex = Agent('openai:gpt-4o')
Integration with Existing Projects
FastAPI Integration
from fastapi import FastAPI
from pydantic_ai import Agent
app = FastAPI()
agent = Agent('openai:gpt-4o')
@app.post('/generate')
async def generate(prompt: str):
result = await agent.run(prompt)
return {'response': result.content}
Async/Await
import asyncio
async def main():
agent = Agent('openai:gpt-4o')
result = await agent.run('Hallo Welt!')
print(result.content)
asyncio.run(main())
Common Mistakes
1. Missing API Keys
import os
from pydantic_ai import Agent
# Set API key
os.environ['OPENAI_API_KEY'] = 'sk-...'
agent = Agent('openai:gpt-4o')
2. Invalid Models
# ✅ Correct
agent = Agent('openai:gpt-4o')
# ❌ Wrong
agent = Agent('openai:gpt-5') # Modell existiert nicht
3. Missing Type Hints
# ✅ With Type Hints
def get_weather(location: str) -> str:
return f'Wetter in {location}'
# ❌ Without Type Hints
def get_weather(location):
return f'Wetter in {location}'
Pydantic AI vs. Alternatives
| Feature | Pydantic AI | LangChain | LlamaIndex |
|---|---|---|---|
| Type-Safety | ✅ Native | ⚠️ Limited | ⚠️ Limited |
| Pydantic Integration | ✅ Full | ⚠️ Partial | ⚠️ Partial |
| Multi-Provider | ✅ Easy | ✅ Yes | ✅ Yes |
| Streaming | ✅ Yes | ✅ Yes | ✅ Yes |
| Tool Calling | ✅ Native | ✅ Yes | ✅ Yes |
| Learning Curve | 🟢 Low | 🟡 Medium | 🟡 Medium |


