
Pydantic Ai Dependency Injection
- 140 installs
- 74 repo stars
- Updated July 21, 2026
- existential-birds/beagle
Implement Pydantic AI RunContext dependency injection for DB clients, auth, config, and shared services across agent tools and runs.
About
Covers Pydantic AI dependency injection patterns using RunContext and deps types to inject databases, auth, settings, and services into agent tools with clean testing seams and lifecycle-aware resource management.
- RunContext deps
- Testable tools
- Scoped resources
- Service wiring
- Context factories
Pydantic Ai Dependency Injection by the numbers
- 140 all-time installs (skills.sh)
- Ranked #3,488 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/existential-birds/beagle --skill pydantic-ai-dependency-injectionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 140 |
|---|---|
| repo stars | ★ 74 |
| Last updated | July 21, 2026 |
| Repository | existential-birds/beagle ↗ |
What it does
Implement Pydantic AI RunContext dependency injection for DB clients, auth, config, and shared services across agent tools and runs.
Files
PydanticAI Dependency Injection
Core Pattern
Dependencies flow through RunContext:
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
@dataclass
class Deps:
db: DatabaseConn
api_client: HttpClient
user_id: int
agent = Agent(
'openai:gpt-4o',
deps_type=Deps, # Type for static analysis
)
@agent.tool
async def get_user_balance(ctx: RunContext[Deps]) -> float:
"""Get the current user's account balance."""
return await ctx.deps.db.get_balance(ctx.deps.user_id)
# At runtime, provide deps
result = await agent.run(
'What is my balance?',
deps=Deps(db=db_conn, api_client=client, user_id=123)
)Defining Dependencies
Use dataclasses or Pydantic models:
from dataclasses import dataclass
from pydantic import BaseModel
# Dataclass (recommended for simplicity)
@dataclass
class Deps:
db: DatabaseConnection
cache: CacheClient
user_context: UserContext
# Pydantic model (if you need validation)
class Deps(BaseModel):
api_key: str
endpoint: str
timeout: int = 30Accessing Dependencies
In tools and instructions:
@agent.tool
async def query_database(ctx: RunContext[Deps], query: str) -> list[dict]:
"""Run a database query."""
return await ctx.deps.db.execute(query)
@agent.instructions
async def add_user_context(ctx: RunContext[Deps]) -> str:
user = await ctx.deps.db.get_user(ctx.deps.user_id)
return f"User name: {user.name}, Role: {user.role}"
@agent.system_prompt
def add_permissions(ctx: RunContext[Deps]) -> str:
return f"User has permissions: {ctx.deps.permissions}"Type Safety
Full type checking with generics:
# Explicit agent type annotation
agent: Agent[Deps, OutputModel] = Agent(
'openai:gpt-4o',
deps_type=Deps,
output_type=OutputModel,
)
# Now these are type-checked:
# - ctx.deps in tools is typed as Deps
# - result.output is typed as OutputModel
# - agent.run() requires deps: DepsNo Dependencies Pattern
When you don't need dependencies:
# Option 1: No deps_type (defaults to NoneType)
agent = Agent('openai:gpt-4o')
result = agent.run_sync('Hello') # No deps needed
# Option 2: Explicit None for type checker
agent: Agent[None, str] = Agent('openai:gpt-4o')
result = agent.run_sync('Hello', deps=None)
# In tool_plain, no context access
@agent.tool_plain
def simple_calc(a: int, b: int) -> int:
return a + bComplete Example
from dataclasses import dataclass
from httpx import AsyncClient
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
@dataclass
class WeatherDeps:
client: AsyncClient
api_key: str
class WeatherReport(BaseModel):
location: str
temperature: float
conditions: str
agent: Agent[WeatherDeps, WeatherReport] = Agent(
'openai:gpt-4o',
deps_type=WeatherDeps,
output_type=WeatherReport,
instructions='You are a weather assistant.',
)
@agent.tool
async def get_weather(
ctx: RunContext[WeatherDeps],
city: str
) -> dict:
"""Fetch weather data for a city."""
response = await ctx.deps.client.get(
f'https://api.weather.com/{city}',
headers={'Authorization': ctx.deps.api_key}
)
return response.json()
async def main():
async with AsyncClient() as client:
deps = WeatherDeps(client=client, api_key='secret')
result = await agent.run('Weather in London?', deps=deps)
print(result.output.temperature)Override for Testing
from pydantic_ai.models.test import TestModel
# Create mock dependencies
mock_deps = Deps(
db=MockDatabase(),
api_client=MockClient(),
user_id=999
)
# Override model and deps for testing
with agent.override(model=TestModel(), deps=mock_deps):
result = agent.run_sync('Test prompt')Gates
Run these in order before treating the agent as correct; each step has an objective pass condition.
1. Deps cover every access — Collect every ctx.deps.<attr> (and nested uses) from tools, @agent.instructions, and @agent.system_prompt. Pass: each <attr> exists on deps_type (and static checking passes if you use mypy/pyright on Agent[DepsType, …]). 2. Every run that needs deps gets them — Pass: each agent.run / run_sync path that executes those tools passes deps= whose type matches deps_type (no None unless the agent truly has no deps). 3. Tests pin deps shape — Pass: tests that use agent.override pass a deps= value with the same fields/types as production Deps (not a partial mock unless tools under test never touch missing fields).
Best Practices
1. Keep deps immutable: Use frozen dataclasses or Pydantic models 2. Pass connections, not credentials: Deps should hold initialized clients 3. Type your agents: Use Agent[DepsType, OutputType] for full type safety 4. Scope deps appropriately: Create deps at the start of a request, close after