- промпт: перечислены ВСЕ 15+ tools, разрешено уверенно управлять домом - route.ts: живой контекст (время + снимок устройств из HA) в системный промпт для LLM-пути (defensive, с таймаутом) - новые tools: control_climate (кондей/термостат), control_tv, set_scene (night/movie/morning/away), all_off, ha_service (универсальный HA с whitelist) - env: CLIMATE_ENTITY, TV_ENTITY (см. RUNBOOK)
12 KiB
RUNBOOK — вывод голосового помощника в продакшен
Пошаговая инструкция, чтобы всё завелось на мини-ПК (где развёрнут
https://tablet.digital-home.site). Рассчитана на то, что её выполняет
Claude-сессия на мини-ПК (cosmo@192.168.31.60), с пометками, что делается
на рабочей машине (Windows), а что — на мини-ПК.
TL;DR — что это добавляет
- Локальный агент (intent+slots) — sidecar-служба, распознаёт команды офлайн за ~2 мс и исполняет действия (таймеры, свет, музыка, очиститель), минуя облачный LLM. Инфо-запросы (погода/календарь) по-прежнему у LLM. Модель обучена: интент 40/40, слоты 40/40.
- Голосовое управление светом — новый tool
control_light(2 лампы). - Смелый визуал — обновлённая тема (космический индиго/aurora), деплоится с обычным билдом.
- 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 нет. Скопировать на мини-ПК:
# на рабочей машине (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 (на мини-ПК)
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-рантайм, что у планшета. Но это не боевая модель — нужен досбор
данных:
- Реальная речь в негативах (корпус Golos) — главный убийца ложняков.
- Фон Android-мика (запись, где планшет стоит).
- Held-out тест своим голосом + подбор порога.
Полный прогон (на рабочей машине) — см. tablet-wake/README.md. Итог —
tablet-wake/out/cosmo.onnx.
Деплой wake-модели (когда готова): заменить файл в репозитории и запушить — инференс планшета НЕ меняется (окно 25 сохранено), только веса:
# на рабочей машине:
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(добавить интенты) и передеплоить модель.
Проверка (после деплоя)
# планшет жив:
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-истории |
Каждая часть независима — можно катить и откатывать по отдельности.