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

To extend Nanobot providers, subclass the LLMProvider base class in nanobot/providers/base.py, register a ProviderSpec entry in nanobot/providers/registry.py, and wire the backend into 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 defines the interface that every provider must implement, including the chat and chat_stream methods. The ProviderSpec registry in 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 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. 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.
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. 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.
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 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.

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. The registry can reference these through env_extras or default_extra_headers.


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

{
  "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 to implement the chat method and optional chat_stream.
  • Register a ProviderSpec in nanobot/providers/registry.py with unique keywords and backend identifier.
  • Wire the factory in 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 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.

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 →