# Домен 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 |