PydanticAI Tutorial: A Type-Safe Agent Framework for LLM Apps

# Membangun Agen LLM yang Type-Safe dengan PydanticAI PydanticAI adalah framework agen dari tim di balik Pydantic, dirancang untuk membawa pengalaman pengembang yang sama seperti yang dibawa FastAPI...

By Ruby Abdullah · · tutorial
PydanticAILLMAI AgentsPydanticStructured OutputPython

Building Type-Safe LLM Agents with PydanticAI

PydanticAI is an agent framework from the team behind Pydantic, designed to bring the same developer experience that FastAPI brought to web APIs into the world of generative AI. This tutorial walks through its core concepts: type-safe agents, structured outputs, dependency injection, and tools, ending with a complete customer-support agent you can adapt to your own projects.

What PydanticAI Is and Why It Exists

The Pydantic team built PydanticAI because much of the Python AI ecosystem either reinvented validation poorly or wrapped LLM calls in loosely typed dictionaries. PydanticAI takes a different stance: lean on the type system, validate everything, and keep the API small enough to reason about.

A few design principles shape the framework:

  • Type-safe by design. Agents declare their dependency type and output type as generic parameters. Static checkers like mypy and Pyright understand them, and your editor autocompletes accordingly.
  • Model-agnostic. OpenAI, Anthropic, Google (Gemini), Groq, Mistral, Ollama, and others are supported behind a single Agent interface. Switching models is usually a one-line change.
  • FastAPI-like ergonomics. Dependency injection, decorators for tools and validators, and a clean separation between defining an agent and running it will feel familiar if you have used FastAPI.
  • Built on Pydantic. Structured outputs are plain Pydantic models, so you get validation, JSON schema generation, and serialization for free.
  • Production observability. First-class integration with Logfire gives you tracing and debugging without bolting on a separate framework.

PydanticAI is intentionally not a do-everything orchestration platform. It focuses on the agent loop, leaving you free to compose agents with your own application code.

Installation

PydanticAI requires Python 3.9 or newer. Install it with pip:

pip install pydantic-ai

The base package pulls in support for the major model providers. If you want a slimmer install, you can select only the providers you need:

pip install "pydantic-ai-slim[openai]"

pip install "pydantic-ai-slim[anthropic]"

pip install "pydantic-ai-slim[google]"

If you plan to use observability, install the Logfire extra as well:

pip install "pydantic-ai[logfire]"

Most providers read their API key from an environment variable:

export OPENAIAPIKEY="sk-..."

export ANTHROPICAPIKEY="sk-ant-..."

export GEMINIAPIKEY="..."

Your First Agent

An Agent is the central object. You give it a model identifier and an optional system prompt, then run it.

from pydanticai import Agent

agent = Agent(

"openai:gpt-4o",

systemprompt="You are a concise assistant. Answer in one or two sentences.",

)

result = agent.runsync("What is the capital of Indonesia?")

print(result.output)

Jakarta is the capital of Indonesia.

The model string follows the pattern provider:model-name. Swapping providers is a one-line change:

agent = Agent("anthropic:claude-3-5-sonnet-latest")

agent = Agent("google-gla:gemini-1.5-flash")

You can also pass a model instance directly if you need custom configuration such as a base URL or timeout. The string form is convenient for the common case.

Three Ways to Run an Agent

PydanticAI offers three run methods depending on your execution context:

import asyncio

Synchronous - convenient in scripts and notebooks

result = agent.runsync("Hello")

print(result.output)

Asynchronous - for async applications and concurrency

async def main():

result = await agent.run("Hello")

print(result.output)

asyncio.run(main())

Streaming - receive output incrementally as it is generated

async def streamexample():

async with agent.runstream("Tell me a short story") as response:

async for chunk in response.streamtext(delta=True):

print(chunk, end="", flush=True)

asyncio.run(streamexample())

All three return (or yield) the same rich result object, which carries the output, the message history, and token usage.

Related Articles

Instructor: Getting Structured Output from LLMs with Python

Instructor: Mendapatkan Structured Output dari LLM dengan Python Salah satu tantangan terbesar saat bekerja dengan Large...

Complete LangGraph Tutorial: Building Complex AI Agents

Tutorial Lengkap LangGraph: Membangun AI Agents yang Kompleks LangGraph adalah library dari LangChain untuk membangun st...

TRL Tutorial: LLM Post-Training with SFT, DPO, and Reward Modeling

Post-Training LLM dengan TRL: SFT, Reward Modeling, dan DPO Setelah sebuah base language model selesai dipretraining, mo...

Axolotl Tutorial: Configuration-Driven LLM Fine-Tuning

Fine-Tuning LLM Berbasis Konfigurasi dengan Axolotl Kebanyakan proyek fine-tuning dimulai dengan cara yang sama: seseora...