agent-builder-pydantic-ai
Build conversational AI agents using Pydantic AI + OpenRouter. Use when creating type-safe Python agents with tool calling, validation, and streaming.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Pydantic AI Agent Builder
Purpose
Create production-ready AI agents with type safety, automatic validation, and minimal boilerplate using Pydantic AI framework.
When to Use
- Building FastAPI backend with AI capabilities
- Need strict type checking and validation
- Want auto-retry on malformed LLM responses
- Creating agents with custom tools
Architecture Pattern
Project Structure
backend/
├── agents/
│ ├── __init__.py
│ ├── base_agent.py # Base agent class
│ └── [feature]_agent.py # Feature-specific agents
├── tools/
│ ├── __init__.py
│ └── [tool_name].py # Tool definitions
└── config/
└── agent_config.py # Agent configurations
Installation
pip install pydantic-ai httpx pydantic python-dotenv
Base Agent Pattern
from pydantic_ai import Agent
from pydantic import BaseModel
import os
class AgentResponse(BaseModel):
result: str
confidence: float
agent = Agent(
model='openrouter:openai/gpt-4o',
output_type=AgentResponse,
tools=[tool1, tool2],
system_prompt="You are a helpful AI assistant."
)
# Usage
result = await agent.run("user message")
Integration with OpenRouter
Setup
import os
from pydantic_ai.models import OpenRouterModel
model = OpenRouterModel(
name='openai/gpt-4o',
api_key=os.getenv('OPENROUTER_API_KEY'),
http_referer=os.getenv('FRONTEND_URL')
)
Environment Variables
OPENROUTER_API_KEY=sk-or-v1-...
FRONTEND_URL=http://localhost:3000
Tool Definition Pattern
from pydantic import BaseModel, Field
from pydantic_ai import Agent, Tool
class GenerateImageArgs(BaseModel):
prompt: str = Field(description="Image description")
num_images: int = Field(ge=1, le=10, default=1)
async def generate_image_tool(args: GenerateImageArgs) -> dict:
# Your implementation
return {"images": [...]}
# Register tool
agent.add_tool(
Tool(
name="generate_image",
description="Generate images using AI",
parameters=GenerateImageArgs,
execute=generate_image_tool
)
)
Streaming Pattern
async def stream_response(agent, message):
async for chunk in agent.stream(message):
yield {
"type": "text" if isinstance(chunk, str) else "tool_call",
"content": chunk
}
Error Handling & Retry
from pydantic_ai import Agent, RetryConfig
agent = Agent(
model='openrouter:openai/gpt-4o',
retry_config=RetryConfig(
max_retries=3,
retry_on=[ValidationError, TimeoutError]
)
)
# Auto-retry on validation errors
try:
result = await agent.run("user message")
except ValidationError as e:
# Will retry automatically
logger.error(f"Validation failed after retries: {e}")
FastAPI Integration
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class ChatRequest(BaseModel):
message: str
history: list = []
@app.post("/chat")
async def chat_endpoint(request: ChatRequest):
try:
result = await agent.run(
request.message,
context={"history": request.history}
)
return {"response": result.result}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Testing Pattern
import pytest
from pydantic_ai import Agent
@pytest.mark.asyncio
async def test_agent_response():
agent = Agent(
model='openrouter:openai/gpt-4o',
system_prompt="You are a test assistant"
)
result = await agent.run("Say hello")
assert "hello" in result.lower()
Best Practices
- Type Safety: Always define Pydantic models for inputs/outputs
- Dependency Injection: Use FastAPI-style DI for tools
- Auto-Retry: Configure retry logic for robustness
- Logging: Add structured logging for debugging
- Testing: Write pytest tests for agent behaviors
- Validation: Let Pydantic handle validation automatically
- Context: Pass context dict for stateful conversations
Example: Complete Agent
from pydantic_ai import Agent, Tool
from pydantic import BaseModel, Field
import os
# Output type
class ChatResponse(BaseModel):
message: str
tool_used: str | None = None
confidence: float = Field(ge=0, le=1)
# Tool definition
class WeatherArgs(BaseModel):
city: str
async def get_weather(args: WeatherArgs) -> dict:
# Your API call here
return {"temp": 72, "condition": "sunny"}
# Create agent
agent = Agent(
model='openrouter:openai/gpt-4o',
output_type=ChatResponse,
system_prompt="You are a helpful weather assistant."
)
# Register tool
agent.add_tool(
Tool(
name="get_weather",
description="Get current weather for a city",
parameters=WeatherArgs,
execute=get_weather
)
)
# Usage
if __name__ == "__main__":
result = await agent.run("What's the weather in SF?")
print(result.message)
Common Pitfalls
❌ Don't: Use any type
✅ Do: Define strict Pydantic models
❌ Don't: Handle retries manually ✅ Do: Configure RetryConfig
❌ Don't: Parse LLM output manually ✅ Do: Let Pydantic AI handle it
Resources
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!