|
| 1 | +# Introduction {.hide} |
| 2 | + |
1 | 3 | --8<-- "docs/.partials/index-header.html"
|
2 | 4 |
|
3 |
| -# PydanticAI {.hide} |
| 5 | +When I first found FastAPI, I got it immediately, I was excited to find something so genuinely innovative and yet ergonomic built on Pydantic. |
| 6 | + |
| 7 | +Virtually every Agent Framework and LLM library in Python uses Pydantic, but when we came to use Gen AI in [Pydantic Logfire](https://pydantic.dev/logfire), I couldn't find anything that gave me the same feeling. |
| 8 | + |
| 9 | +PydanticAI is a Python Agent Framework designed to make it less painful to build production grade applications with Generative AI. |
4 | 10 |
|
5 |
| -You can think of PydanticAI as an Agent Framework or a shim to use Pydantic with LLMs — they're the same thing. |
| 11 | +## Why use PydanticAI |
6 | 12 |
|
7 |
| -PydanticAI tries to make working with LLMs feel similar to building a web application. |
| 13 | +* Built by the team behind Pydantic (the validation layer of the OpenAI SDK, the Anthropic SDK, Langchain, LlamaIndex, AutoGPT, Transformers, Instructor and many more) |
| 14 | +* Multi-model — currently with OpenAI and Gemini are support, Anthropic [coming soon](https://github.com/pydantic/pydantic-ai/issues/63), simply interface to implement other models or adapt existing ones |
| 15 | +* Type-safe |
| 16 | +* Built on tried and tested best practices in Python |
| 17 | +* Structured response validation with Pydantic |
| 18 | +* Streamed responses, including validation of streamed structured responses with Pydantic |
| 19 | +* Novel, type-safe dependency injection system |
| 20 | +* Logfire integration |
8 | 21 |
|
9 | 22 | !!! example "In Beta"
|
10 | 23 | PydanticAI is in early beta, the API is subject to change and there's a lot more to do.
|
11 | 24 | [Feedback](https://github.com/pydantic/pydantic-ai/issues) is very welcome!
|
12 | 25 |
|
| 26 | +## Example — Hello World |
| 27 | + |
| 28 | +Here's a very minimal example of PydanticAI. |
| 29 | + |
| 30 | +```py title="hello_world.py" |
| 31 | +from pydantic_ai import Agent |
| 32 | + |
| 33 | +agent = Agent('gemini-1.5-flash', system_prompt='Be concise, reply with one sentence.') |
| 34 | + |
| 35 | +result = agent.run_sync('Where does "hello world" come from?') |
| 36 | +print(result.data) |
| 37 | +""" |
| 38 | +The first known use of "hello, world" was in a 1974 textbook about the C programming language. |
| 39 | +""" |
| 40 | +``` |
| 41 | +_(This example is complete, it can be run "as is")_ |
| 42 | + |
| 43 | +Not very interesting yet, but we can easily add retrievers, dynamic system prompts and structured responses to build more powerful agents. |
| 44 | + |
13 | 45 | ## Example — Retrievers and Dependency Injection
|
14 | 46 |
|
15 |
| -Partial example of using retrievers to help an LLM respond to a user's query about the weather: |
| 47 | +Small but complete example of using PydanticAI to build a support agent for a bank. |
| 48 | + |
| 49 | +```py title="bank_support.py" |
| 50 | +from dataclasses import dataclass |
16 | 51 |
|
17 |
| -```py title="weather_agent.py" |
18 |
| -import httpx |
| 52 | +from pydantic import BaseModel, Field |
19 | 53 |
|
20 | 54 | from pydantic_ai import Agent, CallContext
|
21 | 55 |
|
22 |
| -weather_agent = Agent( # (1)! |
| 56 | +from bank_database import DatabaseConn |
| 57 | + |
| 58 | + |
| 59 | +@dataclass |
| 60 | +class SupportDependencies: # (3)! |
| 61 | + customer_id: int |
| 62 | + db: DatabaseConn |
| 63 | + |
| 64 | + |
| 65 | +class SupportResult(BaseModel): |
| 66 | + support_advice: str = Field(description='Advice returned to the customer') |
| 67 | + block_card: bool = Field(description='Whether to block their') |
| 68 | + risk: int = Field(description='Risk level of query', ge=0, le=10) |
| 69 | + |
| 70 | + |
| 71 | +support_agent = Agent( # (1)! |
23 | 72 | 'openai:gpt-4o', # (2)!
|
24 |
| - deps_type=httpx.AsyncClient, # (3)! |
25 |
| - system_prompt='Be concise, reply with one sentence.', # (4)! |
| 73 | + deps_type=SupportDependencies, |
| 74 | + result_type=SupportResult, # (9)! |
| 75 | + system_prompt=( # (4)! |
| 76 | + 'You are a support agent in our bank, give the ' |
| 77 | + 'customer support and judge the risk level of their query. ' |
| 78 | + "Reply using the customer's name." |
| 79 | + ), |
26 | 80 | )
|
27 | 81 |
|
28 | 82 |
|
29 |
| -@weather_agent.retriever_context # (5)! |
30 |
| -async def get_location( |
31 |
| - ctx: CallContext[httpx.AsyncClient], |
32 |
| - location_description: str, |
33 |
| -) -> dict[str, float]: |
34 |
| - """Get the latitude and longitude of a location by its description.""" # (6)! |
35 |
| - response = await ctx.deps.get('https://api.geolocation...') |
36 |
| - ... |
37 |
| - |
38 |
| - |
39 |
| -@weather_agent.retriever_context # (7)! |
40 |
| -async def get_weather( |
41 |
| - ctx: CallContext[httpx.AsyncClient], |
42 |
| - lat: float, |
43 |
| - lng: float, |
44 |
| -) -> dict[str, str]: |
45 |
| - """Get the weather at a location by its latitude and longitude.""" |
46 |
| - response = await ctx.deps.get('https://api.weather...') |
47 |
| - ... |
48 |
| - |
49 |
| - |
50 |
| -async def main(): |
51 |
| - async with httpx.AsyncClient() as client: |
52 |
| - result = await weather_agent.run( # (8)! |
53 |
| - 'What is the weather like in West London and in Wiltshire?', |
54 |
| - deps=client, |
55 |
| - ) |
56 |
| - print(result.data) # (9)! |
57 |
| - #> The weather in West London is raining, while in Wiltshire it is sunny. |
58 |
| - |
59 |
| - messages = result.all_messages() # (10)! |
| 83 | +@support_agent.system_prompt # (5)! |
| 84 | +async def add_customer_name(ctx: CallContext[SupportDependencies]) -> str: |
| 85 | + customer_name = await ctx.deps.db.customer_name(id=ctx.deps.customer_id) |
| 86 | + return f"The customer's name is {customer_name!r}" |
| 87 | + |
| 88 | + |
| 89 | +@support_agent.retriever_context # (6)! |
| 90 | +async def customer_balance( |
| 91 | + ctx: CallContext[SupportDependencies], include_pending: bool |
| 92 | +) -> str: |
| 93 | + """Returns the customer's current account balance.""" # (7)! |
| 94 | + balance = await ctx.deps.db.customer_balance( |
| 95 | + id=ctx.deps.customer_id, |
| 96 | + include_pending=include_pending, |
| 97 | + ) |
| 98 | + return f'${balance:.2f}' |
| 99 | + |
| 100 | + |
| 101 | +... # (11)! |
| 102 | + |
| 103 | + |
| 104 | +deps = SupportDependencies(customer_id=123, db=DatabaseConn()) |
| 105 | +result = support_agent.run_sync('What is my balance?', deps=deps) # (8)! |
| 106 | +print(result.data) # (10)! |
| 107 | +""" |
| 108 | +support_advice='Hello John, your current account balance, including pending transactions, is $123.45.' block_card=False risk=1 |
| 109 | +""" |
| 110 | + |
| 111 | +result = support_agent.run_sync('I just lost my card!', deps=deps) |
| 112 | +print(result.data) |
| 113 | +""" |
| 114 | +support_advice="I'm sorry to hear that, John. We are temporarily blocking your card to prevent unauthorized transactions." block_card=True risk=8 |
| 115 | +""" |
60 | 116 | ```
|
61 | 117 |
|
62 |
| -1. An agent that can tell users about the weather in a particular location. Agents combine a system prompt, a response type (here `str`) and "retrievers" (aka tools). |
63 |
| -2. Here we configure the agent to use OpenAI's GPT-4o model, you can also customise the model when running the agent. |
64 |
| -3. We specify the type dependencies for the agent, in this case an HTTP client, which retrievers will use to make requests to external services. PydanticAI's system of dependency injection provides a powerful, type safe way to customise the behaviour of your agents, including for unit tests and evals. |
65 |
| -4. Static system prompts can be registered as key word arguments to the agent, dynamic system prompts can be registered with the `@agent.system_prompot` decorator and benefit from dependency injection. |
66 |
| -5. Retrievers let you register "tools" which the LLM may call while to respond to a user. You inject dependencies into the retriever with `CallContext`, any other arguments become the tool schema passed to the LLM, Pydantic is used to validate these arguments, errors are passed back to the LLM so it can retry. |
67 |
| -6. This docstring is also passed to the LLM as a description of the tool. |
68 |
| -7. Multiple retrievers can be registered with the same agent, the LLM can choose which (if any) retrievers to call in order to respond to a user. |
69 |
| -8. Run the agent asynchronously, conducting a conversation with the LLM until a final response is reached. You can also run agents synchronously with `run_sync`. Internally agents are all async, so `run_sync` is a helper using `asyncio.run` to call `run()`. |
70 |
| -9. The response from the LLM, in this case a `str`, Agents are generic in both the type of `deps` and `result_type`, so calls are typed end-to-end. |
71 |
| -10. [`result.all_messages()`](message-history.md) includes details of messages exchanged, this is useful both to understand the conversation that took place and useful if you want to continue the conversation later — messages can be passed back to later `run/run_sync` calls. |
| 118 | +1. An [agent](agents.md) that acts as first-tier support in a bank, agents are generic in the type of dependencies they take and the type of result they return, in this case `Deps` and `SupportResult`. |
| 119 | +2. Here we configure the agent to use [OpenAI's GPT-4o model](api/models/openai.md), you can also customise the model when running the agent. |
| 120 | +3. The `SupportDependencies` dataclass is used to pass data and connections into the model that will be needed when running [system prompts](agents.md#system-prompts) and [retrievers](agents.md#retrievers). PydanticAI's system of dependency injection provides a powerful, type safe way to customise the behaviour of your agents, including for unit tests and evals. |
| 121 | +4. Static [system prompts](agents.md#system-prompts) can be registered as keyword arguments to the agent |
| 122 | +5. dynamic [system prompts](agents.md#system-prompts) can be registered with the `@agent.system_prompot` decorator and benefit from dependency injection. |
| 123 | +6. [Retrievers](agents.md#retrievers) let you register "tools" which the LLM may call while responding to a user. You inject dependencies into the retriever with [`CallContext`][pydantic_ai.dependencies.CallContext], any other arguments become the tool schema passed to the LLM, Pydantic is used to validate these arguments, errors are passed back to the LLM so it can retry. |
| 124 | +7. The docstring is also passed to the LLM as a description of the tool. |
| 125 | +8. [Run the agent](agents.md#running-agents) synchronously, conducting a conversation with the LLM until a final response is reached. |
| 126 | +9. The response from the agent will, be guaranteed to be a `SupportResult`, if validation fails [reflection](agents.md#reflection-and-self-correction) will mean the agent is prompted to try again. |
| 127 | +10. 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. |
| 128 | +11. In real use case, you'd add many more retrievers to the agent to extend the context it's equipped with and support it can provide. |
72 | 129 |
|
73 |
| -!!! tip "Complete `weather_agent.py` example" |
74 |
| - This example is incomplete for the sake of brevity; you can find a complete `weather_agent.py` example [here](examples/weather-agent.md). |
| 130 | +!!! tip "Complete `bank_support.py` example" |
| 131 | + This example is incomplete for the sake of brevity (the definition of `DatabaseConn` is missing); you can find a complete `bank_support.py` example [here](examples/bank-support.md). |
75 | 132 |
|
76 |
| -## Example — Result Validation |
| 133 | +## Next Steps |
77 | 134 |
|
78 |
| -TODO |
| 135 | +To try PydanticAI yourself, follow instructions [in examples](examples/index.md). |
0 commit comments