# How Open-Notebook's Multi-Provider AI Provisioning System Integrates with Esperanto

> Explore how Open-Notebook's multi-provider AI provisioning system integrates with Esperanto. Seamlessly switch between AI services like OpenAI and Anthropic using a unified code path.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-24

---

**Open-Notebook uses the Esperanto library as a unified abstraction layer that converts stored credentials into provider-specific configurations, enabling seamless switching between OpenAI, Anthropic, Azure, and other AI services through a single code path.**

The `lfnovo/open-notebook` repository implements a sophisticated multi-provider AI provisioning system that leverages Esperanto to normalize access across diverse language models, embedding services, and speech processors. This architecture eliminates provider-specific boilerplate by centralizing credential management in SurrealDB and delegating provider instantiation to Esperanto's factory methods.

## The Three-Layer Provisioning Architecture

The integration follows a strict separation of concerns across credential persistence, environment resolution, and client construction.

### Credential Storage with `to_esperanto_config()`

Each provider's secrets reside in SurrealDB as `Credential` records. The `Credential` model defines a `to_esperanto_config()` method that transforms stored fields—API keys, base URLs, and endpoint specifications—into the dictionary format Esperanto expects for factory instantiation.

When `ModelManager.get_model()` resolves a request, it first checks for an explicitly linked credential. If present, the system calls `Credential.get_credential_obj()` to retrieve the decrypted secret and convert it via `to_esperanto_config()` into a provider-ready configuration dict.

### Environment Variable Fallback via `provision_provider_keys()`

When no credential is explicitly linked to a model, the system falls back to environment variables. The `provision_provider_keys()` function in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) maps provider names to standardized environment variables using the `PROVIDER_CONFIG` lookup table.

This function populates the process environment with credentials retrieved from the database, ensuring that Esperanto's default environment-variable detection works seamlessly. The mapping covers simple key-based providers, Azure's complex endpoint configuration, and Vertex AI requirements.

### Model Construction and Caching

`ModelManager.get_model()` normalizes the provider identifier (converting underscores to hyphens) before selecting the appropriate Esperanto factory. Depending on the model type—language, embedding, speech-to-text, or text-to-speech—the manager calls `AIFactory.create_language_model()`, `AIFactory.create_embedding_model()`, or analogous methods.

Esperanto caches the underlying provider client instances, making repeated model instantiations cheap. The final configuration dict merges credential-derived values with runtime parameters like `temperature` before passing to the factory.

## Provider Configuration Patterns

The [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py) module handles three distinct provider categories, each with specific credential mapping logic.

### Simple API Key Providers

For OpenAI, Anthropic, and Groq, the system extracts `Credential.api_key` and maps it to either the `config["api_key"]` dict entry or the standard environment variable (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.). The `_provision_simple_provider` function handles this mapping generically.

### URL-Based Providers

Ollama, Azure, and Vertex AI require additional endpoint configuration. The `_provision_azure` and `_provision_vertex` functions extract `Credential.base_url` and optional endpoint fields, populating `config["base_url"]` and `config["endpoint"]` respectively. These providers bypass simple key injection in favor of structured configuration objects.

### OpenAI-Compatible Endpoints

Any provider exposing the OpenAI REST schema uses the `_provision_openai_compatible` pathway. This combines simple API key handling with optional base URL overrides, allowing custom endpoints like local LLM servers to piggyback on OpenAI's client configuration while using distinct credentials.

## Core Implementation Files

Understanding the file structure reveals how the provisioning pipeline operates:

### Credential Domain Model

[`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) defines the `Credential` class with encryption-at-rest and the `to_esperanto_config()` serialization logic. This file handles decryption and the transformation of database records into Esperanto-compatible dictionaries.

### Key Provisioning Service

[`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) contains the `PROVIDER_CONFIG` mapping table and `provision_provider_keys()` implementation. This module isolates provider-specific environment variable names and the logic for hydrating the process environment from database credentials.

### Model Resolution Engine

[`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) houses `ModelManager`, which implements the `get_model()` and `get_default_model()` methods. This file orchestrates the credential lookup, provider normalization, and Esperanto factory invocation.

### API Router Integration

[`api/routers/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/models.py) exposes supported model types to the frontend and maintains the provider name mappings used in UI settings, ensuring that user selections in the interface align with the backend provisioning logic.

## Practical Implementation Examples

### Loading Default Models

To retrieve the default chat model regardless of underlying provider:

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

async def get_chat_llm():
    # Returns an instantiated Esperanto LanguageModel (e.g., gpt-4, Claude, etc.)

    llm = await model_manager.get_default_model("chat")
    # The model is already configured with credentials or env vars

    return llm

```

### Manual Provider Provisioning

For scenarios requiring explicit environment setup before model creation:

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

async def use_azure():
    await provision_provider_keys("azure")          # pulls Azure keys from DB → env

    azure_llm = await model_manager.get_model(
        model_id="open_notebook:default_models:azure_chat",
        temperature=0.7,
    )
    # azure_llm is a TextGenerationModel ready for inference

    return azure_llm

```

### Custom Credential Injection

To instantiate an embedding model with a specific credential record:

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

async def embed_text(text: str):
    # Assume a model record links to a Credential storing the Anthropic API key

    embedder = await model_manager.get_model(
        model_id="open_notebook:embedding:anthropic",
        model_name="anthropic-embed-v1",
    )
    vectors = await embedder.embed([text])
    return vectors

```

## Summary

- **Centralized credential management** persists provider secrets in SurrealDB `Credential` records with automatic encryption.
- **Esperanto abstraction** converts stored credentials into provider-specific configurations via `to_esperanto_config()`, eliminating vendor-specific code paths.
- **Environment fallback** ensures models work via `provision_provider_keys()` when explicit credentials aren't linked, using the `PROVIDER_CONFIG` mapping.
- **Cached client instantiation** through `AIFactory.create_*` methods ensures repeated model accesses remain performant.
- **Extensible architecture** requires only updating [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py) and the credential model to add new AI providers.

## Frequently Asked Questions

### How does Open-Notebook handle API key rotation without restarting the service?

The `provision_provider_keys()` function reads credentials fresh from SurrealDB on each model instantiation. Since `ModelManager.get_model()` retrieves credentials dynamically rather than caching them at startup, updated keys in the database propagate immediately to new model instances without requiring process restarts.

### Can I use local models like Ollama alongside cloud providers?

Yes. The system treats Ollama as a URL-based provider. You store the local endpoint URL in a `Credential` record's `base_url` field, and [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py) maps this through the `_provision_openai_compatible` or Ollama-specific pathway. The same `ModelManager.get_model()` call works for both local and cloud models.

### What happens if both a credential and environment variables are available?

`ModelManager.get_model()` prioritizes explicitly linked credentials. It first attempts `Credential.get_credential_obj()` for the model record; only if no credential is linked does it fall back to `provision_provider_keys()`. This allows per-model overrides of global environment settings.

### Where is the provider name normalization implemented?

In [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py), the `ModelManager` normalizes provider identifiers by converting underscores to hyphens (e.g., `open_ai` → `open-ai`) before passing to Esperanto's factory methods. This ensures consistency between database storage conventions and Esperanto's expected provider naming.