src/voicegateway/inference/providers/ holds a BaseProvider ABC and 11 provider classes. Their only production use is health-checking: POST /v1/providers/{id}/test, voicegw doctor’s legacy key-validation check, and the MCP server’s provider-test tools all construct one via create_provider() and call await instance.health_check(). Their create_stt() / create_llm() / create_tts() methods have zero production callers; only two test files exercise them.
attach() and guard() never touch this layer. You construct native livekit.plugins.* / pipecat.services.* instances yourself; attach() reads their .provider/.model attributes off the live instance to build provider/model, and guard() type-checks the instance against livekit.agents.{stt,llm,tts} base classes. Neither looks the provider up in the registry.
BaseProvider ABC
File:src/voicegateway/inference/providers/base_provider.py
Method contracts
Pricing is not a provider-level concern. It resolves via
voice_prices.calc_price inside src/voicegateway/inference/pricing/, keyed entirely on the provider/model string; a provider class never participates.
How attach() and guard() actually relate to providers
attach() is the passive path: it subscribes to the events the plugin already emits and never constructs or wraps anything. guard() is the active path: it wraps the instance you pass it, checking rate limit and budget before delegating, and falling back to the next instance in its fallback=[...] list on a pre-first-token error. Neither path calls registry.create_provider() or consults BaseProvider. See attach() and guard() for the full signatures.
Provider registry
All 11 providers are registered insrc/voicegateway/core/registry.py as (module_path, class_name) tuples, loaded lazily via importlib.import_module(). See Gateway Core for the registry table and its three health-check callers.
Modality support matrix
When a provider does not support a modality, its
create_* method calls self._unsupported(), raising NotImplementedError. Since nothing in production calls create_*, this only surfaces in the two test files that exercise it directly.
Implementation pattern
Every provider follows the same structure:- API key resolution.
config.get("api_key")first (from YAML or managed providers), then the standard environment variable (DEEPGRAM_API_KEY,OPENAI_API_KEY, etc.). - Lazy SDK import. The
from livekit.plugins.deepgram import STTimport happens inside the method, not at module level, so the health-check path only pays the import cost for providers it actually touches. health_check()makes a minimal live request (for example, listing models) so a failed or missing key is caught at test time, not mid-call.
Adding a new provider
- Create
src/voicegateway/inference/providers/myprovider_provider.pyextendingBaseProvider. - Implement
create_stt/create_llm/create_tts(call_unsupported()for modalities it doesn’t support) andhealth_check(). - Register it in
src/voicegateway/core/registry.py: - No pricing step is needed:
voice_prices.calc_priceresolves cost from theprovider/modelstring at request time, not from a per-provider table in this repo. - Point the
ImportErrorinstall hint at the upstream plugin wheel. There is no per-provider extra inpyproject.toml, since VoiceGateway does not bundle provider wheels.