docs/dependencies.md
Pydantic AI uses a dependency injection system to provide data and services to your agent's system prompts, tools and output validators.
Matching Pydantic AI's design philosophy, our dependency system tries to use existing best practice in Python development rather than inventing esoteric "magic", this should make dependencies type-safe, understandable, easier to test, and ultimately easier to deploy in production.
Dependencies can be any python type. While in simple cases you might be able to pass a single object as a dependency (e.g. an HTTP connection), [dataclasses][] are generally a convenient container when your dependencies included multiple objects.
Here's an example of defining an agent that requires dependencies.
(Note: dependencies aren't actually used in this example, see Accessing Dependencies below)
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent
@dataclass
class MyDeps: # (1)!
api_key: str
http_client: httpx.AsyncClient
agent = Agent(
'openai:gpt-5.2',
deps_type=MyDeps, # (2)!
)
async def main():
async with httpx.AsyncClient() as client:
deps = MyDeps('foobar', client)
result = await agent.run(
'Tell me a joke.',
deps=deps, # (3)!
)
print(result.output)
#> Did you hear about the toothpaste scandal? They called it Colgate.
deps_type argument of the [Agent constructor][pydantic_ai.agent.Agent.init]. Note: we're passing the type here, NOT an instance, this parameter is not actually used at runtime, it's here so we can get full type checking of the agent.deps parameter.(This example is complete, it can be run "as is" — you'll need to add asyncio.run(main()) to run main)
Dependencies are accessed through the [RunContext][pydantic_ai.tools.RunContext] type, this should be the first parameter of system prompt functions etc.
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.AsyncClient
agent = Agent(
'openai:gpt-5.2',
deps_type=MyDeps,
)
@agent.system_prompt # (1)!
async def get_system_prompt(ctx: RunContext[MyDeps]) -> str: # (2)!
response = await ctx.deps.http_client.get( # (3)!
'https://example.com',
headers={'Authorization': f'Bearer {ctx.deps.api_key}'}, # (4)!
)
response.raise_for_status()
return f'Prompt: {response.text}'
async def main():
async with httpx.AsyncClient() as client:
deps = MyDeps('foobar', client)
result = await agent.run('Tell me a joke.', deps=deps)
print(result.output)
#> Did you hear about the toothpaste scandal? They called it Colgate.
RunContext][pydantic_ai.tools.RunContext] may optionally be passed to a [system_prompt][pydantic_ai.agent.Agent.system_prompt] function as the only argument.RunContext][pydantic_ai.tools.RunContext] is parameterized with the type of the dependencies, if this type is incorrect, static type checkers will raise an error..deps][pydantic_ai.tools.RunContext.deps] attribute..deps][pydantic_ai.tools.RunContext.deps] attribute.(This example is complete, it can be run "as is" — you'll need to add asyncio.run(main()) to run main)
In addition to [.deps][pydantic_ai.tools.RunContext.deps], [RunContext][pydantic_ai.tools.RunContext] provides access to the running agent via [.agent][pydantic_ai.tools.RunContext.agent], which is useful when tools, hooks, or capabilities need to read agent properties like [name][pydantic_ai.agent.Agent.name] or [output_type][pydantic_ai.agent.Agent.output_type].
Dependency fields can also be referenced in instructions and descriptions via template strings — for example, TemplateStr('Hello {{name}}') renders name from the deps object at runtime. This is especially useful in agent specs where callables aren't available.
System prompt functions, function tools and output validators are all run in the async context of an agent run.
If these functions are not coroutines (e.g. async def) they are called with
[run_in_executor][asyncio.loop.run_in_executor] in a thread pool. It's therefore marginally preferable
to use async methods where dependencies perform IO, although synchronous dependencies should work fine too.
!!! note "run vs. run_sync and Asynchronous vs. Synchronous dependencies"
Whether you use synchronous or asynchronous dependencies is completely independent of whether you use run or run_sync — run_sync is just a wrapper around run and agents are always run in an async context.
Here's the same example as above, but with a synchronous dependency:
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.Client # (1)!
agent = Agent(
'openai:gpt-5.2',
deps_type=MyDeps,
)
@agent.system_prompt
def get_system_prompt(ctx: RunContext[MyDeps]) -> str: # (2)!
response = ctx.deps.http_client.get(
'https://example.com', headers={'Authorization': f'Bearer {ctx.deps.api_key}'}
)
response.raise_for_status()
return f'Prompt: {response.text}'
async def main():
deps = MyDeps('foobar', httpx.Client())
result = await agent.run(
'Tell me a joke.',
deps=deps,
)
print(result.output)
#> Did you hear about the toothpaste scandal? They called it Colgate.
httpx.Client instead of an asynchronous httpx.AsyncClient.(This example is complete, it can be run "as is" — you'll need to add asyncio.run(main()) to run main)
As well as system prompts, dependencies can be used in tools and output validators.
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, ModelRetry, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.AsyncClient
agent = Agent(
'openai:gpt-5.2',
deps_type=MyDeps,
)
@agent.system_prompt
async def get_system_prompt(ctx: RunContext[MyDeps]) -> str:
response = await ctx.deps.http_client.get('https://example.com')
response.raise_for_status()
return f'Prompt: {response.text}'
@agent.tool # (1)!
async def get_joke_material(ctx: RunContext[MyDeps], subject: str) -> str:
response = await ctx.deps.http_client.get(
'https://example.com#jokes',
params={'subject': subject},
headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
)
response.raise_for_status()
return response.text
@agent.output_validator # (2)!
async def validate_output(ctx: RunContext[MyDeps], output: str) -> str:
response = await ctx.deps.http_client.post(
'https://example.com#validate',
headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
params={'query': output},
)
if response.status_code == 400:
raise ModelRetry(f'invalid response: {response.text}')
response.raise_for_status()
return output
async def main():
async with httpx.AsyncClient() as client:
deps = MyDeps('foobar', client)
result = await agent.run('Tell me a joke.', deps=deps)
print(result.output)
#> Did you hear about the toothpaste scandal? They called it Colgate.
RunContext to a tool, use the [tool][pydantic_ai.agent.Agent.tool] decorator.RunContext may optionally be passed to a [output_validator][pydantic_ai.agent.Agent.output_validator] function as the first argument.(This example is complete, it can be run "as is" — you'll need to add asyncio.run(main()) to run main)
When testing agents, it's useful to be able to customise dependencies.
While this can sometimes be done by calling the agent directly within unit tests, we can also override dependencies while calling application code which in turn calls the agent.
This is done via the [override][pydantic_ai.agent.Agent.override] method on the agent.
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.AsyncClient
async def system_prompt_factory(self) -> str: # (1)!
response = await self.http_client.get('https://example.com')
response.raise_for_status()
return f'Prompt: {response.text}'
joke_agent = Agent('openai:gpt-5.2', deps_type=MyDeps)
@joke_agent.system_prompt
async def get_system_prompt(ctx: RunContext[MyDeps]) -> str:
return await ctx.deps.system_prompt_factory() # (2)!
async def application_code(prompt: str) -> str: # (3)!
...
...
# now deep within application code we call our agent
async with httpx.AsyncClient() as client:
app_deps = MyDeps('foobar', client)
result = await joke_agent.run(prompt, deps=app_deps) # (4)!
return result.output
app_deps here will NOT be used when deps are overridden.(This example is complete, it can be run "as is")
from joke_app import MyDeps, application_code, joke_agent
class TestMyDeps(MyDeps): # (1)!
async def system_prompt_factory(self) -> str:
return 'test prompt'
async def test_application_code():
test_deps = TestMyDeps('test_key', None) # (2)!
with joke_agent.override(deps=test_deps): # (3)!
joke = await application_code('Tell me a joke.') # (4)!
assert joke.startswith('Did you hear about the toothpaste scandal?')
MyDeps in tests to customise the system prompt factory.http_client here as it's not used.with block, test_deps will be used when the agent is run.The following examples demonstrate how to use dependencies in Pydantic AI: