# How ModelManager Implements the Factory Pattern with Credential Configuration Fallback

> Learn how ModelManager uses the factory pattern to create AI clients, prioritizing SurrealDB credentials and falling back to environment variables for seamless configuration.

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

---

**The ModelManager in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) implements a factory pattern that instantiates AI provider clients by first checking SurrealDB credential records, then falling back to environment variables when credentials are absent, returning objects that implement the `BaseModelClient` interface.**

The `lfnovo/open-notebook` repository uses a centralized **ModelManager** to abstract the creation of language model clients across multiple AI providers. This component eliminates hard-coded provider logic by combining a registry-based factory pattern with a hierarchical configuration system that prioritizes secure database credentials over environment variables.

## Factory Pattern Architecture in ModelManager

The ModelManager acts as a **factory** that decouples client instantiation from business logic. Instead of embedding provider-specific construction logic throughout the codebase, the manager maintains a **provider registry** that maps provider identifiers (e.g., `"openai"`, `"anthropic"`, `"groq"`) to concrete client-creation functions.

This registry is populated at import time, allowing the `get_model()` method to resolve any supported model request without lengthy `if…elif` chains. When a caller requests a model using the format `provider:model_name`, the factory looks up the corresponding creation function, gathers configuration from the credential hierarchy, and returns a fully initialized client that implements the `BaseModelClient` interface.

## Credential Configuration Hierarchy

The ModelManager implements a **credential-first lookup strategy** with a disciplined fallback chain. This ensures that sensitive API keys can be stored securely in SurrealDB while maintaining out-of-the-box functionality for developers using environment variables.

### SurrealDB Credential Store Lookup

When instantiating a client, the factory first queries the **Credential** records stored in SurrealDB via [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py). If a credential record exists for the requested provider, the manager extracts the API key, endpoint URL, and optional headers directly from the database. This approach keeps secrets out of environment variables and enables runtime credential rotation without application restarts.

### Environment Variable Fallback

If no credential record exists in the database, the factory falls back to standard environment variables as documented in [`CONFIGURATION.md`](https://github.com/lfnovo/open-notebook/blob/main/CONFIGURATION.md). For example, when requesting an OpenAI model, the manager checks for `OPENAI_API_KEY`; for Anthropic, it checks `ANTHROPIC_API_KEY`; and for Groq, it checks `GROQ_API_KEY`. This fallback guarantees that the system functions immediately after cloning the repository, provided the user has configured their `.env` file.

### Interface Compliance via BaseModelClient

Regardless of whether credentials come from SurrealDB or environment variables, the factory passes the gathered configuration to the provider-specific creation function. The resulting object **must implement the `BaseModelClient` interface**, ensuring that downstream components—including LangGraph workflows, chat interfaces, and embedding pipelines—can interact with any provider using a uniform API.

## Error Handling When Configuration Is Missing

If the ModelManager cannot resolve credentials for a requested provider—meaning no SurrealDB record exists and the corresponding environment variable is unset—the factory raises a **`ModelNotConfiguredError`**. This explicit failure mode allows calling code to catch the exception and implement graceful degradation, such as falling back to a cheaper default model or prompting the user to configure credentials through the UI.

## Implementation Examples

The following examples demonstrate how to obtain model clients through the factory and how the credential hierarchy behaves in practice:

```python
from open_notebook.ai.models import ModelManager

# Example 1: Basic model retrieval from OpenAI

client = ModelManager.get_model("openai:gpt-4o")

# Returns a BaseModelClient implementation ready for chat completions

# Example 2: Credential-first behavior (SurrealDB takes precedence)

# If a Credential record exists for Anthropic in the database,

# the factory uses that key and ignores ANTHROPIC_API_KEY env var

client = ModelManager.get_model("anthropic:claude-3")

# Example 3: Fallback to environment variables

# When no Groq credential exists in SurrealDB but GROQ_API_KEY is exported,

# the factory reads the environment and constructs the client anyway

client = ModelManager.get_model("groq:gemma-2b")

```

## Summary

- **The ModelManager** in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) functions as a factory that maps provider identifiers to client instances via a runtime registry.
- **Credential resolution** follows a strict hierarchy: SurrealDB records are checked first, then environment variables serve as fallback.
- **Interface abstraction** ensures all returned objects implement `BaseModelClient`, keeping provider-specific details isolated from business logic.
- **Explicit failure mode** via `ModelNotConfiguredError` prevents silent misconfigurations when both credential sources are empty.
- **Supported providers** include OpenAI, Anthropic, Google, Groq, Ollama, Mistral, DeepSeek, and xAI, each registered in the factory map.

## Frequently Asked Questions

### How does ModelManager resolve provider credentials?

The factory first queries the SurrealDB credential store through [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) for a matching provider record. If found, it uses those credentials exclusively. If no record exists, it falls back to the environment variable naming convention defined in [`CONFIGURATION.md`](https://github.com/lfnovo/open-notebook/blob/main/CONFIGURATION.md) (e.g., `OPENAI_API_KEY`).

### What happens when both credentials and environment variables exist?

The credential store takes precedence. If a SurrealDB Credential record is present for a provider, the ModelManager uses that configuration and ignores any corresponding environment variables. This behavior ensures that database-stored secrets override local environment settings.

### Which AI providers are supported by the factory?

The ModelManager supports OpenAI, Anthropic, Google, Groq, Ollama, Mistral, DeepSeek, and xAI. Each provider is registered in the factory's provider map within [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py), allowing instantiation via the `provider:model_name` syntax.

### How does the factory pattern benefit the Open Notebook architecture?

By centralizing client creation in the ModelManager, the codebase eliminates provider-specific conditionals from graph workflows and chat modules. Developers interact only with the `BaseModelClient` interface, while the factory handles the complexity of credential resolution and provider-specific initialization.