Files
shturman/docs/domains/d-assistant.md
T
kk0t9 77d9a5a0ee docs(domain H): v2 — медиа/аудио + арбитр ducking, после adversarial-ревью (20 находок) + кросс-док
Новый домен H (медиа/аудио). Ядро — сквозной арбитр аудио (политика
фокуса/ducking поверх PipeWire, role-based), плюс медиаплеер (локальное/BT
A2DP/AVRCP/радио/стриминг). Многоагентный adversarial-ревью: 24 находки,
20 подтверждено (default-refute), все применены.

Ключевое из ревью:
- symphonia НЕ декодирует Opus → отдельный audiopus (libopus, BSD).
- AEC: module-echo-cancel (≠ filter-chain) в audio-plane, не в ассистенте — резолв D §147.
- media.role (арбитраж) vs media.category (Playback/Capture) разведены.
- Кнопки громкости мультируля — uinput→Wayland-input (C/K), не интенты ассистента.
- Crash-safe по двум осям (source: жизнь ноды + проактивный cork по NameOwnerChanged/watchdog; sink: пересчёт при возврате output).
- intra-role media-фокус (один media-продюсер), гистерезис фокуса (анти-pumping), duck = относит. аттенюатор.
- boot аудио-плоскости на Stage 1; отказ-пути плеера (битый файл/обрыв A2DP/ENOSPC/underrun); resume без авто-старта.

Кросс-док: D §147 AEC→; tech-stack (symphonia/audiopus/module-echo-cancel);
hardware §4 (amp-mute-GPIO + FM-тюнер + откр. вопросы); B §4 (amp-mute перед
cut); architecture §9 (аудио-арбитр → H); domains/README (строка H).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 18:08:36 +03:00

