# How to Add a New AI Provider to the Esperanto-Based System in Open Notebook

> Learn to add a new AI provider to the Esperanto-based system. Implement discovery coroutines and register environment variables in Open Notebook for seamless integration.

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

---

**Adding a new AI provider requires registering environment variables in [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py), implementing a discovery coroutine in [`model_discovery.py`](https://github.com/lfnovo/open-notebook/blob/main/model_discovery.py), and optionally extending the `Credential` class for complex authentication scenarios.**

Open Notebook uses the **Esperanto** library as its unified front-end for all AI providers. When you add a new AI provider to this Esperanto-based system, you integrate three core touchpoints: credential provisioning, model discovery, and type classification. These modifications enable the system to automatically discover models, classify them by capability (language, embedding, STT, TTS), and inject secure credentials at runtime.

## Step 1: Register the Provider's Environment Variables

Start by mapping your provider name to the environment variables that Esperanto expects. In [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py), add an entry to the `PROVIDER_CONFIG` dictionary:

```python

# open_notebook/ai/key_provider.py

PROVIDER_CONFIG = {
    # Existing entries …

    "myprovider": {
        "env_var": "MYPROVIDER_API_KEY",
        # For URL-based providers, add a second entry:

        # "base_url_var": "MYPROVIDER_API_BASE",

    },
}

```

The `provision_provider_keys()` function looks up `PROVIDER_CONFIG` to copy database-stored credentials into the process environment. This ensures that when a model of provider `myprovider` is requested, Esperanto can fetch its configuration from the environment or from a `Credential` record.

## Step 2: Implement Model Discovery Logic

If your provider publishes an API that lists available models, implement an async discovery coroutine in [`open_notebook/ai/model_discovery.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/model_discovery.py). This function fetches model metadata and returns standardized `DiscoveredModel` objects:

```python

# open_notebook/ai/model_discovery.py

async def discover_myprovider_models() -> List[DiscoveredModel]:
    """Fetch the list of models from MyProvider's /v1/models endpoint."""
    api_key = os.getenv("MYPROVIDER_API_KEY")
    base_url = os.getenv("MYPROVIDER_API_BASE", "https://api.myprovider.com")
    
    if not api_key:
        return []
    
    models: List[DiscoveredModel] = []
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{base_url}/v1/models",
                headers={"Authorization": f"Bearer {api_key}"},
                timeout=30.0,
            )
            resp.raise_for_status()
            for m in resp.json().get("models", []):
                name = m.get("id")
                if name:
                    model_type = classify_model_type(name, "myprovider")
                    models.append(
                        DiscoveredModel(
                            name=name,
                            provider="myprovider",
                            model_type=model_type,
                        )
                    )
    except Exception as exc:
        logger.warning(f"Failed to discover MyProvider models: {exc}")
    return models

```

After defining the coroutine, register it in the `PROVIDER_DISCOVERY_FUNCTIONS` dictionary at the bottom of the same file:

```python

# open_notebook/ai/model_discovery.py

PROVIDER_DISCOVERY_FUNCTIONS["myprovider"] = discover_myprovider_models

```

The `sync_all_providers()` function iterates over `PROVIDER_DISCOVERY_FUNCTIONS` to automatically populate the `model` table, making new models selectable in the UI without manual database inserts.

## Step 3: Update Provider Type Classification

Teach the `classify_model_type` helper how to recognize model capabilities for your new provider. If your provider uses predictable naming conventions (e.g., "embed-" for embeddings), extend the type mappings:

```python

# open_notebook/ai/model_discovery.py

MYPROVIDER_MODEL_TYPES = {
    "language": ["gpt", "chat", "llm"],
    "embedding": ["embed"],
    "speech_to_text": ["stt"],
    "text_to_speech": ["tts"],
}

# Add to the type_mappings dict inside classify_model_type

type_mappings["myprovider"] = MYPROVIDER_MODEL_TYPES

```

Accurate classification guarantees that `ModelManager.get_default_model()` returns the correct model subtype—`LanguageModel`, `EmbeddingModel`, `SpeechToTextModel`, or `TextToSpeechModel`—which determines what methods are available on the instance.

## Step 4: Handle Complex Authentication (Optional)

If your provider requires more than a simple API key (e.g., Azure endpoints, Vertex service accounts, or project IDs), extend the `Credential` domain model in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py):

```python

# open_notebook/domain/credential.py

class Credential(ObjectModel):
    # Existing fields…

    myprovider_project: Optional[str] = None
    myprovider_region: Optional[str] = None
    
    def to_esperanto_config(self) -> Dict[str, Any]:
        cfg = super().to_esperanto_config()
        if self.myprovider_project:
            cfg["project"] = self.myprovider_project
        if self.myprovider_region:
            cfg["region"] = self.myprovider_region
        return cfg

```

The `to_esperanto_config()` method builds the configuration dictionary that Esperanto receives when instantiating a model. This keeps sensitive credentials out of the codebase while ensuring they are available at runtime.

## End-to-End Integration Flow

Once you have added a new AI provider to the Esperanto-based system, the integration follows this execution path:

1. **Credential Provisioning**: When a workflow requests a model, `ModelManager.get_model()` loads the `Model` record and any linked `Credential`, then calls `provision_provider_keys()` to inject environment variables.
2. **Model Instantiation**: The manager calls `AIFactory.create_*()` with the provider name and the config dict from `to_esperanto_config()`.
3. **Discovery**: Running `await sync_provider_models("myprovider")` (or clicking "Sync Models" in the UI) executes your discovery coroutine, inserts `Model` rows into SurrealDB via `Model.save()`, and classifies each model by type.
4. **UI Exposure**: The `/api/routers/models` endpoint serves these records to the frontend, populating **Settings → Models** automatically.

### Using the New Model in Workflows

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

# Retrieve the default language model for MyProvider

my_model = await model_manager.get_model(
    model_id="open_notebook:myprovider:gpt-4",
    temperature=0.7,
)

# Generate text

response = await my_model.generate("Explain quantum computing")

```

## Summary

- **Register environment variables** in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) by adding to `PROVIDER_CONFIG` so `provision_provider_keys()` can inject credentials.
- **Implement discovery logic** in [`open_notebook/ai/model_discovery.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/model_discovery.py) by creating an async function and registering it in `PROVIDER_DISCOVERY_FUNCTIONS`.
- **Update type classification** in `classify_model_type()` to map model names to language, embedding, STT, or TTS capabilities.
- **Extend credentials** in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) for non-standard auth flows, overriding `to_esperanto_config()` to expose custom fields.
- **No UI changes required**—the discovery step automatically registers `Model` records that appear in the Settings interface.

## Frequently Asked Questions

### Do I need to modify the frontend code to add a new provider?

No. The UI pulls data from `/api/routers/models`, which uses `ModelManager` to query the database. Once your discovery function populates the `model` table, the provider automatically appears in the **Models → Add** screen. You only need to modify UI code if you want custom credential input fields beyond the standard API key.

### How does the model type classification determine which Esperanto class to instantiate?

The `classify_model_type()` function returns a string—`"language"`, `"embedding"`, `"speech_to_text"`, or `"text_to_speech"`—which `ModelManager` uses to call the appropriate `AIFactory.create_language_model()`, `create_embedding_model()`, `create_speech_to_text_model()`, or `create_text_to_speech_model()` method. Incorrect classification results in runtime errors when calling provider-specific methods.

### What if my AI provider does not expose a model listing endpoint?

If the provider lacks a discovery API, modify your discovery function to return a static list of `DiscoveredModel` objects instead of making an HTTP request. Hardcode the known model IDs and types, then register the function normally. The system will treat these as discoverable models, and you can still update the list by deploying code changes.

### How are credentials securely injected when using the new provider?

The `ModelManager` loads the `Credential` record linked to the model, calls `to_esperanto_config()` to build a configuration dictionary, and temporarily sets the environment variables defined in `PROVIDER_CONFIG` before instantiating the Esperanto client. This isolation ensures that API keys never leave the server process and are never exposed to the frontend.