How to Add a Custom LLM Provider to aisuite Using the Adapter Pattern

To add a custom LLM provider to aisuite, create a Python module named <provider>_provider.py in aisuite/providers/, subclass the abstract Provider base class from aisuite/provider.py, and implement the chat_completions_create method while optionally using a MessageConverter to translate between aisuite's generic message format and your vendor-specific API schema.

aisuite is a unified Python interface for multiple large language model services developed by Andrew Ng. It isolates each backend service behind a common abstraction using the adapter pattern, allowing you to plug in custom LLMs without modifying the core library.

How the Adapter Pattern Works in aisuite

The architecture separates concerns into three layers: the abstract interface, the dynamic factory, and the adapter implementation.

The Provider Abstract Base
In aisuite/provider.py (lines 24-59), the Provider class defines the contract for chat completions, async operations, streaming, and audio transcription. Any custom provider must inherit from this base and implement the required abstract methods.

Dynamic Discovery via ProviderFactory
The ProviderFactory.create_provider method in aisuite/provider.py (lines 92-118) handles instantiation. It converts a configuration key like "myllm" into a class name MyllmProvider and dynamically imports the corresponding module aisuite/providers/myllm_provider.py. This follows the open-closed principle: you add new files without touching existing factory code.

Message Conversion Adapters
To keep providers thin, aisuite includes a MessageConverter base class in aisuite/providers/message_converter.py. Your adapter implements convert_request() to transform aisuite's Message objects into the vendor's JSON payload, and convert_response() to normalize the API response back into aisuite's framework objects.

Step-by-Step Implementation Guide

Step 1: Create the Provider Module

Create a new file in aisuite/providers/ following the strict naming convention: <key>_provider.py. The filename determines how users reference your provider in configuration.

Step 2: Subclass the Provider Abstract Base

Import Provider from aisuite/provider and implement the required methods. At minimum, you must implement chat_completions_create. You may also override achat_completions_create for async support and chat_completions_create_stream for streaming responses.

from aisuite.provider import Provider, LLMError
from typing import List, Dict, Any

class MyllmProvider(Provider):
    def __init__(self, endpoint: str, api_key: str, **_: dict):
        super().__init__()
        self.endpoint = endpoint
        self.api_key = api_key
    
    def chat_completions_create(self, model: str, messages: List[Dict[str, Any]], **kwargs):
        # Implementation details in Step 3

        pass

Step 3: Implement the Message Converter Adapter

Create an adapter class that inherits from MessageConverter to handle payload translation. This isolates format-specific logic from the provider's HTTP handling.

from aisuite.providers.message_converter import MessageConverter

