Як зібрати селфхост войс-агента без жодного платного API
Ми з командою зібрали голосового агента без зовнішніх AI API. Розпізнавання мовлення, мовна модель і синтез голосу працюють на одному орендованому сервері. Далі про те, як усе запустили, на чому виграли час і чому вільна пам’ять на відеокарті ще не означає, що можна додати більше розмов.
У наших тестах агент починав відповідати через
Стаття для AI та backend-розробників, які придивляються до власного голосового стеку. Покажу налаштування, результати й розрахунок витрат.
Чому свій стек і чому каскад
Нам були потрібні чотири речі: не передавати голос і текст постачальникам моделей, отримати власний голос із короткого запису, платити за оренду, а не за кожну хвилину, і міняти компоненти незалежно один від одного.
Під self-hosted тут маємо на увазі власне розгортання на орендованому сервері, а не власне фізичне залізо. Хостинг-провайдер нікуди не зникає. Оренду платимо навіть тоді, коли дзвінків немає, а фіксованої суми вистачає лише в межах потужності машини.
Ми розглядали три підходи. У гібриді модель отримує звук і повертає текст, який озвучує окремий синтезатор. У speech-to-speech на вході й виході звук. А в каскаді кожен компонент робить свою роботу:
Аудіо → розпізнавання → текст → LLM → синтез → аудіовідповідь
Обрали каскад: так можна окремо замінити розпізнавання, мовну модель або голос. Далі називатиму розпізнавання ASR, мовну модель LLM, а синтез мовлення TTS.
Паралельно детектор вирішує, чи людина вже договорила. Поспішіть, і агент переб’є. Зачекає задовго, і отримаємо незручну паузу. Саме розпізнавання працює ще під час мовлення. LLM теж віддає текст частинами, а TTS починає озвучувати перший придатний фрагмент. Нам важливо, коли людина почує перший звук, а не коли згенерується вся відповідь.
Що працює на сервері

Silero VAD + Smart Turn v3. Silero знаходить голосову активність і паузи. Smart Turn слухає аудіо після паузи та оцінює, чи завершена репліка. Він аналізує саме звук, а не розділові знаки в розпізнаному тексті. Цей механізм описано в документації Pipecat.
Nemotron ASR Streaming 0.6B. Запускаємо int8-версію через sherpa-onnx на CPU: приблизно 1,0 ГБ оперативної пам’яті й жодної VRAM. Подаємо звук чанками, тобто фрагментами, по 1120 мс. Це не означає, що після кожної репліки додається ще 1120 мс тиші: більшу частину звуку модель уже обробила, поки людина говорила.
Gemma 4 12B QAT Q4_0. Цю версію моделі обрали, щоб вона помістилася на GPU поруч із TTS. QAT означає навчання з урахуванням квантизації. Файл ваг займає близько 7 ГБ, але під час роботи модель використовує 8,03 ГіБ VRAM. Тому розмір файлу ще не є відповіддю на питання, скільки пам’яті треба на картці.
CosyVoice 3 0.5B. Використовуємо Fun-CosyVoice3-0.5B-2512: синтез займає 4,32 ГіБ VRAM. Нас цікавило клонування голосу з трисекундного запису, але заміри робили зі стандартним голосом. Серед дев’яти мов у README CosyVoice 3 української немає. Тобто ці результати ще не означають, що ми зібрали готового україномовного агента.
Pipecat. Він з’єднує сервіси, передає кадри й обробляє перебивання. До LLM звертається через локальний OpenAI-сумісний інтерфейс llama-server. Для ASR і TTS ми написали власні сервіси. Просто встановити оригінальні проєкти недостатньо, щоб повторити наші цифри: потрібні ще адаптери й зміни в сервері синтезу.
Як усе запустили й прискорили
Спочатку перевірили саму машину
Сервер із RTX 4090 коштував $0,402/год. У нашому середовищі було 23 процесорні ядра, Ubuntu 22.04, драйвер NVIDIA 580 і Linux 6.8. Дивитися лише на назву відеокарти не варто: ASR працює на CPU, і процесор теж впливає на результат.
Після запуску перевіряли синтез на одній фразі й робили прогрівальний дзвінок. Наш орієнтир — близько 334 мс до першого звуку TTS.
І ще до тестів варто розібратися з мережею. У нашому тарифі внутрішні порти отримували випадкові зовнішні номери. Це ускладнило WebRTC, тому тестували через WebSocket. Йдеться про особливість нашого підключення, а не про те, що WebRTC взагалі не працює через NAT.
LLM: менше повторних обчислень і роздумів
Запускаємо LLM через llama.cpp, зібраний із CUDA:
llama-server \ -m gemma-4-12B-it-QAT-Q4_0.gguf \ -ngl 99 -fa on \ -c 8192 -np 2 \ --cache-type-k q8_0 --cache-type-v q8_0 \ --swa-full --cache-reuse 4 \ --jinja --reasoning-format deepseek
-np 2 — це два слоти для обробки запитів, а не ліміт у дві розмови. Кеш залежить від контексту й того, як використовуються слоти. Тому швидкість одного запиту з уже підготовленим кешем не можна автоматично обіцяти всім сесіям під навантаженням.
Кеш: не рахувати те саме ще раз. У нашому запуску —swa-full —cache-reuse 4 зменшили повторне обчислення токенів на 33%. За це заплатили приблизно 350 МБ пам’яті.
За документацією llama-server, —swa-full вмикає повнорозмірний кеш sliding-window attention, а —cache-reuse 4 задає мінімальний розмір фрагмента для спроби повторно використати кеш. Самі прапорці не обіцяють мінус 33% у будь-якому запуску результат залежить від налаштувань.
Історію розмови ми тільки дописуємо, не переписуємо її початок: інакше доводиться обробляти більше токенів заново. Дивимося на timings.prompt_n — це кількість токенів, які сервер фактично обробив. Слот прогріваємо після підключення. Саме на такому вже «теплому» ході отримали 68 мс до першого токена, а не на холодному старті.
Thinking: роздуми теж коштують часу. У наших тестах модель витрачала на них
{"chat_template_kwargs": {"enable_thinking": false}}
Це фрагмент запиту до моделі. Не плутайте його з —reasoning-format deepseek у команді сервера: той визначає формат обробки роздумів, але не вимикає їх.
TTS: допрацювали сервер CosyVoice
Початковий запуск CosyVoice був надто повільним для нашого сценарію. Ось що змінили й що отримали в замірах:

