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 anAudioimplementation 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:
- Naming convention – A key like
"openai"becomes moduleopenai_providerand classOpenaiProvider. - Dynamic import – The module is loaded only when requested, avoiding hard dependencies on unused SDKs.
- 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:
- Discovery –
ProviderFactory.get_supported_providers()reads the filesystem to build a list of available keys. - Selection – Client code picks a provider key based on user configuration or command-line flags.
- Instantiation –
ProviderFactory.create_provider(key, cfg)imports the matching module (e.g.,aisuite.providers.openai_provider) and constructs the concrete class (e.g.,OpenaiProvider). - Uniform Interface – The caller interacts with the returned instance through the
Providerabstract API, remaining oblivious to the underlying SDK (OpenAI, Anthropic, Azure, etc.). - Audio Support – If the provider supports transcription, its constructor sets
self.audioto anAudiosubclass (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
aisuite/provider.py– Defines theProviderabstract base class,ProviderFactory, andAudiointerfaces. This is the core of the provider abstraction and factory implementation.aisuite/providers/openai_provider.py– Concrete implementation demonstrating how a provider plugs into the abstraction, including audio transcription support.aisuite/providers/– Directory containing all concrete provider implementations (e.g.,anthropic_provider.py,google_provider.py) following the established naming convention.aisuite/framework/provider_interface.py– Higher-level helper that wraps aProviderinstance for integration with the broader framework.
Summary
- aisuite achieves vendor neutrality through an abstract
Providerbase class inaisuite/provider.pythat standardizes chat completion and audio interfaces. - The
ProviderFactoryuses filesystem scanning andimportlibto 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_threadallows 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →