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:
173
README.md
Normal file
173
README.md
Normal 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 |
|
||||
|---|---|---|
|
||||
| 100–200 | 200+ | 0.1–0.3 (плохо) |
|
||||
| 300–500 | 500+ | 0.4–0.6 (минимум) |
|
||||
| 800–1500 | 1000+ | 0.7–0.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
|
||||
Reference in New Issue
Block a user