# RUNBOOK — вывод голосового помощника в продакшен Пошаговая инструкция, чтобы всё завелось на мини-ПК (где развёрнут `https://tablet.digital-home.site`). Рассчитана на то, что её выполняет Claude-сессия **на мини-ПК** (`cosmo@192.168.31.60`), с пометками, что делается на рабочей машине (Windows), а что — на мини-ПК. ## TL;DR — что это добавляет 1. **Локальный агент (intent+slots)** — sidecar-служба, распознаёт команды офлайн за ~2 мс и исполняет действия (таймеры, свет, музыка, очиститель), минуя облачный LLM. Инфо-запросы (погода/календарь) по-прежнему у LLM. Модель обучена: **интент 40/40, слоты 40/40**. 2. **Голосовое управление светом** — новый tool `control_light` (2 лампы). 3. **Смелый визуал** — обновлённая тема (космический индиго/aurora), деплоится с обычным билдом. 4. **Wake «Эй, космо»** — пайплайн переобучения готов и проверен end-to-end; боевая модель требует досбора данных (см. раздел D). Всё под фиче-флагами: без `AGENT_URL` планшет работает ровно как раньше → безопасно для kiosk. --- ## Изменения в репозитории (уже в коде) | Файл | Что | |---|---| | `lib/tools/lights.ts` | новый tool `control_light` (свет через HA) | | `lib/tools/_registry.ts` | регистрация `control_light` | | `lib/agent-local.ts` | быстрый путь: вызов агент-службы + исполнение действий | | `app/api/voice/chat/route.ts` | вставка fast-path (под `AGENT_URL`, с fallback) | | `app/globals.css`, `app/page.tsx` | смелый визуал (токены/aurora) | | `agent/` | sidecar агента: `Dockerfile`, `predict.py`, `joint_model_def.py`, `tablet_map.py`, `requirements.txt` | ⚠️ **Эти изменения надо закоммитить и запушить** (с рабочей машины), чтобы мини-ПК их подтянул `git pull`. Веса моделей в git НЕ идут (переносятся отдельно, ниже). --- ## Архитектура продакшена ``` Планшет (Android, kiosk-браузер) ├─ wake «Эй, космо» (public/wake/cosmo.onnx, WASM) ─┐ └─ UI (Next.js) │ ▼ tablet-контейнер (мини-ПК, network coolify) ── /api/voice/chat │ fast-path: AGENT_URL задан? │ ├─ да → POST http://tablet-agent:8091/predict │ │ интент-действие + score≥0.7 → executeTool → короткий ответ │ └─ нет/инфо/none → облачный LLM (Anthropic/Groq) как раньше ▼ tablet-agent-контейнер (sidecar, network coolify) └─ predict.py --serve (joint_model смонтирован томом) ``` --- ## A. Агент (sidecar) — главный шаг ### A1. Перенести модель на мини-ПК (с рабочей машины) Веса `joint_model/` (~115 МБ) в git нет. Скопировать на мини-ПК: ```bash # на рабочей машине (Windows, Git Bash): scp -i ~/.ssh/id_ed25519 -r "/d/Digital home/tablet-agent/joint_model" \ cosmo@192.168.31.60:/opt/digital-home/agent-model ``` Должно получиться `/opt/digital-home/agent-model/joint.pt` (+ tokenizer, config). > Переобучить модель заново (если правил `tablet-agent/templates.txt`): > `cd tablet-agent && python train_joint.py && python test_joint.py`, затем scp. ### A2. Собрать и запустить sidecar (на мини-ПК) ```bash cd /opt/digital-home/smart-home-tablet git pull origin main # подтянуть код (после пуша с рабочей машины) docker build -t tablet-agent:latest ./agent docker rm -f tablet-agent || true docker run -d --name tablet-agent \ --network coolify \ --restart unless-stopped \ -v /opt/digital-home/agent-model:/app/joint_model \ tablet-agent:latest # проверка службы: docker exec tablet-agent python -c "import urllib.request,json; \ r=urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:8091/predict', \ data=json.dumps({'text':'поставь таймер на 5 минут'}).encode(), \ headers={'Content-Type':'application/json'})); print(r.read().decode())" # ждём: {"intent":"timer_set","slots":{"duration":"5 минут"},"score":...,"tool":{...}} ``` ### A3. Включить fast-path в планшете Добавить в `/opt/digital-home/tablet.env`: ``` AGENT_URL=http://tablet-agent:8091 AGENT_THRESHOLD=0.7 ``` Пере-задеплоить планшет (push любой коммит ИЛИ вручную пере-`docker run` по `deploy.yml`). После рестарта `route.ts` увидит `AGENT_URL` и начнёт использовать агента. > Откат агента: убрать `AGENT_URL` из `tablet.env`, пере-деплой. Мгновенно вернёт > облачный LLM на все команды. Контейнер `tablet-agent` можно оставить. --- ## B. Свет — реальные entity Home Assistant `control_light` по умолчанию шлёт на `light.bedside` / `light.reading` (заглушки). Указать реальные entity_id твоих ламп в `/opt/digital-home/tablet.env`: ``` LIGHT_ENTITIES_JSON={"bedside":"light.ВАШ_СВЕТ_У_КРОВАТИ","reading":"light.ВАША_ЛАМПА_ДЛЯ_ЧТЕНИЯ","default":"light.ВАШ_СВЕТ_У_КРОВАТИ"} ``` Найти id ламп: HA → Developer Tools → States, отфильтровать `light.`. Ключи `bedside`/`reading` соответствуют фразам «свет у кровати» / «лампа для чтения» (маппинг в `lib/tools/lights.ts`, `DEVICE_ALIASES`). Пере-деплой планшета. Без этой переменной команды света пройдут как mock (HA вернёт success, лампа не переключится) — то есть безопасно, но не управляет реальным светом. --- ## C. Дизайн (смелый визуал) Ничего дополнительно — тема живёт в `app/globals.css` (токены) и деплоится обычным билдом. Раскладка/логика не менялись, kiosk не затронут. Если визуал не зайдёт — откат = `git revert` коммита с globals.css. --- ## D. Wake «Эй, космо» — статус и деплой Проект `tablet-wake` (на рабочей машине). Пайплайн переобучения **готов и проверен end-to-end**: smoke-модель разделяет фразу (pos 0.96 / neg 0.01) через тот же WASM-рантайм, что у планшета. Но это **не боевая модель** — нужен досбор данных: 1. Реальная речь в негативах (корпус Golos) — главный убийца ложняков. 2. Фон Android-мика (запись, где планшет стоит). 3. Held-out тест своим голосом + подбор порога. Полный прогон (на рабочей машине) — см. `tablet-wake/README.md`. Итог — `tablet-wake/out/cosmo.onnx`. **Деплой wake-модели** (когда готова): заменить файл в репозитории и запушить — инференс планшета НЕ меняется (окно 25 сохранено), только веса: ```bash # на рабочей машине: cp "/d/Digital home/tablet-wake/out/cosmo.onnx" \ "/d/Digital home/smart-home-tablet/public/wake/cosmo.onnx" # затем commit + push → Gitea пересоберёт планшет ``` Порог срабатывания подстраивается в клиенте (`WAKE_THRESHOLD` в `components/VoiceController.tsx`). Также обнови фразу-подпись в UI: сейчас «Космо» → «Эй, космо». --- ## Сводка новых переменных `tablet.env` ``` AGENT_URL=http://tablet-agent:8091 # включает локального агента AGENT_THRESHOLD=0.7 # порог уверенности (опц.) LIGHT_ENTITIES_JSON={"bedside":"...","reading":"...","default":"..."} CLIMATE_ENTITY=climate.thermostat # кондиционер/термостат (control_climate) TV_ENTITY=media_player.tv # телевизор (control_tv) ``` ## Расширенный умный дом (облачный LLM) Промпт теперь перечисляет ВСЕ tools и разрешает уверенно управлять домом; в системный промпт каждый LLM-запрос инжектится живой снимок (время + состояния устройств из `/api/ha`), поэтому модель понимает, что включено и что есть в доме. Новые tools: `control_climate` (климат), `control_tv` (телевизор), `set_scene` (сцены night/movie/morning/away), `all_off` (выключить всё), `ha_service` (универсальный вызов сервиса HA по entity_id из снимка; домены light/switch/fan/ climate/media_player/cover/scene/humidifier/vacuum). Реальные entity_id ламп/ТВ/ климата задаются переменными выше; без них команды проходят как mock (безопасно). > Локальный агент (fast-path) новые интенты climate/tv/scene/all_off пока не > знает — эти команды обрабатывает облачный LLM. Чтобы они шли и локально, > нужно дообучить `tablet-agent` (добавить интенты) и передеплоить модель. --- ## Проверка (после деплоя) ```bash # планшет жив: curl -sk -o /dev/null -w "%{http_code}\n" https://tablet.digital-home.site/ # агент отвечает изнутри сети: docker exec tablet-yfh53kixpwkjlo4zibglx4n2 \ wget -qO- --post-data='{"text":"выключи свет у кровати"}' \ --header='Content-Type: application/json' http://tablet-agent:8091/predict # логи планшета: fast-path пишет "[voice/chat] local-agent intent=..." docker logs --tail 50 tablet-yfh53kixpwkjlo4zibglx4n2 | grep local-agent ``` Живой тест голосом: сказать «поставь таймер на 5 минут» (действие → агент, мгновенно), затем «какая погода» (инфо → LLM). В логах у первой команды — `local-agent`, у второй — обычный LLM-раунд. --- ## Откат всего | Что | Откат | |---|---| | Агент | убрать `AGENT_URL` из `tablet.env`, пере-деплой | | Свет | убрать `control_light` из `_registry.ts` ИЛИ просто не задавать `LIGHT_ENTITIES_JSON` | | Дизайн | `git revert` коммита globals.css | | Wake | вернуть прежний `public/wake/cosmo.onnx` из git-истории | Каждая часть независима — можно катить и откатывать по отдельности.