Як я зробив офлайн голосовий асистент для розумного будинку на Raspberry Pi — і чому відмовився від хмари

Мене звати Іван, я solo-розробник і вже понад рік будую SelenaCore — open-source хаб для розумного будинку, який працює повністю офлайн, підтримує українську мову та не відправляє жодних даних у хмару. Ця стаття — для тих, хто хоче зрозуміти, як влаштований такий проєкт зсередини, які технічні рішення я приймав і з якими реальними проблемами зіткнувся.

Чому я взагалі за це взявся

Все почалося з простого питання: чому мій розумний будинок не розуміє мене українською?

Google Home і Amazon Alexa — хороші продукти, але вони вимагають постійного інтернету, відправляють голосові запити на сервери компанії і не дуже добре розуміють українську. Home Assistant вирішує частину проблем, але голосове управління там — окрема складна тема, і теж переважно через хмарні сервіси.

Я почав думати: а що якщо зробити все локально? STT на пристрої, LLM на пристрої, TTS на пристрої. Без хмари взагалі. Щоб навіть при відключенні інтернету все працювало.

Так з’явився SelenaCore.

Архітектура: як це влаштовано

Загальна схема

Система складається з кількох шарів:

  1. Офлайн голосовий pipeline — wake-word детекція → STT → обробка → відповідь через TTS
  2. Pluggable LLM layer — підтримує Ollama (локальні моделі) та хмарні провайдери як опцію
  3. Модульна система — 21 вбудований модуль для управління пристроями, таймерами, погодою тощо
  4. React SPA фронтенд — веб-інтерфейс для управління та моніторингу
  5. Docker-based деплоймент — все запускається в контейнерах

Апаратна база — Raspberry Pi 4/5 або NVIDIA Jetson Orin Nano. Вибір залежить від того, чи потрібен LLM inference на борту.

STT: чому Vosk, а не Whisper

Коли я вибирав між Vosk і Whisper для офлайн розпізнавання мовлення, виграв Vosk — і не через якість, а через швидкість і пам’ять.

Whisper дає кращу точність, особливо для нестандартних фраз. Але на Raspberry Pi 4 він занадто повільний для realtime-інтерфейсу. Vosk з моделлю vosk-model-uk дає прийнятну точність і відповідає за 300—500мс, що цілком комфортно для голосового асистента.

Є один важливий нюанс: STT недосконалий. Vosk може розпізнати «включи лампочку» як «включи лампу» або «ввімкни лапку». Це реальна проблема, яка вплинула на всю архітектуру Intent Recognition — але про це нижче.

TTS: Piper і локальні голоси

Для синтезу мови використовую Piper — швидкий офлайн TTS з непоганою якістю Ukrainian голосів. Він працює на CPU без GPU-прискорення і дає затримку ~150—300мс на Pi 4, що прийнятно.

Спочатку я планував просто передавати всі команди напряму в LLM і отримувати відповіді. Але виявилось, що це занадто дорого по латентності і токенах, коли кожен «увімкни світло» йде на LLM з повним контекстом.

Intent Recognition: від простого до three-tier pipeline

Це виявилось найскладнішою частиною проєкту.

Перша версія — regex-based матчинг. Просто: якщо фраза містить «увімкни» і «лампу» — це device.on. Спрацьовувало для простих випадків, але повністю ламалось при будь-яких варіаціях.

Друга версія — LLM для всього. Кожен запит йде в модель. Точність чудова, але затримка 2–4 секунди навіть з Ollama + llama3.1:8b на Pi. Для голосового асистента це критично.

Поточна версія — три рівні:

  1. Tier 0: Fuzzy phrase cache — база вже оброблених фраз з fuzzy matching. Якщо «включи лампочку у вітальні» вже зустрічалась і вирішена — відповідь миттєва.
  2. Tier 1: Embedding classifier — all-MiniLM-L6-v2 через ONNX (без Python ML залежностей). Класифікує intent по векторній схожості. Працює за ~200мс.
  3. Tier 2: LLM — тільки для складних або невизначених запитів, де embedding не впевнений.

Результат: 92.9% точності на тестовому корпусі з 70 українських голосових команд, середня затримка embedding-класифікації ~200мс.

Багатомовність: архітектурне рішення

Одна з ключових фічей — підтримка кількох мов. Але реалізована вона нестандартно.

Вся внутрішня обробка відбувається лише англійською. Переклад відбувається тільки на двох межах:

  • Vosk output (будь-яка мова → англійська) через Helsinki-NLP opus-mt-mul-en з CTranslate2 int8 квантизацією (~300MB)
  • Перед Piper TTS (англійська → мова користувача)

Чому так? Тому що LLM-промпти, intent-каталог, логіка — все це стабільно і передбачувано англійською. Додати нову мову = додати STT модель + TTS голос. Intent Recognition при цьому не змінюється.

На практиці: «увімкни лампу у вітальні» → переклад → «Turn on the lamp in the living room» → embedding classifier → device.on, entity=light, location=living room → виконання → «Лампу у вітальні увімкнено» через TTS.

Реальні проблеми, з якими я зіткнувся

Проблема 1: «turn the tape on» vs «turn on the light»

Helsinki-NLP перекладає «увімкни стрічку в кабінеті» як «Turn the tape on in your office».

Слово «tape» замість «LED strip» або «light strip» — і embedding classifier плутається між device.on і device.off з margin лише 0.003. Ця крихкість перекладу — реальна проблема офлайн pipeline без LLM семантичного згладжування.

Вирішення поки що часткове: phrase_cache + накопичення правильних перекладів через LLM Tier 2 в базу.

Проблема 2: ambiguous entity names

Якщо в базі є «Кондиціонер» з type=air_conditioner, модель embedding бачить «Turn on the air conditioning» і класифікує це як device.set_mode, бо в тренувальних даних «air conditioning» часто асоційоване з режимами, а не вмиканням.

Рішення: для типів пристроїв (thermostat, air_conditioner) додав explicit override: якщо немає value у параметрах і є базова команда «turn on/off» — форсую device.on/off.

Проблема 3: RAM на Raspberry Pi 4

Pi 4 з 4GB RAM тримає:

  • Vosk STT model: ~200MB
  • ONNX embedding model: ~90MB
  • Helsinki translator: ~300MB
  • Piper TTS: ~80MB
  • FastAPI сервер: ~150MB
  • React SPA: ~50MB

Разом ~870MB тільки для core stack. Ollama з llama3.1:8b ще ~5GB. Тому на Pi 4 Ollama = тільки якщо є swap і ви готові до 3+ секунд inference.

Поточне рішення: Tier 2 LLM може бути хмарним (Claude API) — це опція для тих, хто хоче кращу якість без потужного заліза. Або NVIDIA Jetson Orin Nano — там NVMe + LPDDR5 дозволяє комфортно запускати 8B моделі.

Проблема 4: display stack

Хотів мати гарний fullscreen дашборд. Chromium kiosk mode споживає ~300MB RAM і важко оптимізується на Pi.

Зараз переходжу на WPE WebKit через cog — це ~50MB і набагато легший footprint. Але є нюанс: на NVIDIA Jetson Tegra DRM несумісний з wlroots, тому fallback до Xorg + Chromium там залишається.

Що зараз вміє система

  • Управління пристроями через голос: увімкнути/вимкнути світло, регулювати яскравість, колір, температуру
  • Підтримка Tuya-сумісних пристроїв через local protocol (без хмари)
  • Інтеграція з Home Assistant через WebSocket
  • Таймери, будильники, нагадування
  • Запит температури з датчиків
  • Ідентифікація хто вдома через presence detection
  • Радіо (online stream)
  • Повністю офлайн режим — все працює без інтернету

Що в планах

Основна мета на наступний квартал — довести translation pipeline до production-ready стану і зробити перший публічний реліз з повноцінними docker-образами для Pi 4, Pi 5 і Jetson.

