Using Pydantic AI to build a ReAct agent
I wanted to get more familiar with Pydantic AI, so I decided to build a ReAct agent with multiple tools.
After a first failed attempt, I realized that Pydantic AI uses asyncio under the hood, so you need to enable nest_asyncio
to use it in a notebook.
Setup
Then, I did the imports as usual. I hadn’t used logfire
for monitoring LLM applications before, so I thought it’d be a good idea to try it out.
PydanticAI instrumentation uses OpenTelemetry (OTel). So it’s pretty straightforward to use it with Logfire or with any other OTel-compatible observability tool.
You just need to create a project in Logfire, generate a Write token
and add it to the .env
file. Then, you just need to run:
This will ask you to select a project the first time you run it. It will generate a logfire_credentials.json
file in your working directory. In following runs, it will automatically use the credentials from the file.
ReAct Agent
I decided to make an agent that had access to a tool to get the weather and another one that checks if the response that’s going to be sent to the user follows the company guidelines.
Here’s thee code:
from pydantic import BaseModel
class Feedback(BaseModel):
feedback: str
status: Literal['OK', 'REQUIRES FIXING']
evaluator_agent = Agent(
'openai:gpt-4.1-mini',
system_prompt=(
"You're a helpful assistant. Your task is to check if a given response follows the company guidelines. The company guidelines are that responses should be written in the style of a haiku. You should reply with 'OK' or 'REQUIRES FIXING' and a short explanation."
),
output_type=Feedback,
)
react_agent = Agent(
'openai:gpt-4.1-mini',
system_prompt=(
"You're a helpful assistant. Use the tools provided when relevant. Then draft a response and check if it follows the company guidelines. Only respond to the user after you've validated and modified the response if needed."
),
)
@react_agent.tool_plain
def get_weather(latitude: float, longitude: float) -> str:
"""Get the weather of a given latitude and longitude"""
response = requests.get(
f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m"
)
data = response.json()
return str(data["current"]["temperature_2m"])
@react_agent.tool_plain
def check_guidelines(drafted_response: str) -> str:
"""Check if a given response follows the company guidelines"""
response = evaluator_agent.run_sync(drafted_response)
return response.output
response = react_agent.run_sync("What is the temperature in Madrid?")
18:57:42.206 react_agent run
18:57:42.208 chat gpt-4.1-mini
18:57:43.191 running 1 tool
18:57:43.192 running tool: get_weather
18:57:43.408 chat gpt-4.1-mini
18:57:44.117 running 1 tool
18:57:44.118 running tool: check_guidelines
18:57:44.120 evaluator_agent run
18:57:44.120 chat gpt-4.1-mini
18:57:46.291 chat gpt-4.1-mini
And here’s the output:
The output in Logfire looks like typical observability tools:
That’s all!
You can access this notebook here.
Citation
@online{castillo2025,
author = {Castillo, Dylan},
title = {Using {Pydantic} {AI} to Build a {ReAct} Agent},
date = {2025-07-04},
url = {https://dylancastillo.co/til/react-agent-pydantic-ai.html},
langid = {en}
}