Skip to main content
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 in src/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:
Key patterns:
  1. 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.).
  2. Lazy SDK import. The from livekit.plugins.deepgram import STT import happens inside the method, not at module level, so the health-check path only pays the import cost for providers it actually touches.
  3. 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

  1. Create src/voicegateway/inference/providers/myprovider_provider.py extending BaseProvider.
  2. Implement create_stt/create_llm/create_tts (call _unsupported() for modalities it doesn’t support) and health_check().
  3. Register it in src/voicegateway/core/registry.py:
  4. No pricing step is needed: voice_prices.calc_price resolves cost from the provider/model string at request time, not from a per-provider table in this repo.
  5. Point the ImportError install hint at the upstream plugin wheel. There is no per-provider extra in pyproject.toml, since VoiceGateway does not bundle provider wheels.