# How to Add a New AI Provider to Open Notebook Using the Esperanto Library

> Learn how to add a new AI provider to Open Notebook. This guide covers Esperanto library integration, environment variable registration, and model discovery implementation.

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

---

**Adding a new AI provider to Open Notebook requires three main steps: registering the provider's environment variables in [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py), implementing an async discovery function in [`model_discovery.py`](https://github.com/lfnovo/open-notebook/blob/main/model_discovery.py), and optionally extending the `Credential` class for complex authentication flows.**

Open Notebook leverages the **Esperanto** library as a unified abstraction layer for all AI providers. Whether integrating a custom LLM API or a specialized embedding service, you can add support by extending the provider registry and model discovery system—no changes to core business logic required.

## Step 1: Register Provider Credentials in key_provider.py

First, map your provider name to the environment variables that Esperanto expects. Open [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) and 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:

        # "env_var": "MYPROVIDER_API_BASE",

    },
}

```

The `provision_provider_keys()` function reads this configuration to inject database-stored credentials into the process environment. When a model requests provider `myprovider`, Esperanto can now fetch its configuration from the environment or from a linked `Credential` record.

## Step 2: Implement Model Discovery in model_discovery.py

To populate the model selection UI automatically, implement a discovery coroutine that fetches available models from your provider's API.

### Create the Discovery Function

Add an async function to [`open_notebook/ai/model_discovery.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/model_discovery.py) that returns a list of `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 []  # No credentials means no discovery

    
    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

```

### Register the Discovery Function

Associate your coroutine with the provider name in the `PROVIDER_DISCOVERY_FUNCTIONS` registry 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 this registry during the model synchronization process, automatically populating the SurrealDB `model` table with your provider's available models.

### Configure Model Type Classification

Ensure `classify_model_type` recognizes your provider's model naming conventions by updating the type mappings. This guarantees that `ModelManager.get_default_model()` returns the correct model subtype (Language, Embedding, STT, or TTS).

```python

# Inside 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

```

## Step 3: Handle Complex Authentication (Optional)

If your provider requires more than a simple API key (such as Azure endpoints or Vertex service accounts), 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
    
    def to_esperanto_config(self) -> Dict[str, Any]:
        cfg = super().to_esperanto_config()
        if self.myprovider_project:
            cfg["project"] = self.myprovider_project
        return cfg

```

The `to_esperanto_config()` method builds the configuration dictionary that Esperanto receives when creating model instances via `AIFactory`.

## Testing Your Integration

Once implemented, trigger model discovery to verify the integration:

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

# Sync models for your new provider

await sync_provider_models("myprovider")

# Test loading a specific model

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

# my_model is now an Esperanto-wrapped LanguageModel ready for generation

```

The provider automatically appears in **Settings → Models** after synchronization. The UI pulls data from `/api/routers/models`, which uses `ModelManager` to query the SurrealDB records created during discovery.

## Summary

- **Register credentials** in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) by adding your provider to `PROVIDER_CONFIG` with the appropriate environment variable mappings.
- **Implement discovery** 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 that fetches model IDs and registering it in `PROVIDER_DISCOVERY_FUNCTIONS`.
- **Classify model types** by extending the `type_mappings` dictionary in `classify_model_type` to ensure correct model categorization.
- **Extend credentials** in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) if your provider requires custom authentication fields beyond a standard API key.

## Frequently Asked Questions

### Does the UI require manual updates when adding a new provider?

No. The **Settings → Models** screen automatically displays new providers because it consumes the `/api/routers/models` endpoint, which queries the `ModelManager`. As long as your discovery function registers `Model` records in SurrealDB during the sync process, the UI updates without frontend code changes.

### How does Open Notebook securely handle API credentials?

Credentials are stored in SurrealDB via the `Credential` class. When a model is requested, `provision_provider_keys()` temporarily injects the credentials into the process environment variables that Esperanto reads. This keeps keys out of source code while ensuring the Esperanto library can authenticate with the provider's API.

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

Return a static list from your discovery function instead of making an HTTP request. Create `DiscoveredModel` instances for each known model ID and return them directly. The system will still register these models in the database during the sync operation.

### How do I ensure my provider's models are categorized correctly?

Update the `type_mappings` dictionary inside the `classify_model_type` function in [`model_discovery.py`](https://github.com/lfnovo/open-notebook/blob/main/model_discovery.py). Map your provider name to a dictionary containing substrings that identify each model type (e.g., `"embed"` for embeddings, `"tts"` for text-to-speech). Accurate classification ensures that `ModelManager.get_model()` returns the appropriate Esperanto wrapper class.