aisuite Provider Authentication Pattern: API Keys, IAM Roles, and Environment Variables Explained
aisuite implements provider-specific authentication through a base Provider class with a standardized **config constructor that falls back to environment variables, enabling both explicit API keys and implicit IAM role-based access.
aisuite unifies dozens of LLM and ASR services behind a single interface. The library's authentication architecture lets developers pass credentials directly or rely on environment-based discovery, depending on each provider's security model. This article examines the implementation pattern for provider-specific authentication in aisuite, from the abstract base class through concrete provider implementations.
The Three-Layer Authentication Architecture
aisuite separates authentication concerns across three components:
Providerabstract base class — defines the contract all providers must implementProviderFactory— dynamically loads provider modules using naming conventions- Concrete provider classes — implement credential retrieval and client initialization
Base Provider Contract
The abstract base class in aisuite/provider.py establishes the interface that every provider must satisfy.
# aisuite/provider.py#L24-L30
class Provider(ABC):
@abstractmethod
def chat_completions_create(self, model, messages, **kwargs):
"""Create a chat completion for the given model."""
pass
Concrete providers inherit from Provider and only implement the chat and audio methods. Authentication logic lives entirely in the subclass constructor.
ProviderFactory: Dynamic Loading with Config Injection
The ProviderFactory.create_provider() method handles runtime discovery and instantiation. It imports modules following the pattern <provider_key>_provider and instantiates <ProviderKey>Provider, passing the complete configuration dictionary.
# aisuite/provider.py#L92-L106
@staticmethod
def create_provider(provider_key: str, config: dict):
module_name = f"aisuite.providers.{provider_key}_provider"
module = importlib.import_module(module_name)
class_name = f"{provider_key.title().replace('_', '')}Provider"
provider_class = getattr(module, class_name)
return provider_class(**config)
This design means all authentication parameters travel through the **config dictionary, with each provider deciding how to interpret those values.
The Six-Step Authentication Pattern
Every concrete provider in aisuite follows an identical credential handling sequence:
- Accept
**config— constructor signature is alwaysdef __init__(self, **config) - Prefer explicit config — check
config.get("api_key"),config.get("base_url"), etc. - Fall back to environment — use
os.getenv("PROVIDER_API_KEY")when config values are absent - Validate presence — raise
ValueErrorwith a clear message if required credentials are missing - Initialize vendor client — pass normalized config to the underlying SDK
- Store client and converter — keep references for request/response normalization
API Key-Based Authentication Examples
OpenAI Provider
OpenAI requires an API key that can come from either explicit config or the OPENAI_API_KEY environment variable.
# aisuite/providers/openai_provider.py#L15-L23
class OpenAiProvider(Provider):
def __init__(self, **config):
self.config = config
# config dict is passed directly to openai.OpenAI()
# which reads api_key from config or OPENAI_API_KEY env var
self.client = openai.OpenAI(**config)
The openai Python SDK itself handles the environment fallback, so aisuite delegates this responsibility.
Azure Provider
Azure OpenAI requires both an API key and base URL, with explicit validation.
# aisuite/providers/azure_provider.py#L81-L86
def __init__(self, **config):
self.api_key = config.get("api_key", os.getenv("AZURE_API_KEY"))
self.base_url = config.get("base_url", os.getenv("AZURE_BASE_URL"))
if not self.api_key or not self.base_url:
raise ValueError("Azure API key and base URL are required")
Groq Provider
Groq demonstrates the full pattern with explicit environment fallback.
# aisuite/providers/groq_provider.py#L31-L40
class GroqProvider(Provider):
def __init__(self, **config):
self.api_key = config.get("api_key", os.getenv("GROQ_API_KEY"))
if not self.api_key:
raise ValueError("GROQ_API_KEY is not set")
self.client = groq.Groq(api_key=self.api_key)
IAM Role Authentication: AWS Bedrock Pattern
AWS Bedrock represents the alternative model where no API key exists. Instead, authentication relies on AWS's standard credential provider chain.
# aisuite/providers/aws_provider.py#L17-L23
class AwsProvider(Provider):
def __init__(self, **config):
self.config = BedrockConfig(**config) # reads AWS_REGION env var, defaults to us-west-2
self.client = self.config.create_client() # boto3 resolves IAM role, instance profile, etc.
The BedrockConfig class extracts the region from config or AWS_REGION, then boto3.client("bedrock-runtime") automatically discovers credentials through:
- Environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) - IAM roles attached to EC2 instances or ECS tasks
- AWS SSO or IAM Identity Center profiles
- Local AWS CLI configuration
This means zero secrets appear in code or environment variables when running on properly configured AWS infrastructure.
Practical Usage Examples
Explicit API Key Configuration
from aisuite.provider import ProviderFactory
config = {
"api_key": "sk-my-openai-key", # omit if OPENAI_API_KEY is set
"base_url": "https://api.openai.com/v1"
}
openai = ProviderFactory.create_provider("openai", config)
response = openai.chat_completions_create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}]
)
print(response)
IAM Role-Based Access (No Secrets Required)
# Running on AWS infrastructure with IAM role attached
aws = ProviderFactory.create_provider("aws", {}) # empty config
resp = aws.chat_completions_create(
model="anthropic.claude-v2",
messages=[{"role": "user", "content": "Summarize this text"}],
maxTokens=512,
temperature=0.7
)
print(resp)
Direct Provider Instantiation with Environment Fallback
import os
os.environ["GROQ_API_KEY"] = "gsk-my-groq-key"
from aisuite.providers.groq_provider import GroqProvider
groq = GroqProvider() # api_key pulled from GROQ_API_KEY automatically
stream = groq.chat_completions_create(
model="llama3-groq-70b-8192-tool-use-preview",
messages=[{"role": "user", "content": "What is the capital of France?"}]
)
print(stream)
Key Implementation Files
| File | Purpose |
|---|---|
aisuite/provider.py |
Abstract Provider class and ProviderFactory |
aisuite/providers/openai_provider.py |
OpenAI API key handling |
aisuite/providers/azure_provider.py |
Azure OpenAI with dual credential requirement |
aisuite/providers/groq_provider.py |
Groq with environment fallback |
aisuite/providers/aws_provider.py |
AWS Bedrock IAM role authentication |
aisuite/providers/message_converter.py |
Request/response normalization |
Summary
- aisuite authentication is provider-agnostic at the interface level — the
ProviderFactoryand base class know nothing about credential mechanisms - Concrete providers implement the six-step pattern: accept
**config, prefer explicit values, fall back to environment, validate, initialize client, store references - API key providers (OpenAI, Groq, Azure) require explicit secrets that can pass through config or environment variables
- IAM role providers (AWS Bedrock) delegate credential discovery to the underlying SDK, enabling secret-free operation on cloud infrastructure
- Adding new providers requires only: creating
<name>_provider.py, inheriting fromProvider, and following the constructor convention
Frequently Asked Questions
How does aisuite handle missing API keys?
Each concrete provider validates required credentials in __init__ and raises a descriptive ValueError if credentials are absent. For example, the Azure provider checks both api_key and base_url, while Groq validates only api_key.
Can I use AWS Bedrock without any environment variables?
Only AWS_REGION is recommended; it defaults to us-west-2 if unset. No API keys, access keys, or secrets are required when running on AWS infrastructure with an attached IAM role. The boto3 library resolves credentials through the standard AWS credential provider chain.
Why does aisuite use **config instead of explicit parameters?
The **config pattern enables forward compatibility and provider-specific extensions without modifying the base interface. Each provider extracts the keys it needs while ignoring others, allowing the same configuration dictionary to contain parameters for multiple providers simultaneously.
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 →