Files
home-voice-assistant/README.md
Daniil Klimov 780f6f0084 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
2026-04-13 15:40:44 +03:00

174 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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