Skip to content

Instantly share code, notes, and snippets.

@anoochit
Last active July 16, 2025 02:09
Show Gist options
  • Save anoochit/75a6fc7be28f3551e949d242799db3b3 to your computer and use it in GitHub Desktop.
Save anoochit/75a6fc7be28f3551e949d242799db3b3 to your computer and use it in GitHub Desktop.
pydanticai workshop day1
marp theme paginate class
true
default
true
lead
invert
<style> @import url('https://fonts.com/css2?family=Bai+Jamjuree:ital,wght@0,200;0,300;0,400;0,500;0,600;1,200;1,300;1,400;1,500;1,600;1,700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap'); section { width: 1280px; height: 720px; font-size: 40px; font-family: "Bai Jamjuree"; } h1 { font-size: 38pt; } pre { font-family: "Open Sans"; } blockquote { font-size: 32px; } </style>

AI Agent Bootcamp - PydanticAI

Anuchit Chalothorn


Redeem DRAFT - PydanticAI Cookbook

Code : 2VFIT5ZXIGPLZGWK1TI5HZFXNUEJ


Introduction to PydanticAI

"If FastAPI is the framework that makes creating APIs fun, PydanticAI is the tool that makes building AI Agents just as enjoyable."


What is PydanticAI?

PydanticAI is a framework for building AI Agents in Python, designed to help developers create production-grade Generative AI applications quickly, correctly, and with a good structure.


Key Features of PydanticAI (1/2)

  • Built by the Pydantic Team: Developed by the same team that created Pydantic.
  • Model-agnostic: Connects with OpenAI, Anthropic, Gemini, Ollama, and more.
  • Integrates with Pydantic Logfire: Monitor Agent performance in real-time.
  • Safe with Type Checking: Agents and outputs are type-safe.

Key Features of PydanticAI (2/2)

  • Easy to write in a Pythonic style: Familiar Control Flow, Composition, Dependency Injection.
  • Manages results with a defined structure: Ensures consistent responses.
  • Supports streaming results: Ideal for applications requiring real-time responses.
  • Write graph-based code with Pydantic Graph: Manage complex flows with Type Hints.

First Step to an AI Agent: "Hello, World!"

Building an AI Agent starts with Structured Data Extraction.

PydanticAI acts as a bridge between:

  • The world of Unstructured Text from LLMs
  • The world of Structured Data in Pydantic

Prerequisites

  • Python 3.9 or higher
  • pydantic-ai and openai libraries
  • OpenAI API Key

1. Install Libraries

uv add "pydantic-ai-slim[openai]"

2. Set up API Key

Linux / macOS

export OPENAI_API_KEY="sk-..."

Windows (Powershell)

$env:OPENAI_API_KEY="sk-..."

3. Define Data Structure

Create a "blueprint" for the data with Pydantic.

# extract_user.py
from pydantic import BaseModel, Field

class User(BaseModel):
    """
    A model for storing user data extracted from text
    The text in this docstring will be passed to LLM for understanding
    """
    name: str = Field(description="Full name and surname of the user")
    age: int = Field(description="Age of the user in years")

Specifying docstring and description helps the LLM understand the meaning of each field.


4. Create and Run the Agent

Create an Agent to extract data from text according to the User model.

# extract_user.py (continued)
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel

llm = OpenAIModel('gpt-4o')
agent = Agent(llm)

prompt = "My name is John Doe and I will be 30 years old on my next birthday."

agent_run_result = agent.run_sync(prompt, output_type=User)
result: User = agent_run_result.output

print(result)

5. Test the Result

Run the file with the command:

python extract_user.py

Expected output:

Input Prompt: 'My name is John Doe and I will be 30 years old on my next birthday.'

--- Extracted Data ---
Name: John Doe
Age: 29

Type of result: <class '__main__.User'>
Object:  User(name='John Doe', age=29)
----------------------

Analyzing the Result

  1. Data Accuracy: The Agent understands that "will be 30" means the current age is 29.
  2. Result is a Pydantic Object: result is an instance of the User class, allowing direct access to result.name and result.age with automatic data validation.

How it Works Behind the Scenes

  1. Convert Model to Schema: PydanticAI converts the User model into a schema that the LLM understands (similar to Function Calling).
  2. Create Prompt: Creates a prompt containing the schema and instructions for the LLM to respond in a structured format.
  3. Process and Validate: Receives JSON from the LLM, creates a User instance, and validates it.

Summary

PydanticAI allows us to "declare what kind of data we want" and lets the Agent handle the complex parts of communicating with the LLM for us.

If you are looking for a tool that helps make working with LLMs more systematic, reusable, testable, and scalable in the long run, PydanticAI is the answer you shouldn't overlook.


Components of PydanticAI

