- 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
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-modelheader (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
Быстрый старт
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, 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 |
Запись датасета
# по одной записи (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.
Чистка
python remove_silent.py
Удаляет файлы с RMS ниже порога и переименовывает оставшиеся в 001.wav … NNN.wav.
Тренировка
# в 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
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 позитивов с разнообразием.