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

Adding a new AI provider to Open Notebook requires three main steps: registering the provider's environment variables in key_provider.py, implementing an async discovery function in 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 and add an entry to the PROVIDER_CONFIG dictionary.


# 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 that returns a list of DiscoveredModel objects:


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


# 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).


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


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

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 by adding your provider to PROVIDER_CONFIG with the appropriate environment variable mappings.
  • Implement discovery in 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 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. 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.

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 →