Додавати всі ці виграші в одну суму не варто: вони стосуються різних частин роботи. Після скорочення кількості кроків flow з 10 до 6 на слух різниці не помітили, але сліпого порівняння якості тут не наводимо.
Ми також не чекаємо, поки LLM напише все. Щойно з’являється розділовий знак, віддаємо перший фрагмент у TTS. 334 мс до першого звук результат нашого доопрацьованого сервера, а не стандартного запуску CosyVoice.
Паузи: швидше та не завжди краще
Тут підбирали два інтервали VAD/turn: скільки тиші чекати й скільки часу загалом давати на завершення репліки. Почали з 0,5 / 1,2 с. Пара 0,5 / 0,8 с дала близько 110 мс виграшу, а 0,3 / 0,5 с 330 мс.
Для коротких запитань підійшов швидший варіант. Але коли агент раніше вирішує, що людина договорила, треба вміти прийняти продовження її фрази й скасувати вже неактуальну відповідь. Для адрес, номерів і дат із паузами всередині залишаємо 0,5 / 1,2 с. Однієї вдалої пари для всіх розмов немає.
Що показали тести
Коли чути першу відповідь
Від кінця фрази людини до початку відповіді агента в наших замірах минало

Етапи йдуть один за одним, тому їхня сума і дає ті
Скільки розмов можна вести одночасно
Тест відкривав справжні з’єднання через ту саму точку входу, що й браузер. Кожна сесія програвала записані фрази: чотири ходи з паузами. Для 1, 2, 4 та 8 сесій зафіксували відповідно 4/4, 8/8, 15/16 і 31/32 ходи.

