# PrivateGPT Dependency Injection Architecture: How Components Are Wired Together

> Explore PrivateGPTs dependency injection architecture. Learn how its three-layer system using the injector library wires components together for efficient runtime object management.

- Repository: [Zylon/private-gpt](https://github.com/zylon-ai/private-gpt)
- Tags: architecture
- Published: 2026-03-06

---

**PrivateGPT uses the Python `injector` library to manage runtime objects through a three-layer architecture: a root injector for global configuration, per-request injector binding for FastAPI routes, and a component graph of singleton services that auto-wire via constructor injection.**

The dependency injection architecture in PrivateGPT eliminates tight coupling between the FastAPI server, LLM backends, vector stores, and embedding models. By leveraging the `injector` library, the codebase centralizes configuration management in `Settings` while allowing components to declare their dependencies through decorators. This design enables seamless swapping of AI providers (OpenAI, Ollama, LlamaCPP) without modifying business logic.

## The Three Layers of PrivateGPT's Dependency Injection Architecture

### Root Injector and Global Configuration

The foundation of the system resides in [`private_gpt/di.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/di.py). The `create_application_injector()` function instantiates a single `Injector` with `auto_bind=True`, which allows the container to automatically resolve concrete types without explicit binding rules.

```python

# private_gpt/di.py

from injector import Injector
from private_gpt.settings.settings import Settings, unsafe_typed_settings

def create_application_injector() -> Injector:
    _injector = Injector(auto_bind=True)
    _injector.binder.bind(Settings, to=unsafe_typed_settings)
    return _injector

global_injector: Injector = create_application_injector()

```

The `Settings` object—parsed from [`settings.yaml`](https://github.com/zylon-ai/private-gpt/blob/main/settings.yaml)—is bound as a singleton, ensuring all components share the same configuration instance. This global injector lives for the entire process lifetime.

### Per-Request Injector Binding

To make the dependency injection architecture available within FastAPI routes, PrivateGPT binds the root injector to each request's state. This occurs in [`private_gpt/launcher.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/launcher.py) through the `bind_injector_to_request` dependency.

```python

# private_gpt/launcher.py

from fastapi import FastAPI, Request, Depends
from injector import Injector

def create_app(root_injector: Injector) -> FastAPI:
    async def bind_injector_to_request(request: Request) -> None:
        request.state.injector = root_injector

    app = FastAPI(dependencies=[Depends(bind_injector_to_request)])
    return app

```

This pattern allows route handlers to resolve services via `request.state.injector.get(ServiceClass)`, maintaining loose coupling between HTTP layer and business logic.

### Component Graph and Service Singletons

The final layer consists of concrete service classes decorated with `@singleton` and `@inject`. These classes declare their dependencies through constructor parameters, which the injector resolves automatically from the component graph.

For example, `LLMComponent` in [`private_gpt/components/llm/llm_component.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/components/llm/llm_component.py) receives the global `Settings` instance:

```python

# private_gpt/components/llm/llm_component.py

from injector import singleton, inject
from private_gpt.settings.settings import Settings

@singleton
class LLMComponent:
    @inject
    def __init__(self, settings: Settings) -> None:
        self.llm = self._build_llm(settings.llm)

```

Similarly, `VectorStoreComponent` in [`private_gpt/components/vector_store/vector_store_component.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/components/vector_store/vector_store_component.py) selects the appropriate backend (Postgres, Chroma, Qdrant) based on injected settings.

## How Components Are Declared and Wired

PrivateGPT's dependency injection architecture relies on two key decorators from the `injector` library:

- **`@singleton`**: Ensures only one instance exists per injector (process lifetime)
- **`@inject`**: Marks the constructor for automatic dependency resolution

When a class like `ChatService` requires multiple dependencies, it simply lists them in `__init__`:

```python

# private_gpt/server/chat/chat_service.py

from injector import singleton, inject

@singleton
class ChatService:
    @inject
    def __init__(
        self,
        llm_component: LLMComponent,
        vector_store_component: VectorStoreComponent,
        embedding_component: EmbeddingComponent,
        node_store_component: NodeStoreComponent,
    ) -> None:
        self.llm_component = llm_component
        self.vector_store_component = vector_store_component
        self.embedding_component = embedding_component
        self.node_store_component = node_store_component

```

The injector automatically resolves the four component arguments by looking up existing singletons or creating them if necessary. This eliminates manual factory code and ensures consistent initialization order.

## Accessing Services in FastAPI Routes

Route handlers in PrivateGPT never instantiate services directly. Instead, they retrieve them from the request-bound injector:

```python

# private_gpt/server/chat/chat_router.py

from fastapi import APIRouter, Request
from private_gpt.server.chat.chat_service import ChatService

chat_router = APIRouter()

@chat_router.post("/chat/completions")
def chat_completion(request: Request, body: ChatBody):
    service = request.state.injector.get(ChatService)
    # Use service...

```

This pattern maintains strict separation between the HTTP transport layer and the domain logic. Testing becomes straightforward because `ChatService` can be mocked and injected without modifying route code.

## Extending the Architecture with Custom Components

Adding a new service to PrivateGPT's dependency injection architecture requires no changes to [`di.py`](https://github.com/zylon-ai/private-gpt/blob/main/di.py) or [`launcher.py`](https://github.com/zylon-ai/private-gpt/blob/main/launcher.py) thanks to `auto_bind=True`.

**Step 1:** Create the class with decorators:

```python

# my_component.py

from injector import singleton, inject
from private_gpt.settings.settings import Settings

@singleton
class MyComponent:
    @inject
    def __init__(self, settings: Settings) -> None:
        self.config_value = settings.my_section.value

```

**Step 2:** Reference it in existing services:

```python
class ExistingService:
    @inject
    def __init__(self, my_component: MyComponent) -> None:
        self.my_component = my_component

```

The injector automatically discovers and wires `MyComponent` when resolving `ExistingService`.

## Summary

- **PrivateGPT uses the `injector` library** to implement a three-layer dependency injection architecture consisting of a root injector, per-request bindings, and a component graph.
- **The root injector** in [`private_gpt/di.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/di.py) initializes with `auto_bind=True` and binds the global `Settings` singleton.
- **Per-request injection** in [`private_gpt/launcher.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/launcher.py) attaches the injector to `request.state`, making it available to all FastAPI route handlers.
- **Components** are declared as `@singleton` classes with `@inject` constructors, automatically receiving dependencies like `Settings`, `LLMComponent`, or `VectorStoreComponent`.
- **API routes** retrieve services via `request.state.injector.get(ServiceClass)`, maintaining loose coupling between HTTP and business logic.
- **New components** require only the `@singleton` and `@inject` decorators to integrate into the existing graph, thanks to automatic binding.

## Frequently Asked Questions

### What dependency injection library does PrivateGPT use?

PrivateGPT uses the **[injector](https://github.com/alecthomas/injector)** library, a Python implementation of the Guice dependency injection framework. This library provides the `@inject` and `@singleton` decorators used throughout the codebase to manage object lifecycles and wiring.

### How does PrivateGPT handle configuration injection?

Configuration is injected via a singleton `Settings` object bound in [`private_gpt/di.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/di.py). The `create_application_injector()` function binds the parsed YAML configuration (`unsafe_typed_settings`) to the `Settings` type. All components receive this configuration through constructor injection (`settings: Settings`), ensuring consistent access to application settings without global imports.

### Can I use the global injector outside of FastAPI requests?

Yes. The `global_injector` instance exported from [`private_gpt/di.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/di.py) can be used in scripts, background workers, or the Gradio UI. For example, [`private_gpt/ui/ui.py`](https://github.com/zylon-ai/private-gpt/blob/main/private_gpt/ui/ui.py) retrieves the `PrivateGptUi` class via `global_injector.get(PrivateGptUi)`. This allows non-HTTP entry points to participate in the same dependency injection architecture as the API server.

### How do I add a new service to PrivateGPT's dependency injection architecture?

To add a new service, create a class decorated with `@singleton` and `@inject`, then declare its dependencies in the constructor. Thanks to `auto_bind=True` in the root injector, no registration in [`di.py`](https://github.com/zylon-ai/private-gpt/blob/main/di.py) is required. Simply reference the new service as a parameter in existing components, and the injector will automatically resolve and wire it into the component graph.