167 lines
14 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.
# Домен D — Голосовой ассистент
> Русскоязычный со-пилот: слышит речь, **видит данные машины (через E)** и объясняет
> их по-человечески. Интегрирован в UI лаконично (как Grok в Tesla). Вторая половина
> killer-фичи. First-party ап на SDK (Rust + ONNX Runtime).
Статус: **v2 (на ревью).** v2 — после ретро-ревью (9 находок).
Связано с: домены E (Vehicle-Data), **K (GPS/Location)**, **B (power-гейт wake-word)** · [ipc.md](../contracts/ipc.md) (`Assistant`) · [plugin-sdk.md](../contracts/plugin-sdk.md) (интенты) · [security-privacy.md](../contracts/security-privacy.md) · [tech-stack.md](../tech-stack.md) · [principles.md](../principles.md) (#3,#5,#6,#7,#8)
---
## 1. Назначение и границы
- **Что делает:** голосовой и текстовый диалог по-русски; объясняет состояние машины;
выполняет интенты (свои + плагинов).
- **Границы:** не трогает CAN напрямую (контекст берёт у E); **не даёт советов,
опасных для вождения** (констрейнт системного промпта); никаких safety-critical.
## 2. Пайплайн
```
Mic-массив → AEC → Wake-word → VAD → STT(RU) → Intent router
→ [локальные интенты] (громче/домой/… — без LLM, низкая латентность)
→ [LLM backend] (свободный диалог + объяснения + контекст машины)
→ TTS(RU) → Audio out
```
*(AEC = эхоподавление с loopback-референсом TTS/медиа-выхода из PipeWire — нужно для
wake-word во время воспроизведения и для barge-in.)*
Компоненты: **AEC** (loopback-ref выхода) · wake-word (**openWakeWord**, Apache-2.0, RU-фраза
«Штурман»; Porcupine проприетарный → #12, избегаем) · VAD (Silero) · STT (Vosk/Silero, **офлайн**) ·
intent router · LLM backend (pluggable) · TTS (Silero, офлайн).
**Инференс:** ONNX-модели (Silero VAD/STT/TTS) — через ONNX Runtime (`ort`); **Vosk — нативный
движок Kaldi** (крейт `vosk`). См. [tech-stack.md](../tech-stack.md).
## 3. Функции
| функция | MVP/later | зависит от | фаза |
|---------|-----------|------------|------|
| Wake-word «Штурман» | **MVP** | mic, tech-stack | v1 |
| VAD (Silero) | **MVP** | — | v1 |
| STT RU офлайн (Vosk/Silero) | **MVP** | — | v1 |
| TTS RU офлайн (Silero) | **MVP** | — | v1 |
| Push-to-talk + текстовый лог диалога | **MVP** | Shell | v1 |
| Intent router | **MVP** | — | v1 |
| Локальные интенты (громче/домой/…) | **MVP** | — | v1 |
| LLM online (GigaChat/YandexGPT) | **MVP** | Connectivity | v1 |
| Multi-turn контекст | **MVP** | — | v1 |
| **Vehicle-context injection** | **MVP (killer)** | E, data-model | v2 |
| Online degradation/timeout UX («думаю…»/«нет сети») | **MVP** | — | v1 |
| Driver-distraction (приоритет голосу) | **MVP** | Shell (owner); K (GPS) v1; E (OBD) v2 | v1/v2 |
| Provider switch + офлайн-фолбэк (llama.cpp) | later | — | v3 |
| Память о водителе (`.md`) | later | storage | v3 |
| Plugin-интенты (dispatch в IntentHandler) | later | plugin-sdk | v3 |
| Barge-in (прерывание TTS голосом) | later | **AEC**, PipeWire loopback | later |
## 4. Vehicle-context injection (killer-фича)
Ассистент получает в системный промпт **live-снимок** данных машины (из E) и
объясняет простым русским. Пример:
```
[SYSTEM] Ты — Штурман, голосовой со-пилот. Кратко, по-русски, для водителя в движении.
Не давай советов, опасных для вождения.
Данные машины (только валидные/свежие; недоступные опущены):
Скорость 64 км/ч · Обороты 2100 · Охлаждайка 92°C · Бортсеть 14.1В
Лампа Check: горит · Ошибки: P0420 (катализатор, банк 1 — низкая эффективность)
(расход: нет данных)
[Память о водителе]: …
[USER] Почему горит чек?
[ASSISTANT] Из-за P0420 — система считает, что катализатор работает неэффективно.
Часто это датчик кислорода или сам катализатор. Машина поедет, но на диагностику стоит заехать.
```
Снимок берётся из `VehicleData` (E); коды → стандартное описание из базы, человеческое объяснение — LLM.
**Контракт инъектора контекста:**
- Ассистент — first-party SDK-ап, **декларирует `vehicle_read:[…]`** (speed, rpm, coolant_temp,
module_voltage, fuel_level, **mil_on, dtc_count**) + `GetDtcs()`; доступ гейтится тем же
VehicleData-прокси, что и у плагинов (ipc §5) — first-party = авто-грант, не «свободный доступ».
- В промпт — **только сигналы `quality=valid`** (data-model §2/§3); `stale`/`unavailable`
исключаются или помечаются «нет данных» (LLM не утверждает устаревшее).
- Состав гейтится **состоянием машины** (`off`/`accessory`/`running`).
- **Деградация на простых авто (Lada):** богатые PID часто Unavailable → снимок схлопывается
до `mil_on` + DTC — это всегда-доступный MVP-базис.
## 5. LLM backend (pluggable, provider-agnostic)
- **Единый интерфейс бэкенда**, реализации сменные (принцип #8).
- **Online:** GigaChat (OAuth2), YandexGPT (IAM) — HTTP, RU/152-ФЗ.
- **Offline:** локальная квантованная модель через llama.cpp (фаза v3).
- **Авто-fallback:** ошибка/нет сети → офлайн; восстановилась — обратно.
- **Креды — свои у пользователя:** проект не шипит ключи; пользователь подключает
свой GigaChat/YandexGPT (токены в защищённом хранилище, refresh; облако — за его счёт).
- **До офлайн-фолбэка (v1 online-only):** сеть в движении нестабильна → мягкая деградация:
«думаю…», таймаут → «нет сети», без зависаний. Офлайн-фолбэк (v3) делает ассистента надёжным.
- **Принцип #3 (offline-first) в v1 — частично:** wake-word, STT/TTS и локальные интенты
(громче/домой) работают офлайн, ассистент не немеет без сети; офлайн-**диалог** (llama.cpp) — v3.
(Полное #3-соответствие — v3; см. principles #3.)
## 6. Интенты (routing)
- **Локальные латентно-критичные** (громче, домой, отмена) — без LLM, мгновенно.
- **Свободный диалог / объяснения** — LLM (с контекстом машины + памятью).
- **Plugin-интенты** — dispatch в `IntentHandler` плагина; коллизии разрешаются по
политике [plugin-sdk](../contracts/plugin-sdk.md) §5; LLM-роутер — арбитр по контексту.
## 7. Память о водителе (`.md`)
- Хранит факты о водителе/привычках/машине в **локальных `.md`** (приватное
хранилище `/data/apps/assistant/`, как контекст-память у Claude/Cursor).
- **Использование:** подмешивается в контекст (как и данные машины).
- **Приватность:** локально; **не уходит в облако без явного согласия** (security-privacy §7);
пользователь может **смотреть/править/чистить**.
- 🟡 *Осторожно:* что именно авто-запоминать — приватно-чувствительно; нужна
аккуратная политика (по умолчанию минимум, прозрачно).
## 8. Приватность
- Голос обрабатывается **локально** (STT) до отправки чего-либо; в облако — только
**текст запроса** при онлайн-LLM. Контекст машины уходит провайдеру только онлайн и по запросу (security-privacy §7).
- **Wake-word по состояниям питания (не безусловно always-on):** работает при заведённом /
ACC-on; в sleep/off — выключен (риск разряда АКБ на стоянке, принцип #5). Доступен только с
**Stage 2** (в boot-окне голоса нет). Гейтится сигналами Power (`AccChanged`/`Sleep`/`Wake`, ipc).
Включённый микрофон — **видимый индикатор**. Политика прослушки на ACC-off + бюджет разряда — ◻️ домен B/hardware.
## 9. Зависимости
- **Вниз/вбок:** E (контекст машины, `vehicle_read`+`GetDtcs` через прокси), **K** (GPS-скорость
для distraction v1), Connectivity (online LLM), **PipeWire** (`audio_in`/`audio_out` +
**loopback-tap выхода для AEC**), **Power/B** (гейт wake-word по ACC/sleep), Settings, Shell
(UI; политику distraction владеет Shell §7), security-privacy (mic-индикатор), tech-stack
(ONNX Runtime, `vosk`, llama.cpp), data-model (коды/сигналы/quality для контекста).
## 10. Открытые вопросы
- 🟡 **Память о водителе:** что авто-запоминать, схема, consent (приватность). → этот домен + security-privacy.
- 🟡 **Выбор офлайн-модели** (YandexGPT Lite / T-lite / Qwen, квантизация) — по перфу/RAM RK3588. → отложено (бенч на железе).
- ◻️ **Подход к матчингу интентов** (грамматика/ключевые слова для локальных vs LLM-роутер). → реализация.
- ◻️ **Бюджет латентности** пайплайна (wake→ответ) — цель и измерение. → реализация.
- ◻️ **STT:** Vosk (стриминг, легче) vs Silero (качество) — выбрать/поддержать оба. → реализация.
- ◻️ **Barge-in** (прерывание TTS) — later.
- ◻️ **Размер контекста:** машина + память + история → токены (особенно у офлайн-модели
с маленьким окном); нужна обрезка/суммаризация. → реализация.
-**AEC (эхоподавление):** подавление собственного TTS/медиа-выхода (loopback-ref из
PipeWire) — нужно для wake-word во время воспроизведения и barge-in. **Резолв:** узел audio-plane
на базе `module-echo-cancel` (WebRTC APM), референс = monitor выхода, **не внутри ассистента**
loopback-tap публикует Media (H §4); ассистент потребляет уже очищенную capture-ноду.
- ◻️ **Beamforming/денойз** (внешний шум салона): mic-массив; офлайн-STT фиксирован приватностью. → [hardware.md](../contracts/hardware.md).
- ◻️ **Plugin-интенты как function-calling** (tool-use LLM) vs фразы — зависит от
поддержки провайдером (у GigaChat/YandexGPT может быть ограничена). → реализация.
- ◻️ **Обучение wake-word «Штурман»** (openWakeWord) — сбор данных/тренировка. → реализация.
---
## Журнал решений (домен D)
| Решение | Выбор | Дата |
|---------|-------|------|
| Пайплайн | Mic→AEC→wake→VAD→STT→router→(локальные/LLM)→TTS; ONNX (Silero) + Vosk (Kaldi) | 2026-06-16 |
| LLM | provider-agnostic; online (v1) + офлайн-диалог (v3, llama.cpp); деградация UX в v1 | 2026-06-16 |
| Контекст-инъекция | только `quality=valid`, гейт по состоянию машины; `vehicle_read` через прокси | 2026-06-16 |
| Память о водителе | локальные `.md` в приватном хранилище; не в облако без согласия | 2026-06-16 |
| Локальные интенты | латентно-критичные — без LLM | 2026-06-16 |
| Wake-word | openWakeWord (Apache-2.0); Porcupine отвергнут (проприетарный, #12) | 2026-06-16 |
| Микрофон | wake-word по состояниям питания (не always-on), с Stage 2; AEC обязателен | 2026-06-16 |