PydanticAI in Your Python App: Why, When, and How
Every AI builder hits the same wall: you’ve chosen your LLM, but how do you actually wire it into your app without creating a brittle mess?
I’ve been there across multiple projects — from innovation platforms at Brightidea to my own side apps like Resume Wizard. The question always comes up:
👉 Which framework do I wrap around the model?
LangChain? LangGraph? Haystack? Or something lighter?
That’s where PydanticAI comes in. It’s not the biggest framework, but it’s become my go-to when I need strong contracts and predictable outputs. Here’s why it might be the best choice for your app (and when it’s not).
What is PydanticAI?
PydanticAI is a lightweight agent framework from the Pydantic team. It gives you:
- Agents → encapsulate prompt, tools, backend, and schema.
- Schema-first I/O → use Pydantic models to define strict output formats.
- Tool calling → expose Python functions with type hints as callable tools for the LLM.
It’s designed for developers who want strong contracts, predictable outputs, and minimal abstractions.
Install:
pip install pydantic-ai
Why use PydanticAI instead of LangChain?
Strengths
✅ Strict outputs — define schemas with Pydantic and get guaranteed JSON outputs.
✅ Minimal surface area — far less boilerplate than LangChain.
✅ Function tools — wrap Python callables as tools with validated input/output.
✅ Testing & DX — strong typing, validation, and better IDE hints.
Trade-offs
⚠️ Smaller ecosystem — LangChain has more integrations (retrievers, loaders, connectors).
⚠️ Less orchestration — no native graph engine like LangGraph.
Bottom line:
- If you want clean, type-safe, production-ready outputs → PydanticAI shines.
- If you need a sprawling graph of agents, routers, and integrations → LangChain/LangGraph might be better.
🚀 Quick Start Example
from typing import List
from pydantic import BaseModel
from pydantic_ai import Agent, Tool, Model
from pydantic_ai import NativeOutput
# 1) Define structured output
class Answer(BaseModel):
reasoning: str
answer: str
citations: List[str] = []
# 2) Define a tool
class SearchRequest(BaseModel):
query: str
@Tool
def web_search(req: SearchRequest) -> List[str]:
return [f"https://example.com/search?q={req.query}"]
# 3) Create the agent
agent = Agent[Answer](
model=Model("openai:gpt-4o-mini"),
system_prompt="You are a helpful assistant. Use web_search when needed.",
tools=[web_search],
output=NativeOutput[Answer](),
)
# 4) Use it in your app
async def ask_llm(question: str) -> Answer:
result = await agent.run(user_message=question)
return result.data
Integration Patterns
- Service wrapper: Keep your agent inside
app/ai.py
, expose one typed function. - DTOs at boundaries: Map LLM outputs into domain models.
- Retries & guardrails: Wrap
agent.run()
with retries; enforce business logic post-validation. - Observability: Log messages, tool calls, and token usage.
- Evaluation: Snapshot test prompts and expected results for CI checks.
When to Use PydanticAI
- You need typed, validated outputs your backend can trust.
- You want simple tool calling with type hints.
- Your workflows are linear or lightly branched.
- You value minimal abstractions and testability.
When Not To
- You need complex, multi-agent graphs with routers, retries, and long-running workflows.
- You rely heavily on integrations (PDF loaders, databases, retrievers).
Many teams actually pair them:
- LangGraph for orchestration.
- PydanticAI for strict schema-based steps.
💡 Best Practices
- Prefer native structured outputs if your provider supports them (OpenAI, Gemini).
- Keep tools idempotent; log side effects explicitly.
- Version schemas (
AnswerV1
,AnswerV2
) alongside prompts. - Validate business rules after Pydantic parsing.
✍️ My Take
In my own work, I’ve found that PydanticAI hits the sweet spot between control and simplicity. For Resume Wizard, it’s the easiest way to ensure my LLM outputs stay valid JSON resumes instead of malformed text. For Brightidea Spark, it keeps AI features predictable enough to put in front of enterprise customers.
I still reach for LangGraph when I need heavy orchestration or routing — but for 80% of use cases, PydanticAI is my default wrapper.
🚀 The Takeaway
If your AI features are API-like (inputs → validated JSON outputs, with occasional tool calls), PydanticAI is likely the best wrapper right now: fast to ship, easy to test, and less magic.
And if you do need the orchestration power of LangChain or LangGraph? Pair them — let LangGraph handle the workflow, and let PydanticAI enforce the contracts.
That’s the balance I’ve landed on.