class MyllmMessageConverter(MessageConverter):
    def convert_request(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Transform aisuite messages to MyLLM's expected format."""
        return [{"role": m["role"], "content": m["content"]} for m in messages]
    
    def convert_response(self, raw: Dict[str, Any]) -> Dict[str, Any]:
        """Extract the message from MyLLM's response structure."""
        return raw["choices"][0]["message"]

Instantiate this converter in your provider's __init__ and use it within chat_completions_create:

import requests

class MyllmProvider(Provider):
    def __init__(self, endpoint: str, api_key: str, **_: dict):
        super().__init__()
        self.endpoint = endpoint.rstrip("/")
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        self.transformer = MyllmMessageConverter()
    
    def chat_completions_create(self, model: str, messages: List[Dict[str, Any]], **kwargs):
        payload = {
            "model": model,
            "messages": self.transformer.convert_request(messages),
            **kwargs
        }
        try:
            resp = requests.post(
                f"{self.endpoint}/v1/chat/completions",
                json=payload,
                headers=self.headers
            )
            resp.raise_for_status()
            return self.transformer.convert_response(resp.json())
        except Exception as exc:
            raise LLMError(f"MyLLM request failed: {exc}")

Step 4: Handle Configuration

The Client passes a configuration dictionary directly to your provider's constructor via **config. Accept these parameters in __init__ to store API keys, base URLs, or timeout settings.

Common pattern from openai_provider.py:

  • Extract required parameters like api_key and base_url
  • Store headers or client instances as instance attributes
  • Pass optional parameters through to the underlying SDK or HTTP client

Step 5: Add Optional Audio Support

If your custom LLM offers speech-to-text capabilities, subclass Audio and define a Transcriptions inner class. Reference the implementation in aisuite/providers/openai_provider.py (lines 98-106):

from aisuite.provider import Audio

class MyllmProvider(Provider):
    def __init__(self, **config):
        super().__init__()
        self.audio = self.MyllmAudio(self)
    
    class MyllmAudio(Audio):
        def __init__(self, provider):
            self.provider = provider
        
        class Transcriptions:
            def create(self, file, model, **kwargs):
                # Implementation for audio transcription

                pass

Complete Working Example

Here is a full implementation demonstrating the adapter pattern with error handling and message conversion:


# File: aisuite/providers/myllm_provider.py

from typing import List, Dict, Any
import requests
from aisuite.provider import Provider, LLMError, Audio
from aisuite.providers.message_converter import MessageConverter

class MyllmMessageConverter(MessageConverter):
    """Adapter translating between aisuite and MyLLM API formats."""
    
    def convert_request(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        return [{"role": m["role"], "content": m["content"]} for m in messages]
    
    def convert_response(self, raw: Dict[str, Any]) -> Dict[str, Any]:
        return {
            "role": raw["choices"][0]["message"]["role"],
            "content": raw["choices"][0]["message"]["content"]
        }

class MyllmProvider(Provider):
    """Custom provider for MyLLM API using the adapter pattern."""
    
    def __init__(self, endpoint: str, api_key: str, **_: dict):
        super().__init__()
        self.endpoint = endpoint.rstrip("/")
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        self.transformer = MyllmMessageConverter()
    
    def chat_completions_create(self, model: str, messages: List[Dict[str, Any]], **kwargs):
        payload = {
            "model": model,
            "messages": self.transformer.convert_request(messages),
            **kwargs
        }
        
        try:
            response = requests.post(
                f"{self.endpoint}/v1/chat/completions",
                json=payload,
                headers=self.headers,
                timeout=30
            )
            response.raise_for_status()
            return self.transformer.convert_response(response.json())
        except requests.RequestException as exc:
            raise LLMError(f"MyLLM API error: {exc}")

Using Your Custom Provider

No registration code is required. Simply reference your provider by key in the configuration dictionary:

from aisuite import Client

config = {
    "myllm": {
        "endpoint": "https://api.my-llm.com",
        "api_key": "sk-your-key-here"
    }
}

client = Client(config=config)

response = client.chat.completions.create(
    model="myllm:gpt-4",
    messages=[{"role": "user", "content": "Explain the adapter pattern"}]
)
print(response.choices[0].message.content)

Summary

  • Create a module named <key>_provider.py in aisuite/providers/ to enable dynamic discovery by ProviderFactory.
  • Subclass Provider from aisuite/provider.py and implement chat_completions_create to satisfy the abstract contract.
  • Use a MessageConverter adapter to isolate translation logic between aisuite's generic format and your LLM's specific API schema.
  • Store configuration in __init__(self, **config) to receive API keys and endpoints passed by the Client.
  • Leverage auto-discovery: The factory converts keys like "myllm" into class names MyllmProvider automatically, requiring no manual registration.

Frequently Asked Questions

What file naming convention must I follow for aisuite to recognize my provider?

You must name your file <key>_provider.py where <key> is the lowercase identifier users will reference in configuration. For example, myllm_provider.py becomes accessible via the key "myllm" and instantiates the class MyllmProvider.

Do I need to register my custom provider anywhere in the codebase?

No. The ProviderFactory.create_provider method in aisuite/provider.py (lines 92-118) dynamically constructs the class name from your configuration key and imports the corresponding module. Simply adding the file to aisuite/providers/ completes registration.

Which methods are mandatory when subclassing the Provider abstract base?

You must implement chat_completions_create(self, model, messages, **kwargs). The Provider base class in aisuite/provider.py defines other methods like achat_completions_create and chat_completions_create_stream with default implementations that raise NotImplementedError, making them optional unless you need async or streaming support.

Can I reuse my MessageConverter for other providers?

Yes. Since MessageConverter is a separate class inheriting from aisuite/providers/message_converter.py, you can import and instantiate it in multiple provider files. This promotes code reuse when integrating APIs that share similar request/response schemas, such as OpenAI-compatible endpoints.

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 →