How ModelManager Implements the Factory Pattern with Credential Configuration Fallback

The ModelManager in 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. 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. 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:

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 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 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 (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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →