Dependency Injection

Dependency injection is the least flashy Pydantic AI feature and quietly one of the most important — it's what lets your agents reach real databases, API clients, and user context without hard-wiring them, and it's the single biggest reason Pydantic AI agents are so testable. Borrowed straight from how good backend frameworks work, applied to agents.

Tools and dynamic prompts both needed access to things — a database, an API client, the current user, config. Dependency injection (DI) is how Pydantic AI provides those things: a typed, explicit mechanism for supplying an agent (and its tools and prompts) with the data and services it needs at run time. This post covers what DI is, why Pydantic AI made it a core feature, and why it’s the foundation of the framework’s excellent testability. It’s a familiar backend pattern brought to agents.

What dependency injection means here

Dependency injection is providing a component with its dependencies from the outside rather than having it create or fetch them itself. Instead of a tool reaching for a global database connection, the connection is injected — passed in — so the tool just uses what it’s given. In Pydantic AI, you declare a dependency type for an agent (its deps_type), and when you run the agent, you supply an instance of that type; the agent’s tools and dynamic prompts then access it (via the RunContext from the tools post):

# Illustrative shape — see the Pydantic AI docs for exact API.
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

@dataclass
class Deps:
    db: Database
    user_id: int

agent = Agent("openai:gpt-...", deps_type=Deps)

@agent.tool
def recent_orders(ctx: RunContext[Deps]) -> list[Order]:
    return ctx.deps.db.recent_orders(ctx.deps.user_id)

# at run time, you INJECT the dependencies:
result = agent.run_sync("Show my recent orders", deps=Deps(db=real_db, user_id=42))

The agent’s tools and prompts get their db and user_id from the injected Deps, not from globals. You define what the agent needs (the typed deps_type) once, and provide it (a Deps instance) at each run. That separation — declare the need, supply it at run time — is the whole idea.

Why it’s typed, and why that matters

Consistent with the framework’s philosophy, dependencies are typed: deps_type=Deps means the agent’s context is RunContext[Deps], so tools and prompts access ctx.deps with full type safety — the IDE knows ctx.deps.db is a Database and ctx.deps.user_id is an int. This typing delivers real benefits:

This is the same reason typed DI is valued in backend frameworks: it makes dependencies explicit, checked, and controlled rather than implicit and scattered. Pydantic AI brings that discipline to agents, where the temptation to reach for globals (the model, the DB, the config) is strong and leads to tangled, untestable code.

The payoff: testability

Here is the feature’s biggest practical benefit, and a major reason to choose Pydantic AI: dependency injection makes agents genuinely testable. Because an agent gets its dependencies injected rather than creating them, you can inject test dependencies — a fake database, a stub API client, a fixed user — and test the agent and its tools without touching real systems:

This is transformative because LLM agents are notoriously hard to test — they call unpredictable models and reach into real systems. DI removes the “reach into real systems” half of that problem: with dependencies injected, the agent’s own logic (its tools, its prompt construction, its flow) is testable in isolation with fakes. The result is that Pydantic AI agents can have real test suites, not just manual “run it and see” checks — a rarity in agent development and a direct consequence of the DI design.

DI powers dynamic prompts and tools together

Dependency injection isn’t a standalone feature; it’s the substrate that several other features rely on, which is why it’s foundational:

So DI is the common mechanism by which runtime context flows into everything an agent does. Understanding it as “the typed channel through which the agent’s prompts, tools, and validators receive the data and services they need” ties the framework together: the Agent declares the dependency type, and DI carries typed context to every part that needs it. This is why DI, despite being unglamorous, is central — it’s the plumbing that makes the typed, testable, context-aware agent design actually work.

Using dependency injection well

Dependency injection is the quiet foundation that makes Pydantic AI’s agents both context-aware and testable — the backend-framework discipline that turns agents from untestable globals-tangles into clean, injected, verifiable components. The next post builds on it to cover the framework’s standout strength: testing and evaluating agents.

Key takeaways

Further reading

Sources & References