The essential foundation for building flexible, efficient, and scalable AI applications.


Core Components

  1. Agent: The "brain" or main controller of the system.
  2. LLM Models: The connector to Large Language Model providers.
  3. Messages and Chat History: The mechanism for managing conversation context.

1. Agent

The main class that acts as the processing center.

  • Receives input from the user (Prompt)
  • Coordinates with the LLM
  • Decides when to use tools
  • Returns results in a structured format (Pydantic Model)

2. LLM Models

Designed to work with various LLMs through the Model class, which acts as an intermediary to communicate with each provider's API, making it easy to switch models.

  • OpenAI (gpt-4o, gpt-4.1-mini)
  • Google (gemini-2.5-flash)
  • Anthropic (claude-3-5-sonnet-latest)

Using OpenAI

Install: uv add "pydantic-ai-slim[openai]" Setup: export OPENAI_API_KEY='sk-...'


from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel

# Method 1: Use the model name directly
agent = Agent('openai:gpt-4o')

# Method 2: Create a Model instance
model = OpenAIModel('gpt-4o')
agent = Agent(model)

Using Google (Gemini)

Install: uv add "pydantic-ai-slim[google]" Setup: export GOOGLE_API_KEY='your-api-key'


from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel

model = GoogleModel('gemini-2.5-flash')
agent = Agent(model)

For Vertex AI, set GoogleProvider(vertexai=True)


Using Anthropic (Claude)

Install: uv add "pydantic-ai-slim[anthropic]" Setup: export ANTHROPIC_API_KEY='your-api-key'


from pydantic_ai import Agent
from pydantic_ai.models.anthropic import AnthropicModel

# Method 1
agent = Agent('anthropic:claude-3-5-sonnet-latest')

# Method 2
model = AnthropicModel('claude-3-5-sonnet-latest')
agent = Agent(model)

3. Messages and Chat History

The heart of creating a chatbot that can remember conversational context (Multi-turn conversation).

  • Message object: Manages messages in each turn.
  • message_history: A list of Message objects sent with new prompts to help the AI understand the context.

Chatbot with Conversation History

from pydantic_ai import Agent

agent = Agent('openai:gpt-4o')
conversation_history = []

while True:
    prompt = input("You: ")
    if prompt.lower() == "exit":
        break

    # 1. Send prompt with history
    response = agent.run_sync(
        prompt, 
        message_history=conversation_history
    )

    # 2. Update history with new messages
    conversation_history = response.new_messages()
    
    print(f"Bot: {response.output}")

Defining a Persona with a System Prompt

You can define a personality or role for the Agent from the start using system_prompt.


persona = "You are a pirate named Captain Jack, you are gruff and brash."

agent = Agent(
    'openai:gpt-4o', 
    system_prompt=persona
)

response = agent.run_sync("Introduce yourself.")
print(response.output)

Result: "I am Captain Jack! The greatest pirate of the seven seas. What can I do for you?"


Summary of Key Principles

  • Agent: The main controller.
  • LLM Models: Choose the desired model.
  • message_history: Used in run_sync() to send context.
  • response.new_messages(): Used to update the conversation history.
  • system_prompt: Used to define the Agent's personality.

Once you understand these components, you are ready to build more complex AI!


Empowering the Agent with Tools

Transforming the Agent from an "answer provider" to a "doer".


What are Function Tools?

They are Python functions that we give to the Agent, enabling it to:

  • Call Python functions that we create.
  • Access external APIs.
  • Interact with various systems.

This allows the Agent to perform real tasks, not just answer questions.


Example: Creating a Calculator for the Agent

LLMs are not good at math, so we create a new Tool.

# math_tools.py
def add(a: int, b: int) -> int:
    """Use this function to add two numbers."""
    return a + b

def subtract(a: int, b: int) -> int:
    """Use this function to subtract two numbers."""
    return a - b

Important: Type Hints and docstrings help the LLM understand what the Tool does.


Binding the Tool to the Agent

# math_agent.py
from pydantic_ai import Agent
from math_tools import add, subtract

# Bind Tools via a list when creating the Agent
agent = Agent("openai:gpt-4o-mini", tools=[add, subtract])

result = agent.run_sync("What is 125 plus 349?")
print(result.output) 
# Result: 125 plus 349 equals 474.

PydanticAI will automatically select and run the add tool.


Example: Fetching Weather Data from an API

Create a Tool to check the weather from OpenWeatherMap.

# weather_tool.py
import requests

def get_current_weather(location: str) -> str:
    """Get the current weather in a given location."""
    # ... (API call code) ...
    return response.json()

# Bind the Tool to the Agent
weather_agent = Agent('openai:gpt-4o', tools=[get_current_weather])
response = weather_agent.run_sync("What's the weather like in Bangkok?")
print(response.output)

The Agent will call the API, receive the JSON data, and then summarize it in an easy-to-understand language.


How Does the Agent Choose a Tool?

  1. PydanticAI converts the Tool (Python code) into a schema and sends it to the LLM.
  2. LLM analyzes the question and decides which Tool to use.
  3. PydanticAI receives the command from the LLM and calls the actual Python function.
  4. The result from the function is sent back to the LLM.
  5. LLM generates the final answer from that result.

Tools from Other Providers

PydanticAI can connect to other Tool Ecosystems:

  • MCP (Model Context Protocol): A standard for AI Agents.
  • LangChain Tools: Access LangChain's large library of tools.
  • ACI.dev Tools: Connect to business applications like Zendesk, Slack.

Creating a Basic Chatbot

Create a Chatbot that remembers and has a personality.


1. Giving the Agent a Memory (Chat History)

To enable the Chatbot to understand context and have a continuous conversation, we must use Chat History.

  • conversation_history: is a list of messages that have already been exchanged.
  • We send this history with every new question.
  • The LLM uses the history to understand the context.

Example: A Conversational Loop that Remembers

from pydantic_ai import Agent

agent = Agent('openai:gpt-4o')
conversation_history = [] # Start with an empty history

while True:
    prompt = input("You: ")
    if prompt.lower() == "exit": break
    
    # Send the prompt and history together
    response = agent.run_sync(prompt, message_history=conversation_history)
    
    # Update the history with the latest messages
    conversation_history = response.new_messages()
    
    print(f"Bot: {response.output}")

2. Giving the Chatbot a Personality (Persona)

Use system_prompt to define the Agent's role and personality traits.

# Define Persona
persona = "You are a pirate named Captain Jack, you are gruff and always end your sentences with 'Ahoy!'"

# Create Agent with System Prompt
agent = Agent('openai:gpt-4o', system_prompt=persona)

# Test conversation
response = agent.run_sync("What is PydanticAI?")
print(response.output)

Bot: PydanticAI? Never heard of it. If you mean Pydantic, that's a Python library... Ahoy!


RAG: Making Your Agent Smarter with Your Data

Retrieval-Augmented Generation


The Problem: The LLM Doesn't Know Your Private Data

LLMs have general knowledge, but they don't know about:

  • Internal company documents
  • The latest product information
  • Customer databases

RAG is the solution!


What is RAG?

It's like giving the LLM a "cheat sheet" or "reference document" before it answers a question.

Process:

  1. Retrieval: Search for relevant information from our "knowledge base" (Vector Store).
  2. Augmentation: Combine the found information with the original question.
  3. Generation: Send the augmented prompt to the LLM to generate an answer based on that information.

Step 1: Create a Knowledge Base (Vector Store)

We will convert our documents (e.g., .txt, .pdf) into a Vector Store.

  1. Prepare Documents: Create a pydanticai_info.txt file.
  2. Install Libraries:
    uv add langchain-community langchain-openai faiss-cpu

  1. Write a script to create the Vector Store:
    • Load documents
    • Split them into small pieces (Chunks)
    • Convert them into numbers (Embeddings)
    • Save to faiss_index_pydanticai

# create_vector_store.py
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

# 1. Load and split documents
loader = TextLoader("./pydanticai_info.txt")
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)

# 2. Create Embeddings and Vector Store
embeddings = OpenAIEmbeddings()
vector_store = FAISS.from_documents(chunks, embeddings)

# 3. Save
vector_store.save_local("faiss_index_pydanticai")

Step 2: Create the RAG Agent

Create an Agent with a Tool for searching the Vector Store.

# rag_agent.py
from pydantic_ai import Agent
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# 1. Load the previously created Vector Store
embeddings = OpenAIEmbeddings()
vector_store = FAISS.load_local("faiss_index_pydanticai", embeddings, ...)
retriever = vector_store.as_retriever()

# 2. Create a Tool for searching
def retrieve_pydanticai_info(query: str) -> str:
    """Find information about PydanticAI in the knowledge base."""
    docs = retriever.invoke(query)
    return "\n\n".join([doc.page_content for doc in docs])

# 3. Create the Agent with the Tool
rag_agent = Agent("openai:gpt-4o", tools=[retrieve_pydanticai_info])

Test the RAG Agent

# Ask a question that requires knowledge from the document
prompt = "What is the core philosophy of PydanticAI?"
response = rag_agent.run_sync(prompt)

print(response.output)

Result:

The core philosophy of PydanticAI is to combine the power of Pydantic for defining clear data structures with the reasoning capabilities of LLMs...

The Agent will call retrieve_pydanticai_info to search for information before answering, making the answer accurate and based on our document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment