The export stage converts the trained PyTorch classifier to ONNX or TFLite for deployment. The inference API provides WakeWordModel for prediction and WakeWordListener for async microphone detection.
Source: src/livekit/wakeword/export/onnx.py, src/livekit/wakeword/export/tflite.py, src/livekit/wakeword/inference/model.py, src/livekit/wakeword/inference/listener.py
CLI: livekit-wakeword export <config> [--format onnx|tflite]
The output format is chosen by (in priority order) the --format flag, then the output_format field in the config (defaults to onnx).
export_onnx() exports the trained PyTorch classifier head to ONNX format.
| Property | Value |
|---|---|
| Input name | embeddings |
| Input shape | (1, 16, 96) with dynamic batch axis |
| Output name | score |
| Output shape | (1, 1) with dynamic batch axis |
| Opset version | 18 |
quantize_onnx() applies dynamic INT8 quantization using onnxruntime.quantization:
- Weight type:
QuantType.QInt8 - Output filename:
<model_name>.int8.onnx
Enable via the --quantize flag:
livekit-wakeword export configs/hey_jarvis.yaml --quantizerun_export(config, quantize=False, format=None) loads the trained model from output/<model_name>/<model_name>.pt, exports it to ONNX, and optionally quantizes it. format defaults to config.output_format. ONNX is always produced (it is the conversion source for TFLite); when format="tflite", the TFLite artifact is produced as well and its path is returned. Raises FileNotFoundError if the trained model doesn't exist.
export_tflite() converts an exported ONNX classifier to TFLite via onnx2tf (ONNX → TF SavedModel → TFLite), producing an artifact that openWakeWord can load directly.
Requires the optional extra:
uv sync --extra tflite # or: pip install 'livekit-wakeword[tflite]'livekit-wakeword export configs/hey_jarvis.yaml --format tfliteopenWakeWord loads classifier models with ai_edge_litert.interpreter and runs them without resizing tensors, so the artifact must satisfy:
| Requirement | Detail |
|---|---|
| Input shape | Static (1, 16, 96) float32 (no dynamic batch — openWakeWord never calls resize_tensor_input) |
| Output shape | (1, 1) float32 sigmoid score |
| Ops | Builtin TFLite ops only — the LiteRT interpreter has no Flex/SELECT_TF delegate |
We pin the input shape with onnx2tf's overwrite_input_shape + keep_shape_absolutely_input_names (without the latter, onnx2tf's NCHW→NHWC pass transposes the input to (1, 96, 16)) and restrict the converter to TFLITE_BUILTINS.
| Head | TFLite export | Notes |
|---|---|---|
dnn |
Supported | Bit-exact vs ONNX/PyTorch (verified, maxdiff 0.0) |
conv_attention |
Not supported | onnx2tf emits an unsupported constant for the attention block |
rnn |
Not supported | LSTM lowers to TensorList ops requiring the Flex delegate (which openWakeWord can't load) |
Use dnn for openWakeWord-compatible TFLite; deploy conv_attention/rnn via ONNX. Requesting TFLite for an unsupported head raises NotImplementedError before any export work begins.
Source: src/livekit/wakeword/inference/model.py, src/livekit/wakeword/inference/listener.py
The WakeWordModel class is a stateless prediction API for wake word detection. Pass a complete audio window (~2 seconds) and receive confidence scores.
from livekit.wakeword import WakeWordModel
model = WakeWordModel(models=["hey_livekit.onnx"])
# Pass ~2 seconds of 16kHz audio
scores = model.predict(audio_chunk)
# Returns: {"hey_livekit": 0.95}WakeWordModel(
models: list[str | Path] | None = None, # Paths to ONNX classifiers
)Feature extraction models (melspectrogram.onnx, embedding_model.onnx) are bundled with the package and loaded automatically.
| Method | Returns | Description |
|---|---|---|
predict(audio_chunk) |
dict[str, float] |
Scores for each loaded model (0-1) |
load_model(path, name) |
None |
Load additional wake word model |
- Format: 16kHz mono, int16 or float32
- Chunk size: ~2 seconds (32,000 samples) recommended — yields 16 embeddings for the classifier
- Stateless: No internal audio buffering; the caller manages the audio window
The WakeWordListener class provides async microphone detection with debouncing.
import asyncio
from livekit.wakeword import WakeWordModel, WakeWordListener
model = WakeWordModel(models=["hey_livekit.onnx"])
async def main():
async with WakeWordListener(model, threshold=0.5, debounce=2.0) as listener:
while True:
detection = await listener.wait_for_detection()
print(f"Detected {detection.name}! ({detection.confidence:.2f})")
asyncio.run(main())WakeWordListener(
model: WakeWordModel, # WakeWordModel instance with loaded classifiers
threshold: float = 0.5, # Detection threshold (0-1)
debounce: float = 2.0 # Minimum seconds between detections
)@dataclass
class Detection:
name: str # Model name that triggered
confidence: float # Score (0-1)
timestamp: float # Monotonic timeThe listener is designed as an async context manager. On each __aenter__, all internal state is reset — including the audio buffer, error state, and detection queue — so the same listener instance can be safely reused across multiple async with blocks without stale detections carrying over.
Uses PyAudio to capture from the default microphone:
| Parameter | Value |
|---|---|
| Format | int16 (paInt16) |
| Channels | 1 (mono) |
| Sample rate | 16,000 Hz |
| Buffer size | 1,280 samples (80ms) |