Також думаю над spell correction через symspellpy для компенсації STT помилок — але це після накопичення достатнього логу реальних фраз.

Висновок

SelenaCore — це доказ того, що повністю офлайн голосовий асистент для розумного будинку з підтримкою української мови реальний на доступному залізі. Це не комерційний продукт і не спроба замінити Home Assistant — це інструмент для тих, кому важлива приватність і хто хоче розуміти, що відбувається всередині.

Проєкт open-source, MIT ліцензія. Якщо вам цікаво — заходьте на GitHub (github.com/dotradepro/SelenaCore) і зірочка буде дуже доречна 🙂

Якщо маєте питання про архітектуру, конкретні рішення чи хочете обговорити — пишіть у коментарях, постараюсь відповісти на все

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось14
До обраногоВ обраному10
LinkedIn
Ctrl + Enter
Ctrl + Enter

Дуже хороша ініціатива, дякую!

Google Home і Amazon Alexa — ... не дуже добре розуміють українську

а взагалі розуміють? у Вас є досвід налаштування? було б цікаво

Я поки зупинився на хмарних Gemini моделях. Безкоштовних лімітів наразі вистачає на «побавитися».
Маю ще ollama на ігровому десктопі, який прокидається Wake-on-Wlan пакетом, коли хтось стукає в Ollama API, але то не дуже зручно чекати поки воно прокинеться в повсякденному використанні.

Іване, крута стаття — читав і впізнавав себе на кожному кроці 😄

Я теж нещодавно зробив voice dictation, тільки з іншого боку: замість розумного будинку — прямо в VS Code/Cursor. Мікрофон у статус-барі, Gemini транскрибує, текст летить у чат з AI-асистентом. Шлях хмарний (протилежність до твого), але задача та сама — прибрати клавіатуру між мозком і інструментом.

Найбільший інсайт збігається: найскладніше не «зробити щоб працювало», а зробити щоб latency не вбивала UX. У тебе 150-300мс на Pi — повага, це реально непогано для edge inference.

Зірочку поставив, слідкуватиму за релізом 🚀

Дякую за зірочку!
Згоден, затримка — це найбільший біль. Якщо асистент тупить, юзер експірієнс одразу в мінус. Тому я так і заморочився з локалкою на «малинці», щоб витиснути максимум швидкості.
Кейс із диктуванням коду в Cursor — вогонь. Хоч підходи й різні (хмара vs локалка), але мета одна — звільнити руки. Успіхів з проєктом!

Ollama з інференсом на CPU чи GPU?
Можливо варто розглянути llama.cpp, він дає кращій перформанс.

Доволі суттєвий скачок до jetson nano. Не розглядалась та сама Raspberry Pi 4 з 8Gb або RaspberryPi 8/16Gb? Більше пам’яті дасть кращій перформанс, а 5та ще й значно потужніша. З переваг все те саме оточення і ціна суттєво нижча.

В будь якому разі чудовий проєкт. Успіхів.

Дякую, Serhii!

Щодо llama.cpp — теоретично на CPU він може давати кращий результат завдяки більш агресивній оптимізації під конкретне залізо, але ми це окремо не бенчмаркали. Якщо у вас є реальні цифри порівняння на ARM — було б дуже цікаво подивитись, можливо варто розглянути як альтернативний бекенд.

Щодо продуктивності — поділюсь реальними результатами наших тестів:

Канонічний бенч (40 голосових команд, uk/en):
• Точність розпізнавання інтентів: 94.3%
• Embedding класифікатор (MiniLM-L6-v2, 22 MB): classify p50 = 41ms
• E2E з перекладом Helsinki-NLP: p50 = 155ms

Stress-бенч (70 команд, включно з мусорними, заїканнями, слен­гом):
• Точність: 82.5% на Pi 5
• Filler words (ну, um, like): 100% — модель їх просто ігнорує
• Абракадабра / нерозбірливе: коректно йде в unknown

