- 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
12 KiB
Cosmo Voice Satellite
Голосовой ассистент дома — аналог Алисы через OpenClaw. Два агента: Cosmo (владельца) и Люся (жены). Каждый активируется своим wake word и идёт на свой OpenClaw gateway.
Архитектура
┌─────────────┐ wake word ┌──────────────┐ STT (Groq)
│ Microphone │ ───────────► │ Satellite │ ──────────────►
└─────────────┘ └──────────────┘ │
│ ▼
│ ┌──────────────┐
│ │ OpenClaw │
│ │ Gateway │
│ │ (N100 PC) │
│ stream response └──────────────┘
▼ │
┌──────────────┐ │
│ ElevenLabs │ ◄─────────────────┘
│ TTS │
└──────────────┘
│
▼ mp3 stream
┌──────────────┐
│ mpv │ → speakers (BT)
└──────────────┘
Инфраструктура
- Сервер: N100 Mini-PC,
192.168.31.103, Proxmox - Cosmo Gateway: порт
18789, агентopenclaw/main - Люся Gateway: порт
18790, агентopenclaw/wife - Модель:
openai/gpt-5.4-mini(черезx-openclaw-modelheader) - STT: Groq API,
whisper-large-v3-turbo, язык ru - TTS: ElevenLabs,
eleven_flash_v2_5(~75ms латентность) - Wake word: Porcupine (на Pi), Enter (при разработке)
Структура проекта
home-voice-assistant/
├── .env # секреты (не в git)
├── .env.example # шаблон
├── requirements.txt
├── satellite.py # обёртка для запуска
├── satellite/
│ ├── __init__.py
│ ├── __main__.py # entry: python -m satellite [--wake]
│ ├── config.py # env, AGENTS dict, keep-alive sessions
│ ├── text.py # clean_for_speech, find_sentence_end
│ ├── stt.py # transcribe (Groq, BytesIO, без temp файла)
│ ├── audio.py # record, record_with_timeout (VAD)
│ ├── tts.py # ElevenLabs streaming через mpv, barge-in
│ ├── llm.py # ask_agent_stream, Conversation (history)
│ └── modes.py # run_with_enter, run_with_porcupine
└── deploy/
├── setup.sh # установка на Raspberry Pi
└── cosmo-satellite.service # systemd unit
Что важно знать
Сессии диалога
- Одна сессия на день для каждого агента. Это осознанное решение: каждая новая сессия в OpenClaw тяжёлая (чтение памяти, большой контекст).
- История хранится в
Conversation.messages[]на клиенте и отправляется целиком с каждым запросом (stateless к серверу). - Сброс сессии: фраза "начни новую сессию" / "сбрось историю" / "очисти контекст" — паттерны в
RESET_PATTERNSвllm.py. - Автосброс при смене даты (
Conversation.is_expired()). MAX_HISTORY=20— лимит сообщений, чтобы не раздувать контекст.
Оптимизации скорости (все уже внедрены)
- Keep-alive HTTP сессии (
requests.Session()) — вconfig.py._make_session(), переиспользуется TCP/TLS. - Streaming TTS — ElevenLabs аудио пайпится в
mpvчерез stdin, играет пока генерируется. - STT без диска — PCM → WAV в
BytesIO→ Groq, без temp файлов. - Barge-in —
stop_speaking()вызывается при каждой активации, убивает текущий mpv процесс.
Роутинг по wake word
В modes.py::run_with_porcupine Porcupine грузит оба wake word:
- index 0 = Cosmo →
AGENTS["cosmo"](:18789) - index 1 = Люся →
AGENTS["lusya"](:18790)
Каждый агент имеет свой tts_voice в ElevenLabs.
Ошибки не должны ронять сервис
Каждый слой (stt, tts, llm, audio, modes) ловит Exception и пишет в errors.log через config.log. Верхний уровень в modes.py ловит всё непредвиденное и продолжает цикл.
Запуск
macOS / Windows (разработка)
python -m venv .venv
# macOS/Linux: source .venv/bin/activate
# Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # заполнить ключи
python satellite.py # режим Enter (без wake word)
python satellite.py --wake # режим Porcupine (нужны .ppn + PORCUPINE_KEY)
Raspberry Pi (продакшн)
sudo bash deploy/setup.sh
# далее:
sudo systemctl start cosmo-satellite
sudo journalctl -u cosmo-satellite -f
Зависимости системы
- Python 3.12+
- portaudio — для
pyaudio(brew install portaudio/apt install portaudio19-dev) - mpv — для воспроизведения TTS (
brew install mpv/apt install mpv) - ffmpeg — опционально, для совместимости форматов
Windows
- Python 3.12+ с pip
pip install pyaudio— обычно работает через колеса pipwin или pre-built wheels. Если нет —pip install pipwin && pipwin install pyaudio- mpv: скачать с mpv.io, положить
mpv.exeв PATH - Porcupine работает и на Windows — wake word модель нужна под платформу windows (качать отдельную
.ppn)
Переменные окружения
Все в .env. Ключевые:
| Переменная | Что |
|---|---|
GATEWAY_URL, LUSYA_GATEWAY_URL |
URL OpenClaw gateway на N100 |
GATEWAY_TOKEN, LUSYA_GATEWAY_TOKEN |
Токены авторизации |
AGENT, LUSYA_AGENT |
Имя агента в OpenClaw (openclaw/main, openclaw/wife) |
VOICE_MODEL, LUSYA_VOICE_MODEL |
Модель LLM для голоса |
GROQ_API_KEY |
Groq для STT |
ELEVENLABS_API_KEY |
ElevenLabs TTS |
COSMO_TTS_VOICE, LUSYA_TTS_VOICE |
Voice ID в ElevenLabs |
ELEVENLABS_MODEL |
eleven_flash_v2_5 (быстрый) |
AUDIO_SINK |
На Pi: bluez_sink.XX_XX_XX.a2dp_sink. На Mac/Win: пусто. |
PORCUPINE_KEY, WAKE_WORD_COSMO, WAKE_WORD_LUSYA |
Только для --wake режима |
SILENCE_THRESHOLD=500 |
VAD: чувствительность (ниже = ловит тихую речь) |
SILENCE_DURATION=1.5 |
Сек тишины = конец фразы |
FOLLOWUP_TIMEOUT=8 |
Сек ожидания продолжения диалога |
MAX_HISTORY=20 |
Макс. сообщений в сессии |
Частые задачи
Сменить голос у агента: меняй COSMO_TTS_VOICE / LUSYA_TTS_VOICE в .env. Voice ID берётся на elevenlabs.io/app/voice-library.
Отладить VAD (ассистент не слышит / слушает слишком долго): SILENCE_THRESHOLD (громкость) и SILENCE_DURATION (сек).
Добавить третьего агента: в config.py::AGENTS новый ключ, в modes.py::run_with_porcupine добавить WAKE_WORD_* и wake_word_map.append(...).
Сменить модель LLM: VOICE_MODEL в .env — передаётся в header x-openclaw-model. Модель openclaw/main остаётся как agent (это маршрут в OpenClaw).
Что НЕ делать
- Не комитить
.env(есть в.gitignore) - Не возвращать fallback на macOS
say— проект специально унифицирован на ElevenLabs + mpv - Не создавать новую сессию Conversation на каждую активацию — это было в старой версии, сейчас одна сессия на день
- Не добавлять temp файлы для WAV/mp3 — всё идёт через
BytesIO/ stdin pipe
Тренировка своего wake word
Пайплайн в training/:
record_wav.py <model> <positive|negative>— запись 16kHz mono PCM 16-bit вtraining/own_samples/<model>/training/step_1.py…step_5.py— установка зависимостей, конвертация датасетов, генерация конфига, обучение, экспорт вdata/models/<name>.onnxtraining/training_config.json— параметры (wake_word_list,use_own_samples, штрафы, шаги)training/openwakeword/— форк openwakeword,examples/custom_model.yml— базовый шаблон конфига- Под капотом: openwakeword (НЕ Porcupine, несмотря на легаси-имена в коде). Wake word работает через DNN-модель .onnx.
Реалистичные цифры для своего голоса: 500+ positive и 1000+ negative wav-файлов, иначе recall/FP/hour не сходятся. Negative должны включать фонетически близкие слова.
Roadmap
Done
- Модулизация satellite.py (audio/stt/llm/tts/modes/config)
- ElevenLabs streaming TTS + mpv pipe
- Keep-alive HTTP сессии, STT через BytesIO, barge-in
- Сессии диалога (одна на день, MAX_HISTORY, паттерны сброса)
- Пайплайн тренировки своего wake word на собственных записях
In progress
- Дообучение модели cosmo (на текущем датасете 300 pos / 117 neg метрики плохие — recall 25%, FP/hr 32). Нужно дозаписать данные.
- Подключить Люсю в
run_with_wakeword(сейчас грузится только модель cosmo, lusya wake word не работает)
Planned
- systemd autostart на Raspberry Pi (
deploy/cosmo-satellite.serviceесть, но не проверен в проде) - Home Assistant tool в OpenClaw воркспейсе (управление светом/температурой через голос)
- Real-time barge-in (прерывание по голосу во время озвучки, не только по новой активации)
- Контекст окружения в system prompt (время, погода, состояние устройств)
- Speaker identification (определять кто говорит без разных wake words)
- Проактивные уведомления (WebSocket от сервера → satellite сам начинает говорить)