До чотирьох сесій помітної зміни затримки не було. На восьми додалося приблизно 350 мс. На дванадцяти найповільніші відповіді вже починалися через 6 с і більше це весь час очікування, а не ще шість секунд зверху.
Тому вісім сесій наш орієнтир для цього сценарію, а не фізична межа RTX 4090. І «нуль помилок» ми не заявляємо: частину ходів тест не зарахував, а самі лічильники не пояснюють, чому.
Найважливіший момент: уперлися не в пам’ять, а в чергу синтезу. VRAM під навантаженням трималася в межах 12
Щоб попередньо прикинути кількість сесій, використовували таку формулу:
Час TTS на хід ≈ час до першого звуку + RTF × тривалість відповіді
Сесії ≈ інтервал між ходами однієї сесії / час TTS на хід
RTF показує, скільки секунд обробки потрібно на секунду аудіо. У нас він був близько 0,1, а відповідь тривала 4 с. Отримуємо 0,334 + 0,1 × 4 = 0,734 с на хід. За інтервалу між ходами
Формулу ми звірили з навантажувальним тестом на семи машинах: на шести результат розійшовся не більш ніж на 20 %. На RTX 4090 формула дає від 9 сесій, тест показав 8. На дванадцяти сесіях найповільніші відповіді вже перевищували 6 с.
Перебивання перевіряли окремим
Коли оренда дешевша за API
Рахуємо оренду за нашою ставкою $0,402/год і умовними 730 годинами на місяць:
0,402 × 730 = $293,46 на місяць
Для API беремо $0,0556/хв із нашого кошторису каскаду Deepgram + Gemini Flash + ElevenLabs. Це ставка, яку ми заклали під свій трафік, а не перевірений тут поточний прайс. Детального розрахунку за токенами, символами й тарифами в матеріалі немає.

Щоб знайти обсяг, за якого суми зрівняються, ділимо місячну оренду на ціну хвилини API:
293,46 / 0,0556 ≈ 5 278 хв/місяць
Після цієї позначки оренда дешевша за цей конкретний API-сценарій. Але це ще не означає, що окупилися розробка й підтримка.
А звідки взялися $0,00084 за хвилину? Із розрахунку, в якому всі вісім сесій зайняті цілодобово:
Теоретичний обсяг = 8 × 60 × 730 = 350 400 хв/місяць
Оренда на хвилину = 293,46 / 350 400 ≈ $0,00084
Місячного тесту, який підтверджує такий обсяг, у матеріалі немає. Це теоретична ціна оренди на хвилину за повного завантаження, а не універсальна собівартість розмови. За 20 000 хвилин вона вже становить $0,0147. Крім загального обсягу, треба дивитися, скільки розмов припадає на пік і яку затримку можна допустити.

У цьому порівнянні немає витрат на розробку, підтримку, моніторинг, зберігання, додаткову мережу, телефонію й резервну машину. Інша ціна API теж змінює картину: за умовних $0,017/хв точка рівності зсувається до 17,3 тисячі хвилин.
Що ще перевірити перед запуском
Чи добре агент виконує задачу. Тут ми не порівнювали з хмарою точність ASR, природність голосу й виконання задач. Швидко заговорити ще не те саме, що правильно зрозуміти людину й допомогти їй. Мову свого продукту теж потрібно перевіряти окремо.
Що відбувається з даними й голосом. Відмова від зовнішніх AI API не робить систему приватною автоматично. Залишаються доступ до сервера, записи, логи, строки їх зберігання та інфраструктура орендодавця. А можливість клонувати голос із короткого запису не замінює дозволу людини на його використання.
Як усе працює під вашим навантаженням. Потрібні довші діалоги, реальні тривалості відповідей, піковий трафік і розбір незарахованих ходів. Лише після цього варто визначати робочу кількість одночасних сесій.
А в підсумку
Повний голосовий каскад без платних AI API помістився на одному сервері: ASR працює на CPU, LLM і TTS на RTX 4090. Але вибрати моделі було недостатньо. Результат: кеш, прогрів, передавання тексту частинами, зміни в TTS і правильна обробка перебивань.
Головний технічний висновок для нас: дивитися треба не лише на VRAM, а й на те, де запити стають у чергу. Головний висновок щодо витрат: низька ціна хвилини без пояснення завантаження мало про що говорить.
Власне розгортання дало нам контроль над компонентами й передбачувану оренду. Чи варто повторювати цей шлях , залежить від вашого трафіку, вимог до даних і витрат на підтримку.
Буду радий обговорити деталі в коментарях і поділитися досвідом інтеграції.
2 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарівДякую за цікаву статтю . 👏
Дякуємо, Назаре! 🔥 Тішимось, що ти з нами й що можемо робити такі круті речі разом 🙌 Якщо вам відгукується те, що ми робимо, заглядайте до нас, зараз шукаємо людей у команду 😉