voice: локальный агент (fast-path), tool control_light, смелая тема, RUNBOOK
All checks were successful
Deploy / deploy (push) Successful in 3m43s

- lib/agent-local.ts + route.ts: команды-действия исполняет локальная intent-служба
  (под AGENT_URL, с fallback на LLM); инфо-запросы остаются у LLM
- lib/tools/lights.ts: голосовое управление светом через HA (control_light)
- app/globals.css + page.tsx: космическая indigo/aurora тема (токены, kiosk-safe)
- agent/: sidecar-служба (Dockerfile, predict.py), веса монтируются томом
- RUNBOOK.md: вывод в продакшен на мини-ПК
This commit is contained in:
d.klimov
2026-07-22 21:48:24 +03:00
parent d30ed1bac1
commit e6dab09c38
13 changed files with 975 additions and 32 deletions

213
RUNBOOK.md Normal file
View File

@@ -0,0 +1,213 @@
# 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":"..."}
```
---
## Проверка (после деплоя)
```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-истории |
Каждая часть независима — можно катить и откатывать по отдельности.