# How the ModelManager Factory Pattern Implements Fallback Logic for AI Model Selection

> Learn how Open Notebook's ModelManager factory pattern prioritizes AI model credentials, falling back to environment variables when needed. Ensure seamless model selection.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: internals
- Published: 2026-06-19

---

**Open Notebook's ModelManager uses a three-stage factory pattern that prioritizes database-stored credentials while automatically falling back to environment variables when credentials are unavailable.**

The lfnovo/open-notebook repository implements a robust AI model management system through its `ModelManager` class in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py). This factory pattern abstracts the complexity of credential resolution and model instantiation, ensuring seamless AI model selection with intelligent fallback mechanisms. The implementation guarantees that model construction never fails due to missing configuration by providing multiple resolution paths.

## Model Retrieval and Validation

The factory process begins with strict model record validation. When `model_manager.get_model(model_id)` is called, it invokes `Model.get(model_id)` to retrieve the model configuration from the database (lines [2-10](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L2-L10)).

This returns a `Model` instance containing the model name, provider, type, and an optional `credential` reference. If the record cannot be found, the system raises a `ConfigurationError` immediately, preventing invalid state propagation.

## Credential Resolution: Database-First with Environment Variable Fallback

The core fallback logic operates in two distinct paths within the credential resolution phase (lines [20-34](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L20-L34)).

**Primary Path: Database Credentials**

If the model links to a `Credential` (i.e., `model.credential` is set), the manager loads that credential using `model.get_credential_obj()` and converts it to an Esperanto configuration via `credential.to_esperanto_config()` (lines [20-28](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L20-L28)). This method is defined in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) and provides structured access to API keys and provider settings.

**Fallback Path: Environment Variable Provisioning**

If the credential cannot be loaded or the model has no credential at all, the manager falls back to environment-variable provisioning. It calls `provision_provider_keys(model.provider)` from [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) (lines [46-80](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py#L46-L80)).

This function implements a secondary fallback within [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py) (lines [22-41](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py#L22-L41)): it attempts to read any stored credential for the provider from the database and sets the appropriate environment variables (e.g., `OPENAI_API_KEY`). If no database entry exists, the existing environment variables remain untouched, preserving standard configuration workflows.

## Concrete Model Instantiation via AIFactory

After credential handling, the factory prepares the final configuration. The manager merges any extra keyword arguments (e.g., `temperature`) into the configuration and normalizes the provider name by converting underscores to hyphens (lines [44-48](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L44-L48)).

The appropriate `AIFactory` method is then called based on `model.type`:
- `create_language` for chat models
- `create_embedding` for embedding models
- `create_speech_to_text` for transcription models
- `create_text_to_speech` for voice synthesis models

These calls occur in lines [50-75](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L50-L75). Because `AIFactory` internally caches the created model instances, subsequent calls for the same model ID are computationally cheap.

## Practical Implementation Examples

The following examples demonstrate how the factory automatically handles fallback scenarios:

```python

# Example: Request the default chat model with fallback handling

from open_notebook.ai.models import model_manager

async def get_chat_model():
    # This will pick the model ID stored in the defaults table,

    # resolve credentials or env vars, and return a ready LanguageModel.

    chat_model = await model_manager.get_default_model("chat", temperature=0.7)
    return chat_model

```

```python

# Example: Manually provision provider keys before creating a model

from open_notebook.ai.key_provider import provision_provider_keys
from open_notebook.ai.models import model_manager

async def create_openai_model():
    # Ensure any DB-stored OpenAI keys are available as env vars

    await provision_provider_keys("openai")
    # Now retrieve a specific model (ID stored in DB)

    model = await model_manager.get_model("model:openai:gpt-4")
    # `model` is an instance of esperanto.LanguageModel ready for use

    return model

```

## Summary

- **ModelManager** in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) implements a factory pattern with three-stage resolution: model lookup, credential resolution, and factory instantiation.
- The **database-first** approach attempts to load credentials via `credential.to_esperanto_config()` before falling back to environment variables.
- **Fallback logic** in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) ensures that missing database credentials automatically trigger environment variable provisioning via `provision_provider_keys()`.
- **AIFactory** methods create concrete model instances with caching support, reducing overhead for repeated model requests.

## Frequently Asked Questions

### What happens if a model credential is missing from the database?

If `model.credential` is not set or `get_credential_obj()` fails, the system automatically calls `provision_provider_keys()` from [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py). This function attempts to load provider keys from the database into environment variables; if no keys exist there, it preserves existing environment variables, allowing standard configuration to function.

### How does ModelManager handle different model types?

The factory inspects the `model.type` attribute and routes to the appropriate `AIFactory` method: `create_language` for chat models, `create_embedding` for embeddings, `create_speech_to_text` for transcription, or `create_text_to_speech` for voice generation (lines [50-75](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L50-L75)).

### Where is the provider name normalization implemented?

Provider name normalization occurs in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) lines [44-48](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L44-L48), where underscores are converted to hyphens before passing the provider string to the `AIFactory` methods.

### How does the factory pattern improve performance?

The `AIFactory` class internally caches created model instances. When `model_manager.get_model()` or `model_manager.get_default_model()` is called repeatedly with the same model ID, the factory returns the cached instance rather than reconstructing the object, significantly reducing initialization overhead.