# 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 | |---|---|---| | 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+ | Главное — **разнообразие**: разные дистанции, интонации, время дня, фоны. Негативы должны включать фонетически близкие слова («космос», «косо», «просто»), обычную речь, имена других ассистентов («Алиса», «Сири»). ## .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 позитивов с разнообразием.