Skip to content

Depends#

The Depends mechanism allows you to calculate and inject dependencies dynamically at execution time.

The key difference with Dependency Injection is their execution model. Inject is used to retrieve static objects or configurations that have already been created (like an existing database connection or API key). Depends, on the other hand, executes a callable function during the tool's invocation to resolve the dependency.

Under the hood, Depends uses the exact same mechanism and design philosophy as FastAPI's dependency injection system.

Side-execution#

You can use Depends to execute side-effects before your tool runs—even if your tool doesn't actually need the return value of the dependency. This is extremely useful for things like authentication, logging, or permission verification.

To do this, simply declare the dependency in your tool's signature. The framework will execute it, and you can safely ignore the injected value.

from typing import Annotated
from autogen.beta import Depends, tool

def verify_permissions(user_id: int) -> None:
    # Perform complex verification here
    # Raises an exception if permissions are invalid
    raise PermissionDenied(user_id)

@tool
def delete_user(
    user_id: int,
    # The dependency is executed, acting as a gatekeeper
    auth: Annotated[None, Depends(verify_permissions)]
) -> str:
    return f"User {user_id} deleted."

Sync/Async

Depends can be used with both synchronous and asynchronous functions.

Depends with yield#

Just like in FastAPI, you can create dependencies that use yield instead of return. This allows you to execute "teardown" or "cleanup" code after the tool has finished executing.

This is the recommended approach for managing resource lifecycles, such as opening and closing database sessions or file handlers.

def get_db_session():
    print("Opening database session...")
    session = "db_session_object"

    # The tool execution happens here
    yield session

    # This runs after the tool finishes
    print("Closing database session...")

@tool
def fetch_records(
    db: Annotated[str, Depends(get_db_session)],
) -> str:
    return "Records fetched."

Combining Depends and Inject#

A powerful pattern is to combine Depends with Inject. You can use Inject to retrieve a static configuration or persistent resource (like a database connection pool), and then use Depends to manage a short-lived resource (like a database session) based on that configuration.

from typing import Annotated
from autogen.beta import Depends, Inject, tool, Agent

def get_db_session(
    db_pool: Annotated[Pool, Inject("database_pool")],
) -> Session:
    session = db_pool.acquire()
    yield session
    session.release()

@tool
def fetch_records(
    db_session: Annotated[object, Depends(get_db_session)],
) -> str:
    return "Records fetched."

agent = Agent(
    "TestAgent",
    tools=[fetch_records],
    dependencies={"database_pool": Pool()},
)

Dependencies caching#

By default, if multiple parameters in your tool (or multiple sub-dependencies) depend on the exact same Depends function, the framework will only execute that function once per tool call. The result is cached and reused for any subsequent injections within that specific execution step.

def get_expensive_config() -> dict:
    print("Calculating config...") # This will only print once!
    return {"timeout": 30}

def get_timeout(
    config: Annotated[dict, Depends(get_expensive_config)],
) -> int:
    return config["timeout"]

@tool
def process_data(
    timeout: Annotated[int, Depends(get_timeout)],
    # cached dependency
    config: Annotated[dict, Depends(get_expensive_config)],
) -> str:
    return "Done"

If you explicitly want the dependency to be re-calculated every single time it is injected, you can disable the cache by passing use_cache=False:

1
2
3
4
5
6
7
@tool
def random_tool(
    val1: Annotated[int, Depends(get_random_number, use_cache=False)],
    val2: Annotated[int, Depends(get_random_number, use_cache=False)]
) -> str:
    # val1 and val2 will be different numbers
    pass

Dependencies Overrides#

During testing, you often need to mock or override complex dependencies (like replacing a production database with a mock test database).

You can easily override any Depends function at the agent level using the dependency_provider. When the agent executes, it will automatically route all requests for the original dependency to your override function.

from autogen.beta import Agent, tool

def get_production_db():
    raise Exception("Do not call this in tests!")

@tool
def read_data(db: Annotated[object, Depends(get_production_db)]) -> str:
    return "Data"

agent = Agent("TestAgent", tools=[read_data])

# Create a mock function
def get_test_db():
    return "mock_database"

# Override the production dependency with the test dependency
agent.dependency_provider.override(get_production_db, get_test_db)

# When the tool is called, it will use `get_test_db` instead
await agent.ask("Read some data")

To override Inject dependencies, you can just set dependencies={...} in the ask call.

1
2
3
4
5
6
7
agent = Agent("TestAgent", tools=[read_data])

# Override the production `Inject` dependency with the test dependency
await agent.ask(
    "Read some data",
    dependencies={"database_pool": Pool()},
)