Files
home-voice-assistant/README.md
Daniil Klimov a9001aef92 refactor: VAD upgrade, retry, dead code cleanup, AGENT removal
- audio: switch VAD to webrtcvad with RMS gate + fallback to RMS
- audio: honor FOLLOWUP_TIMEOUT — short silence wait after bot response
- llm: retry with exponential backoff on network errors and 5xx
- llm: VOICE_MAX_TOKENS env (default 300) instead of hardcoded 150
- tts: optional VAD-based barge-in (BARGE_IN_ENABLED, off by default)
- tts: remove dead start_barge_in_listener / was_barge_in helpers
- config: drop AGENT/LUSYA_AGENT — routing happens via session_key
- modes: remove unused imports, pass FOLLOWUP_TIMEOUT to follow-up record()
- docs: full rewrite of README and CLAUDE.md to match current architecture
2026-04-16 17:10:59 +03:00

228 lines
12 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
Домашний голосовой ассистент поверх LLM — аналог Алисы/Siri, но умнее, потому что за ним стоит полноценный агент OpenClaw с памятью, tools и инструментами.
Два агента — **Cosmo** (владельца) и **Люся** (жены). Каждый со своим wake word, своим голосом ElevenLabs и своим OpenClaw gateway.
## Архитектура
```
mic ─► wake word (openwakeword)
└► STT (Groq whisper) ─► OpenClaw Gateway (session_key, N100) ─► LLM
▼ streamed text
ElevenLabs TTS ─► mpv stdin ─► speakers
```
- **Wake word**: openwakeword (`.onnx`, обученная на своих записях)
- **STT**: Groq API, `whisper-large-v3-turbo`, ru
- **Агент**: OpenClaw на N100, модели через `x-ocplatform-model` header (`openai/gpt-5.4-mini` и т.п.)
- **История диалога**: на сервере OpenClaw (per `session_key`), клиент stateless
- **TTS**: ElevenLabs streaming через mpv
## Структура
```
home-voice-assistant/
├── satellite.py # entry-обёртка
├── satellite/ # рантайм
│ ├── __main__.py # python -m satellite [--wake]
│ ├── config.py # AGENTS, keep-alive sessions
│ ├── audio.py # запись + RMS VAD
│ ├── stt.py # Groq whisper
│ ├── llm.py # ask_agent_stream, strip_fillers, RESET_PATTERNS
│ ├── tts.py # ElevenLabs → mpv stdin
│ ├── text.py # clean_for_speech (+ pymorphy3 для времени)
│ └── modes.py # run_with_enter / run_with_porcupine
├── record_wav.py # запись датасета wake word
├── remove_silent.py # чистка тихих + перенумерация
├── training/ # openwakeword пайплайн (в .gitignore)
├── data/models/ # готовые .onnx wake word моделей
└── deploy/ # setup.sh + systemd unit для Pi 5
```
## Быстрый старт
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # заполнить ключи
python satellite.py # режим Enter (отладка)
python satellite.py --wake # режим wake word (нужна модель в data/models/)
```
### Системные зависимости
macOS: `brew install portaudio mpv`
Linux: `apt install portaudio19-dev mpv`
Windows: мpv с [mpv.io](https://mpv.io), `pip install pipwin && pipwin install pyaudio`
## Сессия диалога
История и память живут **на стороне OpenClaw**. Satellite отправляет только текущее сообщение + `x-openclaw-session-key`. Сервер сам подклеивает контекст.
Сброс сессии:
- Голосом: «начни новую сессию» / «сбрось историю» / «очисти контекст» → satellite шлёт slash-команду `/new` в OpenClaw
- Программно: меняй `COSMO_SESSION_KEY` в `.env`
## Обучение своего 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`.
### Тренировка
```bash
# в training/training_config.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
# }
rm -rf training/my_custom_model/cosmo data/models/cosmo.onnx
python training/step_4.py
# → .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+ |
Главное — **разнообразие**: разные дистанции, интонации, время дня, фоны. Негативы должны включать фонетически близкие слова («космос», «косо», «просто»), обычную речь, имена других ассистентов («Алиса», «Сири»).
## .env (ключевые переменные)
| Переменная | Что |
|---|---|
| `GATEWAY_URL`, `LUSYA_GATEWAY_URL` | URL OpenClaw gateway |
| `GATEWAY_TOKEN`, `LUSYA_GATEWAY_TOKEN` | Bearer токены |
| `AGENT`, `LUSYA_AGENT` | Имя агента в OpenClaw |
| `VOICE_MODEL` | LLM (передаётся в `x-ocplatform-model`) |
| `COSMO_SESSION_KEY`, `LUSYA_SESSION_KEY` | Идентификатор серверной сессии |
| `GROQ_API_KEY` | STT |
| `ELEVENLABS_API_KEY`, `ELEVENLABS_MODEL` | TTS |
| `COSMO_TTS_VOICE`, `LUSYA_TTS_VOICE` | Voice ID |
| `WAKE_WORD_COSMO`, `WAKE_WORD_LUSYA` | Пути к `.onnx` |
| `WAKE_THRESHOLD` | Порог активации (дефолт 0.5) |
| `TTS_MODE` | `full` (цельная интонация) / `stream` (быстрый старт) |
| `AUDIO_SINK` | На Pi: BT sink. На Mac/Win: пусто |
| `SILENCE_THRESHOLD`, `SILENCE_DURATION`, `MAX_DURATION`, `FOLLOWUP_TIMEOUT` | VAD |
## Деплой на Raspberry Pi 5
```bash
sudo bash deploy/setup.sh
# подключи BT колонку, пропиши AUDIO_SINK в .env
# положи обученную .onnx в data/models/cosmo.onnx
sudo systemctl start cosmo-satellite
sudo journalctl -u cosmo-satellite -f
```
## Roadmap
### ✅ Сделано
- Модульная структура satellite
- ElevenLabs streaming TTS через mpv pipe
- Keep-alive HTTP + STT без диска
- Серверные сессии OpenClaw (`x-openclaw-session-key`)
- Slash-команда `/new` для сброса голосом
- Нормализация речи (числа, время, единицы) через pymorphy3 + num2words
- Пайплайн тренировки wake word на своих записях
- systemd unit для Pi
### 🚧 В работе
- Дообучить wake-модель до recall ≥ 0.7 (нужно 500+ позитивов + разнообразие)
- Подключить Люсю в `run_with_porcupine` (код готов, закомментирован)
- Чистка мёртвого кода (`start_barge_in_listener`, `conv` и т.п.)
- Починить `FOLLOWUP_TIMEOUT` (сейчас после ответа ассистент ждёт полный 15 с)
### 📋 Этап 2 — качество и надёжность
- Автосброс OpenClaw сессии по таймауту (>1 ч → `/new`)
- Retry с backoff для gateway
- TTS-cache дежурных реплик
- Persistent PyAudio stream (быстрее запись на Pi)
- Заменить RMS-VAD на `webrtcvad` / `silero-vad` — RMS ломается с фоновой музыкой
- Whisper `prompt` с именами собственными
- Size-cap / logrotate для `errors.log`
### 📋 Этап 3 — «умнее Алисы»
- **Home Assistant tool** в OpenClaw: свет/климат/медиа голосом
- **Контекст окружения** в каждом запросе: время, комната, погода, кто говорит
- **Proactive notifications**: OpenClaw → WebSocket/SSE → satellite сам начинает говорить (таймеры, напоминания, входящие)
- **Realtime barge-in голосом** — прерывать TTS, когда пользователь начал говорить (требует echo cancellation)
- **No-wake mode** в доверенной комнате — VAD + STT + intent filter без обязательного wake word
- **Streaming TTS пер-токен** — выдавать в речь куски раньше полного предложения
### 📋 Этап 4 — амбициозное
- **Speaker identification** (`pyannote.audio` / `resemblyzer`) — разные персонализации по голосу
- **Multi-room координация** — MQTT между сателлитами, отвечает тот, кто слышит громче
- **Локальный fallback LLM** на Pi когда gateway оффлайн (phi/llama для простых команд)
- **Камера + vision** — агент видит кто в комнате, что происходит
- **Voice-memory hooks UX** — голосовое «запомни/забудь»
## Известные ограничения
- Нет echo cancellation — если колонки близко к мику, TTS может триггерить wake-модель (поднимай `WAKE_THRESHOLD`).
- RMS-VAD не отличает голос от музыки/ТВ — ассистент может «залипнуть» в шумной среде.
- OpenClaw-сессия живёт вечно, пока не сказать «сбрось» — контекст раздувается.
- При потере связи с gateway — ассистент молчит до явной ошибки.
## Troubleshooting
**Ассистент «иностранец», монотонно читает** → смени `ELEVENLABS_MODEL` на `eleven_multilingual_v2`, возьми native-русский голос из Voice Library (не default English-speakers), в `tts.py` поставь `style=0.0, speed=1.0` (style/speed ломают просодию).
**Числа читаются цифрами вместо слов** → проверь `text.py::clean_for_speech` применяется. Для времени нужны `num2words` и `pymorphy3` в requirements.
**Wake срабатывает сам по себе, когда TTS говорит**`WAKE_THRESHOLD=0.7`, разнеси колонку и мик. В будущем — AEC.
**Ассистент не реагирует на тихий голос** → понижай `SILENCE_THRESHOLD` (дефолт 500).
**`pyaudio` не ставится на Windows** → `pip install pipwin && pipwin install pyaudio`, или бери pre-built wheel.
**Wake модель плохо работает** → дело в данных. Смотри таблицу выше — нужно минимум 500 позитивов с разнообразием.