PydanticAI Project Setup
2024-12-092 turns17,135 charso1
Summary
User wants to set up a new project using PydanticAI framework.
Messages
help me build a project with this: <div align="center">
<a href="https://ai.pydantic.dev/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://ai.pydantic.dev/img/pydantic-ai-dark.svg">
<img src="https://ai.pydantic.dev/img/pydantic-ai-light.svg" alt="PydanticAI">
</picture>
</a>
</div>
<div align="center">
<em>Agent Framework / shim to use Pydantic with LLMs</em>
</div>
<div align="center">
<a href="https://github.com/pydantic/pydantic-ai/actions/workflows/ci.yml?query=branch%3Amain"><img src="https://github.com/pydantic/pydantic-ai/actions/workflows/ci.yml/badge.svg?event=push" alt="CI"></a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/pydantic-ai"><img src="https://coverage-badge.samuelcolvin.workers.dev/pydantic/pydantic-ai.svg" alt="Coverage"></a>
<a href="https://pypi.python.org/pypi/pydantic-ai"><img src="https://img.shields.io/pypi/v/pydantic-ai.svg" alt="PyPI"></a>
<a href="https://github.com/pydantic/pydantic-ai"><img src="https://img.shields.io/pypi/pyversions/pydantic-ai.svg" alt="versions"></a>
<a href="https://github.com/pydantic/pydantic-ai/blob/main/LICENSE"><img src="https://img.shields.io/github/license/pydantic/pydantic-ai.svg?v" alt="license"></a>
</div>
---
**Documentation**: [ai.pydantic.dev](https://ai.pydantic.dev/)
---
When I first found FastAPI, I got it immediately. I was excited to find something so innovative and ergonomic built on Pydantic.
Virtually every Agent Framework and LLM library in Python uses Pydantic, but when we began to use LLMs in [Pydantic Logfire](https://pydantic.dev/logfire), I couldn't find anything that gave me the same feeling.
PydanticAI is a Python Agent Framework designed to make it less painful to build production grade applications with Generative AI.
## Why use PydanticAI
* Built by the team behind Pydantic (the validation layer of the OpenAI SDK, the Anthropic SDK, LangChain, LlamaIndex, AutoGPT, Transformers, CrewAI, Instructor and many more)
* Model-agnostic — currently OpenAI, Gemini, and Groq are supported. And there is a simple interface to implement support for other models.
* [Type-safe](https://ai.pydantic.dev/agents/#static-type-checking)
* Control flow and agent composition is done with vanilla Python, allowing you to make use of the same Python development best practices you'd use in any other (non-AI) project
* [Structured response](https://ai.pydantic.dev/results/#structured-result-validation) validation with Pydantic
* [Streamed responses](https://ai.pydantic.dev/results/#streamed-results), including validation of streamed _structured_ responses with Pydantic
* Novel, type-safe [dependency injection system](https://ai.pydantic.dev/dependencies/), useful for testing and eval-driven iterative development
* [Logfire integration](https://ai.pydantic.dev/logfire/) for debugging and monitoring the performance and general behavior of your LLM-powered application
## In Beta!
PydanticAI is in early beta, the API is still subject to change and there's a lot more to do.
[Feedback](https://github.com/pydantic/pydantic-ai/issues) is very welcome!
## Hello World Example
Here's a minimal example of PydanticAI:
```py
from pydantic_ai import Agent
# Define a very simple agent including the model to use, you can also set the model when running the agent.
agent = Agent(
'gemini-1.5-flash',
# Register a static system prompt using a keyword argument to the agent.
# For more complex dynamically-generated system prompts, see the example below.
system_prompt='Be concise, reply with one sentence.',
)
# Run the agent synchronously, conducting a conversation with the LLM.
# Here the exchange should be very short: PydanticAI will send the system prompt and the user query to the LLM,
# the model will return a text response. See below for a more complex run.
result = agent.run_sync('Where does "hello world" come from?')
print(result.data)
"""
The first known use of "hello, world" was in a 1974 textbook about the C programming language.
"""
```
_(This example is complete, it can be run "as is")_
Not very interesting yet, but we can easily add "tools", dynamic system prompts, and structured responses to build more powerful agents.
## Tools & Dependency Injection Example
Here is a concise example using PydanticAI to build a support agent for a bank:
**(Better documented example [in the docs](https://ai.pydantic.dev/#tools-dependency-injection-example))**
```py
from dataclasses import dataclass
from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
from bank_database import DatabaseConn
# SupportDependencies is used to pass data, connections, and logic into the model that will be needed when running
# system prompt and tool functions. Dependency injection provides a type-safe way to customise the behavior of your agents.
@dataclass
class SupportDependencies:
customer_id: int
db: DatabaseConn
# This pydantic model defines the structure of the result returned by the agent.
class SupportResult(BaseModel):
support_advice: str = Field(description='Advice returned to the customer')
block_card: bool = Field(description="Whether to block the customer's card")
risk: int = Field(description='Risk level of query', ge=0, le=10)
# This agent will act as first-tier support in a bank.
# Agents are generic in the type of dependencies they accept and the type of result they return.
# In this case, the support agent has type `Agent[SupportDependencies, SupportResult]`.
support_agent = Agent(
'openai:gpt-4o',
deps_type=SupportDependencies,
# The response from the agent will, be guaranteed to be a SupportResult,
# if validation fails the agent is prompted to try again.
result_type=SupportResult,
system_prompt=(
'You are a support agent in our bank, give the '
'customer support and judge the risk level of their query.'
),
)
# Dynamic system prompts can make use of dependency injection.
# Dependencies are carried via the `RunContext` argument, which is parameterized with the `deps_type` from above.
# If the type annotation here is wrong, static type checkers will catch it.
@support_agent.system_prompt
async def add_customer_name(ctx: RunContext[SupportDependencies]) -> str:
customer_name = await ctx.deps.db.customer_name(id=ctx.deps.customer_id)
return f"The customer's name is {customer_name!r}"
# `tool` let you register functions which the LLM may call while responding to a user.
# Again, dependencies are carried via `RunContext`, any other arguments become the tool schema passed to the LLM.
# Pydantic is used to validate these arguments, and errors are passed back to the LLM so it can retry.
@support_agent.tool
async def customer_balance(
ctx: RunContext[SupportDependencies], include_pending: bool
) -> float:
"""Returns the customer's current account balance."""
# The docstring of a tool is also passed to the LLM as the description of the tool.
# Parameter descriptions are extracted from the docstring and added to the parameter schema sent to the LLM.
balance = await ctx.deps.db.customer_balance(
id=ctx.deps.customer_id,
include_pending=include_pending,
)
return balance
... # In a real use case, you'd add more tools and a longer system prompt
async def main():
deps = SupportDependencies(customer_id=123, db=DatabaseConn())
# Run the agent asynchronously, conducting a conversation with the LLM until a final response is reached.
# Even in this fairly simple case, the agent will exchange multiple messages with the LLM as tools are called to retrieve a result.
result = await support_agent.run('What is my balance?', deps=deps)
# The result will be validated with Pydantic to guarantee it is a `SupportResult`, since the agent is generic,
# it'll also be typed as a `SupportResult` to aid with static type checking.
print(result.data)
"""
support_advice='Hello John, your current account balance, including pending transactions, is $123.45.' block_card=False risk=1
"""
result = await support_agent.run('I just lost my card!', deps=deps)
print(result.data)
"""
support_advice="I'm sorry to hear that, John. We are temporarily blocking your card to prevent unauthorized transactions." block_card=True risk=8
"""
```
## Next Steps
To try PydanticAI yourself, follow the instructions [in the examples](https://ai.pydantic.dev/examples/).
Read the [docs](https://ai.pydantic.dev/agents/) to learn more about building applications with PydanticAI.
Read the [API Reference](https://ai.pydantic.dev/api/agent/) to understand PydanticAI's interface.
Below is a step-by-step guide to help you create a small demonstration project using PydanticAI. We’ll walk through:
1. Setting up your environment
2. Installing and configuring PydanticAI
3. Building a simple agent
4. Extending the agent with tools, structured responses, and dependency injection
5. Running and testing the project
6. Ideas for further expansion
This guide assumes some familiarity with Python, virtual environments, and asynchronous programming, but we’ll keep it as accessible as possible.
## 1. Setting Up Your Environment
First, create a new directory and a virtual environment for your project:
```bash
mkdir pydantic_ai_example
cd pydantic_ai_example
python3 -m venv venv
source venv/bin/activate
```
(If you’re on Windows, use `venv\Scripts\activate` instead.)
## 2. Installing PydanticAI and Dependencies
PydanticAI is still in beta, so ensure you have a compatible Python version (likely 3.10+). Then, install it via pip:
```bash
pip install pydantic-ai
```
You may also need to install an LLM backend or have credentials for a model (e.g., an OpenAI API key if you’re using `openai:gpt-4o`).
For OpenAI models:
```bash
pip install openai
```
You’ll need to set your `OPENAI_API_KEY` as an environment variable:
```bash
export OPENAI_API_KEY="your-openai-api-key"
```
## 3. Building a Simple Agent
Create a file `main.py` and start with a “Hello World” style agent:
```python
from pydantic_ai import Agent
def main():
# Create a simple agent using OpenAI's GPT-4 (or another model if you prefer)
agent = Agent(
model='openai:gpt-4o',
system_prompt='You are a helpful assistant that responds concisely.'
)
# Run the agent synchronously for simplicity in this small demo
result = agent.run_sync("What is the origin of 'hello world'?")
print("Agent response:", result.data)
if __name__ == '__main__':
main()
```
Run this:
```bash
python main.py
```
You should see a concise response explaining the origin of "hello world."
## 4. Extending the Agent With Tools, Structured Responses, and Dependencies
To show off PydanticAI’s power, let’s build a more practical scenario. Suppose we’re creating a customer support agent for a fictional bank. The agent should:
- Greet the user by name (fetched from a “database”).
- Provide the user’s account balance.
- Return a structured response indicating what advice was given, a risk score, and whether to block the customer’s card.
### 4.1 Defining Dependencies and Mock Database
Create a new file `bank_database.py` to simulate a database:
```python
import asyncio
class DatabaseConn:
async def customer_name(self, id: int) -> str:
# Simulate DB query
await asyncio.sleep(0.1)
return "Alice Wonderland"
async def customer_balance(self, id: int, include_pending: bool) -> float:
await asyncio.sleep(0.1)
if include_pending:
return 1234.56
else:
return 1200.00
```
This is just a fake async DB implementation.
### 4.2 Defining the Result Model
In your main file, we’ll define a Pydantic model that the agent must return. Let’s call it `models.py`:
```python
from pydantic import BaseModel, Field
class SupportResult(BaseModel):
support_advice: str = Field(description="Advice returned to the customer")
block_card: bool = Field(description="Whether to block the customer's card")
risk: int = Field(description="Risk level of query", ge=0, le=10)
```
### 4.3 Defining the Dependencies Dataclass
Create a `deps.py` file for dependency injection:
```python
from dataclasses import dataclass
from bank_database import DatabaseConn
@dataclass
class SupportDependencies:
customer_id: int
db: DatabaseConn
```
### 4.4 Building the Enhanced Agent
Now, let’s update `main.py` to use a more complex agent:
```python
import asyncio
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
from bank_database import DatabaseConn
from models import SupportResult
from deps import SupportDependencies
from pydantic_ai import tool, system_prompt
# Create the agent
support_agent = Agent[SupportDependencies, SupportResult](
model='openai:gpt-4o',
deps_type=SupportDependencies,
result_type=SupportResult,
system_prompt=(
"You are a support agent in our bank, give the customer advice and judge the risk level of their query."
),
)
@system_prompt(support_agent)
async def add_customer_name(ctx: RunContext[SupportDependencies]) -> str:
customer_name = await ctx.deps.db.customer_name(id=ctx.deps.customer_id)
return f"The customer's name is {customer_name!r}."
@tool(support_agent)
async def customer_balance(ctx: RunContext[SupportDependencies], include_pending: bool) -> float:
"""
Returns the customer's current account balance.
Parameters:
include_pending: If true, include pending transactions in the balance.
"""
balance = await ctx.deps.db.customer_balance(
id=ctx.deps.customer_id,
include_pending=include_pending,
)
return balance
async def main():
# Create dependencies (in a real app, these might be generated dynamically)
deps = SupportDependencies(customer_id=123, db=DatabaseConn())
# Example 1: Customer asks about their balance
result = await support_agent.run("What is my current account balance?", deps=deps)
print("Agent response 1:", result.data)
# Example 2: Customer reports a lost card
result = await support_agent.run("I just lost my card!", deps=deps)
print("Agent response 2:", result.data)
if __name__ == '__main__':
asyncio.run(main())
```
### What’s Going On Here?
- We defined an `Agent` that requires `SupportDependencies` and produces a `SupportResult`.
- We used `@system_prompt` to dynamically add information (the customer’s name) into the system prompt before querying the model.
- We defined a `@tool` that the LLM can call to get the customer’s balance. The LLM will reason about when to call `customer_balance(include_pending=...)` and use the result in forming its final answer.
- Finally, we ran two scenarios: asking for the balance and reporting a lost card. The agent will produce a structured result that includes `support_advice`, `block_card`, and `risk`.
Run it again:
```bash
python main.py
```
You should see the agent return structured results that reflect the conversation. The output might look something like:
```
Agent response 1: support_advice='Hello Alice Wonderland, your current balance including pending transactions is $1234.56.' block_card=False risk=2
Agent response 2: support_advice='I am sorry to hear that, Alice Wonderland. We will block your card immediately to protect your account.' block_card=True risk=7
```
(This is just an example; the exact response may vary depending on the model’s creativity.)
## 5. Running and Testing the Project
Because we’ve structured the code with clear `Agent` definitions and dependency injections, it’s straightforward to test. You can:
- Write unit tests for your tools.
- Use mock dependencies for testing so you don’t rely on external services.
- Add type hints and run `mypy` to ensure everything type-checks cleanly.
For example, to run type checks:
```bash
pip install mypy
mypy .
```
## 6. Ideas for Further Expansion
- **Add More Tools:** Perhaps a tool to lock/unlock the card, a tool to update account details, or even call external APIs.
- **Streaming Responses:** Implement streaming to handle longer outputs in real-time.
- **Logging and Monitoring:** Integrate [Logfire](https://ai.pydantic.dev/logfire/) to monitor agent interactions, performance, and errors.
- **Web Frontend or CLI:** Present the agent’s responses via a FastAPI or Flask endpoint, or make a CLI chatbot.
- **Advanced Control Flow:** Use Python’s control structures and dependency injection to orchestrate complex workflows (e.g., multi-step verification, user identity checks).
## Conclusion
You now have a working example of a PydanticAI-based project that demonstrates agents, system prompts, tools, dependency injection, and structured responses. From here, you can refine your agent, add complexity, and integrate it into a larger application or service. The official [PydanticAI documentation](https://ai.pydantic.dev/) is an excellent next step for exploring advanced features and best practices.
o1