How aisuite Implements Provider Abstraction and the ProviderFactory Pattern

aisuite implements provider abstraction through a lightweight abstract base class Provider in aisuite/provider.py and instantiates concrete implementations via the dynamic ProviderFactory class using naming conventions and lazy imports.

aisuite is an open-source Python library that provides a unified interface for multiple large language model (LLM) and audio providers. The project, hosted at andrewyng/aisuite, achieves its goal of vendor-agnostic AI integration through a clean aisuite provider abstraction layer and a dynamic ProviderFactory pattern that eliminates hard dependencies on specific SDKs until runtime.

The Provider Abstract Base Class

Core Interface Definition

The foundation of aisuite's provider abstraction lives in aisuite/provider.py. The Provider abstract base class defines the contract that every concrete provider must implement:

  • chat_completions_create – Synchronous chat completion request.
  • achat_completions_create – Async version that runs the sync method in a thread pool by default.
  • chat_completions_create_stream / achat_completions_create_stream – Optional streaming interfaces enabled by overriding.
  • audio – An optional attribute holding an Audio implementation for transcription services.

# aisuite/provider.py (excerpt)

class Provider(ABC):
    def __init__(self):
        self.audio: Optional[Audio] = None

    @abstractmethod
    def chat_completions_create(self, model, messages):
        ...

    async def achat_completions_create(self, model, messages, **kwargs):
        return await asyncio.to_thread(
            lambda: self.chat_completions_create(model, messages, **kwargs)
        )

Synchronous and Asynchronous Support

The base class provides a default asynchronous implementation using asyncio.to_thread, which wraps the synchronous chat_completions_create method. This design allows providers to support async operations immediately without implementing custom async logic, while still permitting overrides for native async SDKs.

The ProviderFactory Pattern Implementation

Dynamic Provider Discovery

The ProviderFactory class in aisuite/provider.py implements the classic Factory pattern with filesystem discovery. The get_supported_providers method scans the aisuite/providers directory and caches results using @functools.cache:


# aisuite/provider.py (excerpt)

class ProviderFactory:
    PROVIDERS_DIR = Path(__file__).parent / "providers"

    @classmethod
    @functools.cache
    def get_supported_providers(cls):
        provider_files = Path(cls.PROVIDERS_DIR).glob("*_provider.py")
        return {file.stem.replace("_provider", "") for file in provider_files}

This approach returns a set of available provider keys (e.g., {"openai", "anthropic", "google"}) without importing any provider modules, keeping startup time minimal.

Lazy Loading and Instantiation

The create_provider method translates provider keys into module and class names using a strict naming convention, then uses importlib.import_module for lazy loading:

  1. Naming convention – A key like "openai" becomes module openai_provider and class OpenaiProvider.
  2. Dynamic import – The module is loaded only when requested, avoiding hard dependencies on unused SDKs.
  3. Configuration forwarding – The factory passes the user-supplied configuration dictionary directly to the provider's constructor.

# aisuite/provider.py (excerpt)

class ProviderFactory:
    @classmethod
    def create_provider(cls, provider_key, config):
        provider_class_name = f"{provider_key.capitalize()}Provider"
        provider_module_name = f"{provider_key}_provider"
        module_path = f"aisuite.providers.{provider_module_name}"
        module = importlib.import_module(module_path)
        provider_class = getattr(module, provider_class_name)
        return provider_class(**config)

Architecture Workflow: From Discovery to Execution

The aisuite provider abstraction and ProviderFactory pattern work together through five distinct phases:

  1. DiscoveryProviderFactory.get_supported_providers() reads the filesystem to build a list of available keys.
  2. Selection – Client code picks a provider key based on user configuration or command-line flags.
  3. InstantiationProviderFactory.create_provider(key, cfg) imports the matching module (e.g., aisuite.providers.openai_provider) and constructs the concrete class (e.g., OpenaiProvider).
  4. Uniform Interface – The caller interacts with the returned instance through the Provider abstract API, remaining oblivious to the underlying SDK (OpenAI, Anthropic, Azure, etc.).
  5. Audio Support – If the provider supports transcription, its constructor sets self.audio to an Audio subclass (e.g., OpenAIAudio), which follows the same abstract pattern (Audio.Transcription).

Because the factory relies only on naming conventions and dynamic imports, adding a new provider requires only creating a file <name>_provider.py defining a <Name>Provider class inheriting from Provider. No changes to the core factory logic are required.

Practical Implementation Examples

Creating a Provider from Configuration

from aisuite.provider import ProviderFactory

config = {"api_key": "sk-…", "base_url": "https://api.openai.com/v1"}
provider = ProviderFactory.create_provider("openai", config)

# Use the uniform API

response = provider.chat_completions_create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)

Listing All Available Providers

from aisuite.provider import ProviderFactory

available = ProviderFactory.get_supported_providers()
print("Supported providers:", sorted(available))

# → Supported providers: ['anthropic', 'aws', 'azure', 'cohere', …]

Using Async Helpers

import asyncio
from aisuite.provider import ProviderFactory

async def demo():
    prov = ProviderFactory.create_provider("anthropic", {"api_key": "…"})
    resp = await prov.achat_completions_create(
        model="claude-3-5-sonnet-20240620",
        messages=[{"role": "user", "content": "Explain quantum entanglement."}],
    )
    print(resp.content[0].text)

asyncio.run(demo())

Accessing Audio Transcription

provider = ProviderFactory.create_provider("openai", {"api_key": "…"})
transcript = provider.audio.transcriptions.create(
    model="whisper-1",
    file="speech.wav",
)
print(transcript.text)

Key Source Files

Summary

  • aisuite achieves vendor neutrality through an abstract Provider base class in aisuite/provider.py that standardizes chat completion and audio interfaces.
  • The ProviderFactory uses filesystem scanning and importlib to discover and instantiate providers dynamically, avoiding hard dependencies on unused SDKs.
  • Naming conventions (<key>_provider.py / <Key>Provider) enable automatic plugin discovery without registry modifications.
  • Lazy loading ensures that provider SDKs are imported only when explicitly requested, optimizing startup performance.
  • The default async implementation using asyncio.to_thread allows immediate async support for synchronous-only SDKs.

Frequently Asked Questions

How does aisuite handle async operations for providers that only offer synchronous SDKs?

The Provider base class in aisuite/provider.py provides a default achat_completions_create method that uses asyncio.to_thread to execute the synchronous chat_completions_create method in a separate thread. This allows all providers to support async operations immediately, while providers with native async SDKs can override the method for optimized performance.

What naming convention must I follow to add a new provider to aisuite?

You must create a file named <provider_key>_provider.py in the aisuite/providers/ directory containing a class named <Provider_key>Provider (capitalized first letter) that inherits from Provider. For example, a provider with key "cohere" requires cohere_provider.py containing class CohereProvider. The ProviderFactory relies on this convention to locate and instantiate the class dynamically.

How does ProviderFactory avoid importing all provider SDKs at startup?

The ProviderFactory uses lazy loading via importlib.import_module inside the create_provider method. Modules are imported only when a specific provider is requested. Additionally, get_supported_providers scans the filesystem for filenames ending in _provider.py without importing the modules, ensuring zero SDK overhead during discovery.

Can I use the provider abstraction without the ProviderFactory pattern?

Yes, you can instantiate concrete provider classes directly by importing them from their respective modules (e.g., from aisuite.providers.openai_provider import OpenaiProvider). However, using the ProviderFactory is recommended as it handles configuration validation, enforces the naming convention, and maintains the loose coupling that makes the architecture extensible.

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 →