# How ModelManager Integrates with Provider-Specific Credential Configuration in Open Notebook

> Discover how ModelManager integrates provider-specific credential configuration in Open Notebook. Discover dynamic resolution of settings and automatic fallback to environment variables.

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

---

**ModelManager acts as a central factory that transforms database Model records into fully configured Esperanto AI clients by dynamically resolving provider-specific settings from linked Credential objects, with automatic fallback to environment variables when credentials are absent.**

Managing API keys, base URLs, and provider-specific parameters across multiple AI services can become unwieldy in production applications. In the `lfnovo/open-notebook` repository, the `ModelManager` class eliminates hard-coded secrets by seamlessly bridging the `Model` and `Credential` domain models. This integration allows the platform to instantiate ready-to-use AI clients for OpenAI, Anthropic, Azure, Google Vertex, Ollama, and other providers while keeping sensitive configuration in the database.

## The ModelManager Architecture

The `ModelManager` implementation lives in [[`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py)](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py#L98) and serves as the primary interface between SurrealDB records and the Esperanto AI library. Rather than storing connection details in code, the manager retrieves a `Model` record by its SurrealDB ID, validates its type, and constructs the appropriate Esperanto client using provider-specific configuration extracted from an associated `Credential` object.

This architecture ensures that authentication details, endpoint URLs, and provider-specific parameters remain decoupled from application logic, enabling secure multi-tenant deployments where different models may use different API keys or cloud regions.

## Provider-Specific Credential Resolution Process

The integration follows a deterministic sequence to ensure every model receives the correct configuration for its provider.

### Loading the Model Record

The process begins when `ModelManager.get_model()` fetches a `Model` record using `await Model.get(model_id)`. The manager immediately validates that the model's `type` column matches one of the four supported categories: **language**, **embedding**, **speech-to-text**, or **text-to-speech**. This validation ensures that the subsequent factory method receives the correct parameters for constructing the appropriate AI client.

### Resolving Credential Configuration

When a model includes a `credential` field, the manager loads the associated `Credential` object via `await model.get_credential_obj()`. According to the implementation in [[`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py)](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py#L72), the `Credential` class stores provider-specific fields such as `api_key`, `base_url`, `api_version`, `endpoint`, and cloud-specific parameters like `project` or `location`.

The credential's `to_esperanto_config()` method transforms these fields into a plain dictionary that Esperanto recognizes. This method extracts every provider-specific setting, ensuring that Azure OpenAI deployments receive their required `base_url` and `api_version`, while Ollama instances receive the correct `num_ctx` or local endpoint configuration.

### Fallback to Environment Variables

If a model lacks a linked credential or the credential cannot be loaded, `ModelManager` invokes `provision_provider_keys(provider)` to populate the configuration dictionary from environment variables. This fallback mechanism ensures that models remain functional even when database credentials are temporarily unavailable, reading standard environment variables like `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` based on the provider name.

### Normalizing Provider Names

Database records store provider names using underscores (e.g., `openai_compatible` or `azure_openai`), but Esperanto expects hyphenated formats (`openai-compatible`, `azure-openai`). The manager automatically performs this conversion using `provider.replace("_", "-")` before passing the value to the factory, ensuring compatibility without requiring manual string manipulation in the database.

### Creating the Concrete Esperanto Model

Finally, the manager merges any caller-provided runtime arguments—such as `temperature` or `max_tokens`—into the configuration dictionary. Based on the model's `type`, it invokes the appropriate `AIFactory.create_*` method (e.g., `create_language_model()`, `create_embedding_model()`), passing the model name, normalized provider string, and the merged config. Esperanto internally caches these instantiated clients, allowing subsequent calls to reuse existing connections.

## Practical Implementation Example

The following workflow demonstrates how to create a credential with Azure-specific settings, link it to a model, and retrieve a configured client via `ModelManager`.

First, create a credential with provider-specific fields:

```python
from open_notebook.domain.credential import Credential
from pydantic import SecretStr

cred = Credential(
    name="My Azure OpenAI",
    provider="azure",
    modalities=["language", "embedding"],
    api_key=SecretStr("my-azure-key"),
    base_url="https://my-resource.openai.azure.com/",
    api_version="2023-05-15",
    endpoint="deployments",
)
await cred.save()

```

Next, create a model that references this credential:

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

model = Model(
    name="gpt-4o-mini",
    provider="azure",
    type="language",
    credential=cred.id,
)
await model.save()

```

Retrieve the ready-to-use Esperanto model through the manager:

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

language_model = await model_manager.get_model(model.id, temperature=0.7)

# Returns an esperanto.LanguageModel configured with Azure endpoint and API key

```

Use the model within a LangGraph workflow or similar pipeline:

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

async def embed_source(text: str):
    manager = ModelManager()
    embedding_model = await manager.get_embedding_model()
    vector = await embedding_model.embed(text)
    return vector

```

## Summary

- **ModelManager** in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) serves as the central factory for converting database records into functional AI clients.
- **Credential resolution** occurs via `to_esperanto_config()`, which extracts provider-specific settings like `base_url`, `api_version`, and cloud project details from the `Credential` model.
- **Environment fallback** activates automatically when credentials are missing, using `provision_provider_keys()` to read standard API keys from environment variables.
- **Provider name normalization** converts underscore-separated database values to hyphenated formats expected by Esperanto.
- **Runtime parameter merging** allows caller-specified arguments like `temperature` to override or extend stored configuration.

## Frequently Asked Questions

### What happens if a Model does not have a linked Credential?

When `ModelManager` encounters a model without a `credential` field, it automatically calls `provision_provider_keys(provider)` to populate the configuration from environment variables. This ensures the model remains functional using standard env-var naming conventions (e.g., `OPENAI_API_KEY`) even when no explicit credential record exists in the database.

### How does ModelManager handle different provider naming conventions?

The manager normalizes provider names by replacing underscores with hyphens using `provider.replace("_", "-")` before passing the value to Esperanto. This bridges the gap between database storage conventions (e.g., `openai_compatible`) and the hyphenated format required by the Esperanto AI library (e.g., `openai-compatible`).

### Can runtime parameters override credential settings?

Yes. Any additional keyword arguments passed to `ModelManager.get_model()`—such as `temperature`, `max_tokens`, or `top_p`—are merged into the configuration dictionary after the credential settings are applied. This allows dynamic adjustment of model behavior without modifying stored credentials.

### Where is the Credential model defined?

The `Credential` domain model and its `to_esperanto_config()` method are defined in [[`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py)](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py#L72). This file contains the Pydantic model that stores API keys as `SecretStr` and translates provider-specific fields into the flat dictionary format required by Esperanto.