Files
home-voice-assistant/CLAUDE.md
Daniil Klimov 780f6f0084 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
2026-04-13 15:40:44 +03:00

12 KiB
Raw Blame History

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-model header)
  • 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 — лимит сообщений, чтобы не раздувать контекст.

Оптимизации скорости (все уже внедрены)

  1. Keep-alive HTTP сессии (requests.Session()) — в config.py._make_session(), переиспользуется TCP/TLS.
  2. Streaming TTS — ElevenLabs аудио пайпится в mpv через stdin, играет пока генерируется.
  3. STT без диска — PCM → WAV в BytesIO → Groq, без temp файлов.
  4. Barge-instop_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.pystep_5.py — установка зависимостей, конвертация датасетов, генерация конфига, обучение, экспорт в data/models/<name>.onnx
  • training/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 сам начинает говорить)