Provider Abstraction
All 11 providers in VoiceGateway implement the same abstract base class, giving the core layer a uniform interface regardless of whether the underlying service is a cloud API or a local model.
Provider Abstraction
All 11 providers in VoiceGateway implement the same abstract base class, giving the core layer a uniform interface regardless of whether the underlying service is a cloud API or a local model.
BaseProvider ABC
File: src/voicegateway/providers/base.py
class BaseProvider(ABC):
@abstractmethod
def create_stt(self, model: str, **kwargs: Any) -> Any:
"""Create an STT instance."""
...
@abstractmethod
def create_llm(self, model: str, **kwargs: Any) -> Any:
"""Create an LLM instance."""
...
@abstractmethod
def create_tts(self, model: str, voice: str | None = None, **kwargs: Any) -> Any:
"""Create a TTS instance."""
...
@abstractmethod
async def health_check(self) -> bool:
"""Check if the provider is reachable."""
...
def _unsupported(self, modality: str) -> None:
"""Raise NotImplementedError for unsupported modalities."""
raise NotImplementedError(
f"{self.__class__.__name__} does not support {modality}"
)Method Contracts
| Method | Returns | Unsupported Behavior |
|---|---|---|
create_stt(model, **kwargs) | LiveKit-compatible STT instance | Call self._unsupported("stt") |
create_llm(model, **kwargs) | LiveKit-compatible LLM instance | Call self._unsupported("llm") |
create_tts(model, voice, **kwargs) | LiveKit-compatible TTS instance | Call self._unsupported("tts") |
health_check() | True if reachable, False otherwise | Must always be implemented |
Pricing is no longer a provider-level concern. Rates for all three modalities (LLM, STT, and TTS) resolve via voice-prices (see the wrapper modules src/voicegateway/inference/pricing/{llm,stt,tts}.py, which call voice_prices.calc_price). Use voicegateway.inference.pricing.catalog.calculate_cost(modality, model, ...) from anywhere that needs a per-request cost.
Argument order trap.
voicegateway.pricing.catalog.calculate_cost(modality, model, ...)takesmodalityfirst; the legacyCostTracker.calculate_cost(model_id, modality, ...)onvoicegateway.middleware.cost_trackertakesmodel_idfirst. The two helpers serve different layers (catalog is the pricing facade; CostTracker bridges to the storage record), but the reversed positional order is easy to transpose. When in doubt, use keyword arguments or call the catalog directly.
Provider Registry
All 11 providers are registered in src/voicegateway/core/registry.py as (module_path, class_name) tuples. The Registry uses importlib.import_module() for lazy loading -- a provider's SDK is only imported when that provider is first used.
Modality Support Matrix
| Provider | STT | LLM | TTS | Install Extra |
|---|---|---|---|---|
| OpenAI | Yes | Yes | Yes | openai |
| Deepgram | Yes | -- | -- | deepgram |
| Cartesia | -- | -- | Yes | cartesia |
| Anthropic | -- | Yes | -- | anthropic |
| Groq | Yes | Yes | -- | groq |
| ElevenLabs | -- | -- | Yes | elevenlabs |
| AssemblyAI | Yes | -- | -- | assemblyai |
| Ollama | -- | Yes | -- | ollama |
| Whisper | Yes | -- | -- | whisper |
| Kokoro | -- | -- | Yes | kokoro |
| Piper | -- | -- | Yes | piper |
When a provider does not support a modality, its create_* method calls self._unsupported(), which raises NotImplementedError. This propagates cleanly through the Router and Gateway layers.
Implementation Pattern
Every provider follows the same structure:
class DeepgramProvider(BaseProvider):
"""Deepgram STT provider."""
def __init__(self, config: dict[str, Any]):
self._api_key = config.get("api_key") or os.environ.get("DEEPGRAM_API_KEY", "")
# Provider-specific initialization
def create_stt(self, model: str, **kwargs: Any) -> Any:
from livekit.plugins.deepgram import STT
return STT(model=model, api_key=self._api_key, **kwargs)
def create_llm(self, model: str, **kwargs: Any) -> Any:
self._unsupported("llm")
def create_tts(self, model: str, voice: str | None = None, **kwargs: Any) -> Any:
self._unsupported("tts")
async def health_check(self) -> bool:
# Lightweight API call to verify credentials
...Key patterns:
-
API key resolution:
config.get("api_key")first (from YAML or managed providers), then fall back to the standard environment variable (DEEPGRAM_API_KEY,OPENAI_API_KEY, etc.). -
LiveKit plugin wrapping: each
create_*method returns a LiveKit Agents plugin instance (livekit.plugins.deepgram.STT,livekit.plugins.openai.LLM, etc.), making VoiceGateway a drop-in replacement for direct LiveKit plugin usage. -
Lazy SDK import: the
from livekit.plugins.deepgram import STThappens inside the method, not at module level. This allows installing only the providers you need.
Modular Installation
Each provider is an optional dependency:
# Install only what you need
pip install voicegateway[openai,deepgram,cartesia]
# Install everything
pip install voicegateway[all]
# Local-only stack (no cloud SDKs needed)
pip install voicegateway[whisper,kokoro]If a provider's SDK is missing, the Registry raises a clear ImportError:
Could not import provider 'deepgram': No module named 'deepgram'.
Install with: pip install voicegateway[deepgram]Adding a New Provider
- Create
src/voicegateway/providers/myprovider_provider.pyextendingBaseProvider - Implement the five abstract methods (use
_unsupported()for unsupported modalities) - Register it in
src/voicegateway/core/registry.py:Python "myprovider": ("voicegateway.providers.myprovider_provider", "MyProviderProvider"), - Add pricing data to
src/voicegateway/pricing/catalog.py - Add the optional dependency to
pyproject.toml
Middleware
The middleware layer sits between the Gateway and provider instances, providing cross-cutting concerns: cost tracking, latency monitoring, rate limiting, fallback chains, budget enforcement, and reque
Security
VoiceGateway encrypts all API keys stored in its database, masks secrets in API responses, and maintains an audit log of configuration changes.