Ключовий момент: GPU потрібен тільки для LLM-інференсу (складний діалог, нові фрази). Розпізнавання команд через embedding-класифікатор працює блискавично на будь-якому залізі — 22 MB модель, CPU-only, без GPU взагалі. Більшість домашніх команд обробляється за 100-200ms end-to-end.

Для звичайного розумного будинку Pi 5 8GB більш ніж достатньо. Pi 5 16GB дає змогу запускати більшу LLM для діалогу — але на розпізнаванні команд це не відображається.

Доповню: Ollama вже не вбудований провайдер у SelenaCore. Винесли його нарівні з Claude, OpenAI та іншими — як зовнішній LLM через REST API. Користувач вказує endpoint у налаштуваннях і підключає той інференс, що йому зручно: Ollama, llama.cpp у server mode, LocalAI, чи хмару.
Причина та, про яку ви пишете: різне залізо і ОС не дають єдиної оптимізації. Хтось витисне максимум із llama.cpp під свій CPU, хтось — із Ollama + CUDA на Jetson. Тому ядро бекенд-агностичне — кожен обирає під себе.

Я в захваті від локальних голосових помічників, особливо українською мовою. Для мене LMM що працюють локально це досі щось нове та дике. Та і сама ідея україномовних голосових помічників ще три чотири роки тому також вважалось чимось з сфери фантастики. Дякую вам за роботу і статтю з нетерпінням чекаю docker образ

Богдане, дякую за теплі слова! Так, попередня бета вже доступна на GitHub — github.com/dotradepro/SelenaCore. Вона сирувата і ще не обкатана, але я намагаюсь рухатись максимально швидко.
Паралельно треную голосові моделі Piper для україномовних відповідей. Зараз, наприклад, іде тренування голосу Леся Подерев’янського — ще трохи кульгає, але вже є впізнаваний характерний голос 😄
Docker-образ буде — слідкуйте за репозиторієм!

А голоси можна тренувати? Це як? В мене знову вибух мозку. До речі про локальні LLM встановив собі програму Ollama та локальну LLM gemma4:e2b

Так, тренування голосу — реально! Я навіть зробив окремий інструмент для цього: github.com/dotradepro/Piper-Master-Trainer
Принцип простий: берете аудіо з виступів потрібної людини, запускаєте тренування — і модель вчиться відтворювати голос. На прикладі Трампа мені вистачило 2–3 години навчання, причому відео брав просто з публічних виступів — і модель дуже швидко почала говорити його голосом
Ollama + gemma4b — хороший старт для локального LLM! Для розумного будинку з простими командами навіть невелика модель справляється непогано.

Так, при бажанні можна навіть відтворити голос улюбленого персонажа з ігор/фільмів, якщо достатньо звукових семплів назбирати. Так наприклад, мій асистент розмовляє голосом GLaDOS з Portal. Мопед не мій, але звучить круто) Ще й «характер» можна відповідний налаштувати, відредагувавши системний промпт.
github.com/...​uan-binh/glados-respeaker

Ваша glados україномовна чи англомовна? Дуже люблю glados і portal, навіть озвучку українську голосом glados зробив youtu.be/Gk78p5PVVB8, але мені озвучувала акторка дубляжу github.com/sverdlyuk/glados_ukr у вас respeaker lite? Я не зміг його примусити працювати і купив voice assistant preview edition

В мене англомовна, як в оригіналі. В цілому, наразі багато готових тулів та автоматизацій для Home Assistant Voice Pipeline, який я використовую, зроблено з розрахунком саме на англійську, тож використовую що є. Геть усе переписувати нажаль немає часу)

Я так розумію розмовний агент в pipeline це LLM, а Text-to-Speech це озвучка голосом. Що у вас використовується? У мене ось так
res.cloudinary.com/...​/ahof0rxt4bukgoiianhd.jpg

Whisper -> Gemini (aka Google Generative AI) -> Piper
Все це (звісно, за винятком LLM) крутиться на Mini PC з Intel N100, без GPU.

Підписатись на коментарі