Provider Pattern
SmarterAvatar uses a provider adapter pattern to abstract AI services behind consistent interfaces.
Why Adapters?
Different AI providers have different APIs, authentication methods, and capabilities. The adapter pattern provides:
- Consistent interface - Same code works with any provider
- Easy switching - Change provider via environment variable
- Future-proof - Add new providers without modifying core code
- Testing - Mock providers for development and testing
Provider Types
LLM Providers
Handle text generation and reasoning:
interface LLMProvider {
chat(messages: Message[], options?: ChatOptions): Promise<Response>;
stream(messages: Message[], options?: ChatOptions): AsyncIterable<Chunk>;
getCapabilities(): ProviderCapabilities;
}
Available: Gemini, OpenAI, Anthropic
STT Providers
Handle speech-to-text conversion:
interface STTProvider {
transcribe(audio: Buffer, options?: TranscribeOptions): Promise<Transcript>;
getCapabilities(): ProviderCapabilities;
}
Available: Whisper (OpenAI), Deepgram
Avatar Providers
Handle video avatar synthesis:
interface AvatarProvider {
createSession(config: SessionConfig): Promise<Session>;
speak(sessionId: string, text: string): Promise<void>;
endSession(sessionId: string): Promise<void>;
}
Available: HeyGen LiveAvatar
Configuration
Providers are selected via environment variables:
# LLM Provider
LLM_PROVIDER=gemini # gemini | openai | anthropic
# STT Provider
STT_PROVIDER=whisper # whisper | deepgram
# Avatar (auto-detected from API key)
LIVEAVATAR_API_KEY=... # HeyGen
Provider Selection
At runtime, factory functions return the configured provider:
// Get the configured LLM provider
const llm = getLLMProvider();
const response = await llm.chat(messages);
// Get the configured STT provider
const stt = getSTTProvider();
const transcript = await stt.transcribe(audioBuffer);
Capabilities
Each provider reports its capabilities:
const capabilities = llm.getCapabilities();
// {
// streaming: true,
// functionCalling: true,
// vision: true,
// maxTokens: 1000000,
// contextWindow: 1000000
// }
This allows the application to adapt behavior based on provider features.
Adding New Providers
To add a new LLM provider:
- Create an adapter implementing the
LLMProviderinterface - Register it in the provider factory
- Add configuration to
.env.example - Document in the providers section
The modular design means new providers don't require changes to the rest of the application.
Error Handling
All providers use consistent error types:
try {
const response = await llm.chat(messages);
} catch (error) {
if (error instanceof ProviderAuthError) {
// Invalid API key
} else if (error instanceof ProviderRateLimitError) {
// Rate limited
} else if (error instanceof ProviderUnavailableError) {
// Service down
}
}