Hybrid voice assistant. Supports local and API models.
For local mode hardware requirements: GPU with 8GB VRAM
uv sync # base dependencies
uv sync --extra cuda # + CUDA 12 runtime libs for GPU STT (see below)Python 3.11, package manager uv.
Listens to the microphone via openWakeWord, prints Wake word! on detection. Useful
for tuning --threshold / --gain without loading VAD/STT.
uv run test-wakeword
uv run test-wakeword --debug-scores # print per-model scores
uv run test-wakeword --list-devices # pick a microphoneWaits for wake word → plays earcon → records speech → stops recording after a silence
pause (5 sec by default) → transcribes via faster-whisper
→ prints text to stdout. Loops; --once for a single command.
uv run test-stt
uv run test-stt --once
uv run test-stt --silence-sec 2 --language ru
uv run test-stt --no-earcon # skip confirmation soundTranscribed text goes to stdout, all status/diagnostics — to stderr.
Same flow as test-stt, but after transcription it speaks the text back
via Piper TTS (echo). Useful for testing the full voice pipeline.
uv run test-tts
uv run test-tts --once
uv run test-tts --tts-model ru_RU-irina-medium # change voice
uv run test-tts --tts-speaker 0 # multi-speaker models
uv run test-tts --no-tts # equivalent to test-sttVoice configuration: TTS_MODEL=ru_RU-irina-medium in .env or --tts-model.
The model is looked up in TTS_MODEL_PATH (default .local/piper) by prefix —
e.g. ru_RU-irina-medium will find ru_RU-irina-medium.onnx. Download voices:
huggingface.co/rhasspy/piper-voices.
The main graph-eval datasets are split by language:
evals/datasets/command_graph_ru.yaml and
evals/datasets/command_graph_en.yaml. They validate the full multi-turn
dialog: slot filling, continuation, policy, and permit issuance. For
diagnosing individual draft extraction, use
nlu_drafts_ru.yaml and nlu_drafts_en.yaml.
uv run eval-nlu --language ru
uv run eval-nlu --language en --min-composite 0.85
uv run eval-nlu --language all --repeat 3
uv run eval-nlu-graph --language ru
uv run eval-nlu-graph --language all --repeat 3Each run saves a full JSON report to evals/reports/. This directory is
gitignored; YAML datasets and their schema are versioned golden data.
Local JSONL logging is always on and writes to logs/voice-assistant.jsonl
by default.
LOGFIRE_TOKENenables trace export to Logfire.- Langfuse is enabled only when
LANGFUSE_PUBLIC_KEY,LANGFUSE_SECRET_KEY, andLANGFUSE_BASE_URLare all set. - Langfuse credentials do not require
LOGFIRE_TOKEN: Langfuse will receive the same OpenTelemetry traces independently. - With an incomplete set of Langfuse variables or a backend error, the app continues to work and retains local logs/reports.
When running evals with Langfuse enabled, Datasets, Dataset Runs, links to case traces, and evaluator scores are created without re-running the model.
faster-whisper runs on top of ctranslate2, which is built against CUDA 12. If you don't have the CUDA 12 toolkit installed, install runtime libs from pip wheels:
uv sync --extra cudaThis installs nvidia-cublas-cu12, nvidia-cuda-runtime-cu12, nvidia-cudnn-cu12
into the venv. Their shared libraries are loaded automatically before importing
ctranslate2 (see src/voice_assistant/cuda_bootstrap.py) — no manual
configuration needed.
On a system with incompatible CUDA (e.g. CUDA 13) or without a GPU, test-stt
will still work: on CUDA initialization failure it automatically falls back
to CPU (--stt-device cpu, compute_type=int8).
Model weights are stored in .local/ (gitignored):
openwakeword/— melspectrogram, embedding model, wake word models (hey_jarvis),silero_vad.onnx. Downloaded automatically by openWakeWord on the first run oftest-wakeword/test-stt.- whisper
small(~480 MB) is downloaded to the HF cache on the first run oftest-stt. piper/— voice models for TTS (.onnx+.onnx.json). Download manually from Piper Voices.
src/voice_assistant/
├── core/
│ ├── config.py # env configuration (.env)
│ ├── device.py # audio device selection/listing
│ └── paths.py # paths to .local/ models
├── modules/voice/
│ ├── model.py # AudioSegment, Transcript
│ ├── stt.py # STT protocol
│ ├── tts.py # TTS protocol
│ └── adapters/
│ ├── wake_word.py # WakeWordDetector (openWakeWord)
│ ├── vad_silero.py # VadSilero (ONNX, no torch)
│ ├── earcon_player.py # EarconPlayer (numpy beep)
│ ├── audio_recorder.py # AudioRecorder
│ ├── stt_faster_whisper.py # FasterWhisperSTT
│ └── tts_piper.py # PiperTTS (local TTS)
└── entrypoints/
├── wakeword_cli.py # test-wakeword
├── stt_cli.py # test-stt (state machine)
└── tts_cli.py # test-tts (STT + TTS echo)