# How to Extend Nanobot Providers: A Complete Guide to Adding Custom LLM Backends

> Learn how to extend Nanobot providers by subclassing LLMProvider, registering a ProviderSpec, and wiring the backend.  A complete guide to adding custom LLM backends.

- Repository: [✨Data Intelligence Lab@HKU✨/nanobot](https://github.com/HKUDS/nanobot)
- Tags: how-to-guide
- Published: 2026-07-22

---

**To extend Nanobot providers, subclass the `LLMProvider` base class in [`nanobot/providers/base.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/base.py), register a `ProviderSpec` entry in [`nanobot/providers/registry.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/registry.py), and wire the backend into [`nanobot/providers/factory.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/factory.py).**

Extending Nanobot providers allows you to integrate custom LLM services into the HKUDS/nanobot framework. Whether you're connecting to a private API or a new cloud provider, the modular architecture makes it straightforward to add support without modifying core logic. This guide walks through the exact file locations and code patterns required to extend Nanobot providers correctly.

## Understanding the Nanobot Provider Architecture

Before you extend Nanobot providers, understand the three core components that handle LLM interactions. The **`LLMProvider`** abstract base class in [`nanobot/providers/base.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/base.py) defines the interface that every provider must implement, including the `chat` and `chat_stream` methods. The **`ProviderSpec`** registry in [`nanobot/providers/registry.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/registry.py) serves as the single source of truth for provider metadata, keywords, and environment variable mappings. Finally, the factory in [`nanobot/providers/factory.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/factory.py) instantiates concrete provider classes based on configuration, handling API key injection and base URL resolution.

## Step-by-Step Guide to Extending Nanobot Providers

### Step 1 - Implement the Concrete Provider Class

Create a new file in `nanobot/providers/` that subclasses `LLMProvider` from [`nanobot/providers/base.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/base.py). You must implement the abstract `chat` method, and optionally `chat_stream` if your service supports streaming. If your API is OpenAI-compatible, consider subclassing **`OpenAICompatProvider`** instead to reuse existing logic.

Key implementation details:

- Set `supports_progress_deltas = True` if your API streams partial responses.
- Use `_sanitize_empty_content()` to clean message payloads.
- Return an **`LLMResponse`** instance containing content and optional tool calls.

```python
import httpx
from nanobot.providers.base import LLMProvider, LLMResponse, ToolCallRequest
from typing import Any, List

class MyProvider(LLMProvider):
    """Example provider that talks to a hypothetical OpenAI‑compatible endpoint."""
    supports_progress_deltas = True

    async def chat(
        self,
        messages: List[dict[str, Any]],
        tools: List[dict[str, Any]] | None = None,
        model: str | None = None,
        max_tokens: int = 4096,
        temperature: float = 0.7,
        reasoning_effort: str | None = None,
        tool_choice: str | dict[str, Any] | None = None,
    ) -> LLMResponse:
        payload = {
            "model": model or self.default_model,
            "messages": self._sanitize_empty_content(messages),
            "max_tokens": max_tokens,
            "temperature": temperature,
        }
        if tools:
            payload["tools"] = tools
        if tool_choice:
            payload["tool_choice"] = tool_choice

        async with httpx.AsyncClient() as client:
            resp = await client.post(
                f"{self.api_base}/chat/completions",
                headers={"Authorization": f"Bearer {self.api_key}"},
                json=payload,
                timeout=60,
            )
            data = resp.json()

        content = data.get("choices", [{}])[0].get("message", {}).get("content")
        tool_calls = []
        for tc in data.get("choices", [{}])[0].get("message", {}).get("tool_calls", []):
            tool_calls.append(
                ToolCallRequest(
                    id=tc["id"],
                    name=tc["function"]["name"],
                    arguments=tc["function"]["arguments"],
                )
            )
        return LLMResponse(content=content, tool_calls=tool_calls)

```

### Step 2 - Register the Provider in the Registry

Add a **`ProviderSpec`** entry to the `PROVIDERS` list in [`nanobot/providers/registry.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/registry.py). This specification tells Nanobot how to identify and configure your provider.

Critical fields:

- **`name`**: Unique identifier (e.g., `"myprovider"`).
- **`keywords`**: Tuple of strings for model-name matching (e.g., `("myprovider", "my-model")`).
- **`backend`**: Short identifier used by the factory (e.g., `"myprovider"`).
- **`env_key`**: Environment variable name for the API key.
- **`default_api_base`**: URL prefix for API calls.

```python
from nanobot.providers.registry import ProviderSpec

ProviderSpec(
    name="myprovider",
    keywords=("myprovider", "my-model"),
    env_key="MYPROVIDER_API_KEY",
    display_name="MyProvider",
    backend="myprovider",
    default_api_base="https://api.myprovider.com/v1",
    env_extras=(("MY_SPECIAL_HEADER", "{api_key}"),),
),

```

Place your entry in the list where you want it to rank for automatic matching; the registry order determines priority when multiple providers match a model prefix.

### Step 3 - Wire the Backend into the Factory

Extend the `make_provider` function in [`nanobot/providers/factory.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/factory.py) to recognize your new `backend` value. Insert a branch that imports and instantiates your provider class, passing the API key, base URL, and any extra configuration.

```python
elif backend == "myprovider":
    from nanobot.providers.myprovider import MyProvider

    provider = MyProvider(
        api_key=p.api_key if p else None,
        api_base=config.get_api_base(model, preset=resolved),
        default_model=model,
        extra_body=p.extra_body if p else None,
    )

```

The factory already handles validation of API keys, base URLs, and OAuth/local flags, so you only need to pass the relevant configuration pieces to your constructor.

### Step 4 - Add Provider-Specific Configuration (Optional)

If your service requires custom headers, query parameters, or authentication methods, expose them via **`ProviderConfig`** in [`nanobot/config/schema.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/config/schema.py). The registry can reference these through `env_extras` or `default_extra_headers`.

```python

# In nanobot/config/schema.py

class ProvidersConfig(BaseModel):
    # ... existing providers ...

    myprovider: ProviderConfig = Field(default_factory=ProviderConfig)

```

## Configuring and Using Your Extended Provider

Once implemented, configure your extended provider in `~/.nanobot/config.json`. Use the model prefix that matches your registered keywords.

```json
{
  "providers": {
    "myprovider": {
      "api_key": "sk-xxxxxxxxxxxx",
      "api_base": "https://api.myprovider.com/v1"
    }
  },
  "agents": {
    "defaults": {
      "model": "myprovider/gpt-4",
      "provider": "auto"
    }
  }
}

```

When you launch Nanobot with `"model": "myprovider/gpt-4"`, the system matches the `myprovider/` prefix to your `ProviderSpec`, constructs `MyProvider` via the factory, and routes all chat calls through your implementation.

## Summary

- **Subclass `LLMProvider`** in [`nanobot/providers/base.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/base.py) to implement the `chat` method and optional `chat_stream`.
- **Register a `ProviderSpec`** in [`nanobot/providers/registry.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/registry.py) with unique keywords and backend identifier.
- **Wire the factory** in [`nanobot/providers/factory.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/factory.py) by adding a branch to `make_provider` that instantiates your class.
- **Configure via JSON** using the model prefix matching your registered keywords to activate the provider.
- **Reuse `OpenAICompatProvider`** if your API follows OpenAI conventions to minimize boilerplate.

## Frequently Asked Questions

### Do I need to implement streaming support to extend Nanobot providers?

No, streaming is optional. Implement the `chat_stream` method only if your LLM service supports server-sent events or progressive deltas. For non-streaming providers, Nanobot will handle the response using your synchronous `chat` implementation. Set `supports_progress_deltas = False` (or omit it) to indicate that your provider returns complete responses only.

### Can I extend Nanobot providers for non-OpenAI-compatible APIs?

Yes, you can extend Nanobot providers for any HTTP-based LLM service. While subclassing `OpenAICompatProvider` simplifies integration for OpenAI-compatible endpoints, the base `LLMProvider` class is agnostic to the underlying protocol. Implement the `chat` method to handle your specific authentication headers, request payload structure, and response parsing.

### How does the provider registry prioritize matching when multiple keywords overlap?

The registry evaluates `PROVIDERS` in [`nanobot/providers/registry.py`](https://github.com/HKUDS/nanobot/blob/main/nanobot/providers/registry.py) sequentially, and the first match wins. Place your `ProviderSpec` higher in the list if you want it to take precedence over existing providers with similar keywords. The `keywords` tuple supports multiple aliases, allowing model names like `myprovider/gpt-4` or `my-model/llama` to resolve to the same backend.

### What environment variables are required when extending Nanobot providers?

At minimum, define an `env_key` in your `ProviderSpec` (e.g., `"MYPROVIDER_API_KEY"`). The factory automatically checks for this variable if no API key is provided in the config file. You can also specify `env_extras` for additional headers or configuration tokens that your provider requires beyond the standard API key.