Files
home-voice-assistant/CLAUDE.md
Daniil Klimov 7ca8268b78 Initial commit: Cosmo Voice Satellite
Two-agent voice assistant (Cosmo + Люся) via OpenClaw Gateway.
Streaming STT (Groq) + LLM + TTS (ElevenLabs) pipeline with
keep-alive sessions, barge-in, and daily conversation sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:34:08 +03:00

166 lines
9.9 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
Голосовой ассистент дома — аналог Алисы через 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-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 (разработка)
```bash
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 (продакшн)
```bash
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](https://mpv.io/installation/), положить `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](https://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
## Roadmap
- [ ] Speaker identification (определять кто говорит без разных wake words)
- [ ] Проактивные уведомления (WebSocket от сервера → satellite сам начинает говорить)
- [ ] Контекст окружения в system prompt (время, погода, состояние устройств)
- [ ] Real-time barge-in (прерывание по голосу во время озвучки, не только по новой активации)