Switch wake word from Porcupine to openwakeword + training pipeline

- Add training/ pipeline (step_1..step_5) and own-samples flow
- record_wav.py with single-shot and long-record modes, RMS-based silence filter
- remove_silent.py to drop silent samples and renumber
- modes.py: openwakeword inference with reset() and quiet predictions; commented Lusya block for later
- stt.py: drop local faster-whisper fallback, Groq-only
- config.py: remove unused STT_PROVIDER/WHISPER_*
- llm.py: replace __import__("os") hack with proper import
- tts.py: remove debug traceback in play_error_sound
- requirements.txt: add openwakeword/sounddevice/scipy, drop faster-whisper
- deploy/setup.sh: validate ELEVENLABS_API_KEY and WAKE_WORD_COSMO presence
- README.md, CLAUDE.md, project_roadmap memory updated to reflect new architecture
This commit is contained in:
2026-04-13 15:40:44 +03:00
parent 0a89bf5105
commit 780f6f0084
13 changed files with 378 additions and 140 deletions

173
README.md Normal file
View File

@@ -0,0 +1,173 @@
# Cosmo Voice Satellite
Домашний голосовой ассистент. Слушает wake word, распознаёт речь, ходит в OpenClaw gateway, проигрывает ответ через ElevenLabs.
Два агента: **Cosmo** (владельца) и **Люся** (жены) — каждый со своим wake word и своим gateway.
## Архитектура
```
mic ─► wake word (openwakeword) ─► STT (Groq) ─► OpenClaw gateway ─► TTS (ElevenLabs) ─► mpv ─► speakers
```
- **Wake word:** openwakeword (обучается на своих записях, см. ниже). Раньше планировался Porcupine — отказались.
- **STT:** Groq API, `whisper-large-v3-turbo`, ru.
- **LLM:** OpenClaw gateway на N100 (`192.168.31.103:18789` для cosmo, `:18790` для lusya), `openai/gpt-5.4-mini`.
- **TTS:** ElevenLabs `eleven_flash_v2_5` стримом через mpv stdin.
## Структура
```
home-voice-assistant/
├── satellite.py # entry-обёртка
├── satellite/ # рантайм
│ ├── __main__.py # python -m satellite [--wake]
│ ├── config.py, text.py
│ ├── stt.py, audio.py, tts.py, llm.py
│ └── modes.py # run_with_enter / run_with_porcupine (wake word)
├── record_wav.py # запись датасета для wake word
├── remove_silent.py # чистка тихих + перенумерация
├── training/ # пайплайн обучения wake word
│ ├── step_1.py … step_5.py
│ ├── training_config.json
│ ├── own_samples/<word>/{positive,negative}/*.wav
│ ├── openwakeword/ # форк
│ └── my_custom_model/<word>/ # фичи + .onnx
├── data/models/ # готовые .onnx wake word моделей
└── deploy/ # setup.sh + systemd unit для Pi
```
## Запуск
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # заполнить ключи
python satellite.py # режим Enter (без wake word, для отладки)
python satellite.py --wake # режим wake word (нужна обученная модель в data/models/)
```
Системные зависимости:
- Python 3.12+
- `portaudio``brew install portaudio`
- `mpv``brew install mpv`
## Обучение своего wake word
OpenWakeWord обучает DNN-модель на твоих записях слова. Пайплайн в `training/`:
| Шаг | Что делает |
|-----|-----------|
| `step_1.py` | Установка зависимостей (piper, openwakeword) |
| `step_2.py` | Создаёт `training_config.json` (параметры обучения) |
| `step_3.py` | Скачивает датасеты (audioset, fma, RIRs, ACAV features) — ~17 GB |
| `step_4.py` | Аугментация → тренировка → экспорт `.onnx` в `data/models/` |
| `step_5.py` | Проверка моделей и подсказки для `.env` |
### Запись датасета
```bash
# по одной записи (Enter → 2 секунды → сохраняем)
python record_wav.py cosmo positive
python record_wav.py cosmo negative
# непрерывно N секунд → нарезаем по 2с, тишину выкидываем
python record_wav.py cosmo negative long 300
INPUT_DEVICE=1 python record_wav.py cosmo negative long 600
```
`record_wav.py` отбраковывает тихие записи по `MIN_RMS=300` (изменить можно константой в начале файла).
### Чистка
```bash
python remove_silent.py
```
Удаляет файлы с RMS ниже порога и переименовывает оставшиеся в `001.wav … NNN.wav`.
### Тренировка
1. В `training/training_config.json` укажи `wake_word_list`, `use_own_samples: true`, параметры:
```json
{
"wake_word_list": ["cosmo"],
"use_own_samples": true,
"false_activation_penalty": 100,
"target_false_positives_per_hour": 3.0,
"target_recall": 0.5,
"number_of_training_steps": 3000,
"layer_size": 64
}
```
2. В `training/openwakeword/examples/custom_model.yml` подними `augmentation_rounds: 10` (или больше).
3. Снеси кэш если был старый запуск:
```bash
rm -rf training/my_custom_model/<word> data/models/<word>.onnx
```
4. Запусти:
```bash
python training/step_4.py
```
5. Пропиши в `.env`: `WAKE_WORD_COSMO=data/models/cosmo.onnx`.
### Сколько данных нужно
| Positive | Negative | Recall |
|---|---|---|
| 100200 | 200+ | 0.10.3 (плохо) |
| 300500 | 500+ | 0.40.6 (минимум) |
| 8001500 | 1000+ | 0.70.85 |
| 2000+ | 2000+ | 0.9+ |
Главное — **разнообразие**: разные дистанции до микрофона, интонации, время дня, фоны. Аугментация (`augmentation_rounds`) умножит твой датасет в N раз во время обучения.
Негативы должны включать **фонетически близкие** слова ("космос", "косо", "просто"), обычную речь, имена других ассистентов ("алиса", "сири"), бытовые звуки.
## Архитектурные решения
- **Одна сессия диалога на день** на агента (`Conversation` в `llm.py`). История хранится клиентом, отправляется целиком. Сброс — фразой "сбрось историю" или сменой даты.
- **Keep-alive HTTP** (`requests.Session`) — переиспользует TCP/TLS.
- **Streaming TTS** — ElevenLabs пайпится в `mpv` через stdin, играет пока генерируется.
- **STT без диска** — PCM → WAV в `BytesIO` → Groq.
- **Barge-in** — `stop_speaking()` убивает mpv при новой активации.
- **Ошибки не роняют сервис** — каждый слой ловит `Exception`, пишет в `errors.log`.
## .env (ключевые переменные)
| Переменная | Что |
|---|---|
| `GATEWAY_URL`, `LUSYA_GATEWAY_URL` | OpenClaw gateways |
| `GATEWAY_TOKEN`, `LUSYA_GATEWAY_TOKEN` | Авторизация |
| `AGENT`, `LUSYA_AGENT` | `openclaw/main`, `openclaw/wife` |
| `VOICE_MODEL` | LLM для голоса (передаётся в `x-openclaw-model`) |
| `GROQ_API_KEY` | STT |
| `ELEVENLABS_API_KEY`, `COSMO_TTS_VOICE`, `LUSYA_TTS_VOICE` | TTS |
| `WAKE_WORD_COSMO`, `WAKE_WORD_LUSYA` | Пути к `.onnx` моделям |
| `SILENCE_THRESHOLD`, `SILENCE_DURATION` | VAD |
| `MAX_HISTORY` | Лимит сообщений в сессии |
| `AUDIO_SINK` | На Pi: `bluez_sink.XX_XX_XX.a2dp_sink` |
## Деплой на Raspberry Pi
```bash
sudo bash deploy/setup.sh
sudo systemctl start cosmo-satellite
sudo journalctl -u cosmo-satellite -f
```
## Roadmap
- [x] Модулизация satellite
- [x] ElevenLabs streaming + barge-in
- [x] Сессии диалога с автосбросом
- [x] Пайплайн тренировки wake word на своих записях
- [ ] Обучить рабочую модель cosmo (нужно ~500+ позитивов)
- [ ] Подключить Люсю в `run_with_porcupine` (сейчас грузится только cosmo)
- [ ] Проверить systemd autostart на Pi в проде
- [ ] Home Assistant tool в OpenClaw
- [ ] Real-time barge-in (прерывание голосом во время TTS)
- [ ] Контекст окружения в system prompt
- [ ] Speaker identification
- [ ] Проактивные уведомления через WebSocket