MAATRIX / Блог / Как поднять голосового бота на своём сервере

Как поднять голосового бота на своём сервере

Как поднять голосового бота на своём сервере

MAATRIX

Голосовой бот в Telegram, который слушает и отвечает голосом, — это не подписка на облачный API за доллары с минуты, а три открытые модели на обычном VPS. Многие материалы про такую связку сразу требуют GPU ради диалога с задержкой в доли секунды, но для голосовых сообщений в мессенджере это лишняя трата. Ниже — путь без видеокарты: порты, конфиги, код бота на Python и тексты ошибок, на которых обычно спотыкаются.

Из чего собран голосовой бот: три сервиса и оркестратор

Голосовой бот — это конвейер из трёх независимых сервисов плюс код, который их связывает. Пользователь присылает голосовое сообщение в Telegram: распознавание речи превращает голос в текст, языковая модель формирует ответ, синтез речи превращает ответ обратно в голос, и бот отправляет его назад. Каждое звено — отдельный процесс на одном сервере, общаются они по localhost, наружу торчит только сам бот.

ЗвеноИнструментПортОриентир по памяти
Распознавание речи (STT)Whisper (faster-whisper)8001~1,5–2 ГБ для модели small
Языковая модель (LLM)Ollama114342,2–5,6 ГБ на весах модели 3–8B в Q4
Синтез речи (TTS)Piper5000сотни МБ на голос
ОркестраторPython-скрипт (aiogram)— (long polling)десятки МБ

Портов наружу не нужно вовсе: бот сам стучится в Telegram по исходящему HTTPS, а три сервиса слушают только 127.0.0.1 и видны лишь друг другу. Это упрощает и установку, и защиту — в фаерволе, кроме SSH, открывать нечего.

Дальше по шагам: что ставится само из каталога, что разворачивается вручную, и как склеить всё в работающего бота.

Какой VPS купить для Telegram-бота

Для одного простого Telegram-бота без локальной нейросети начните с Lite: 1 vCPU, 2 ГБ памяти и 40 ГБ NVMe. Если у бота есть база данных, обработка файлов или несколько сервисов рядом, выберите Старт: 2 vCPU и 4 ГБ памяти.

Один небольшой бот

Lite · 1 vCPU · 2 ГБ RAM
40 ГБ NVMe
Запуск бота постоянно, независимо от вашего компьютера.

Заказать Lite

Бот с базой данных

Старт · 2 vCPU · 4 ГБ RAM
80 ГБ NVMe
Начальная конфигурация для бота и сопутствующих сервисов.

Заказать Старт

После аренды VPS установите зависимости и настройте запуск бота по инструкции ниже. Размер файлов и число одновременных задач влияют на дальнейший выбор ресурсов.

В кнопках заказа выбрана локация в Великобритании. Другую страну можно выбрать при оформлении.

Оплата VPS картой РФ или по СБП доступна в личном кабинете. Для безналичной оплаты от юридического лица или ИП запросите счёт у support@maatrix.io, указав тариф и реквизиты.

Почему обычный путь не работает: облачные API, домашний ПК и живой звонок

Первая идея — взять готовый облачный голосовой API. У неё три проблемы. Счёт растёт с каждой минутой распознавания и каждым символом синтеза — при активном боте на десятки диалогов в день сумма перестаёт быть символической. Часть сервисов требует зарубежную карту или недоступна с российских IP напрямую. И приватность: голос и переписка идут через чужую инфраструктуру, а для бота поддержки или личного ассистента это не всегда приемлемо.

Вторая идея — запустить всё на домашнем компьютере. Проверка простая: сравните IP на роутере (WAN-адрес) с тем, что отдаёт curl -s ifconfig.me из локальной сети. Разные адреса — вы за CGNAT (carrier-grade NAT), и провайдер не пробросит входящее соединение ни при какой настройке роутера; многие российские провайдеры включают его по умолчанию. Даже без CGNAT остаются проблемы домашнего железа: ПК уходит в сон, IP меняется при переподключении, обновления перезагружают машину в случайный момент — бот молча пропадает из чата.

Третья идея — раз нужен голос, значит нужен GPU-сервер с диалогом в реальном времени, как для телефонного звонка. Это верно для разговора с задержкой в доли секунды между репликами. Но голосовое сообщение в Telegram — формат асинхронный: пользователь наговорил, отправил и ждёт ответа не затаив дыхание, а как обычное сообщение. Эта разница меняет требования к серверу — разберём, во что это выливается на практике.

Шаг 1. Распознавание речи: Whisper на входе

Whisper есть в каталоге приложений apps.maatrix.io — при заказе сервера с этим приложением он ставится автоматически, без единой команды с вашей стороны, а адрес и токен доступа появляются в личном кабинете, в разделе «Доступ». Такая сборка поднимает обычный HTTP-эндпоинт поверх faster-whisper: POST /transcribe, файл в теле запроса, в ответ — JSON с полем text. Проверить, что сервис жив, можно одной командой, подставив свои значения из кабинета:

curl -s -X POST https://ваш-адрес/transcribe \
     -H "Authorization: Bearer $WHISPER_TOKEN" \
     -F "file=@test.wav;type=audio/wav"

Для голосового бота важны два параметра, если сборка их принимает: язык ru — экономит проход автоопределения и не даёт короткой фразе уехать в английский, и фильтр тишины (VAD) — без него Whisper на паузах и шуме склонен домысливать текст. Модель small — разумное значение по умолчанию: заметно легче medium, а на короткой фразе разница в точности небольшая. Апгрейд до medium, если точность станет важнее скорости, — смена одной переменной в кабинете.

На вход Whisper ждёт WAV 16 кГц, моно, PCM. Голосовые сообщения Telegram приходят в формате OGG/Opus, так что между «получили от пользователя» и «отправили в Whisper» всегда стоит перекодирование — соберём его в коде бота, в шаге 3. Если хотите поднять faster-whisper не через каталог, а руками, и посмотреть на установку изнутри — подробный разбор в отдельной статье: как установить и настроить faster-whisper на VPS.

Шаг 2. Мозг бота: LLM через Ollama и выбор модели по скорости

Ollama тоже есть в каталоге отдельным приложением, но для этой статьи ставим её руками — так нагляднее видно, что происходит внутри связки. Команда одна:

curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:7b

Сервис поднимается на порту 11434 и по умолчанию слушает только 127.0.0.1 — снаружи недоступен, что здесь и нужно. Проверка:

curl http://127.0.0.1:11434/api/generate -d '{
  "model": "qwen2.5:7b",
  "prompt": "Скажи одним предложением, что ты на связи.",
  "stream": false,
  "options": {"num_thread": 8}
}'

Модель, не скачанную заранее, Ollama не подтянет сама на лету — вернётся ошибка, и это нормальное поведение, а не сбой:

{"error":"model 'qwen2.5:7b' not found, try pulling it first"}

Для голосового бота выбор модели зависит не только от качества ответа, но и от скорости генерации — она напрямую превращается в секунды ожидания перед тем, как заговорит TTS. На нашем стенде (AMD EPYC 9554, 16 vCPU = 8 физических ядер + HT, Ollama 0.33.1, num_thread 16) генерация выглядит так:

МодельГенерация, ток/сЗанято в памяти
qwen2.5:3b34,12,2 ГБ
llama3.1:8b12,85,6 ГБ
mistral:7b12,15,0 ГБ
gemma2:9b8,8—
qwen2.5:7b7,45,1 ГБ

Разброс в разы на моделях сопоставимого размера — не опечатка, а разная эффективность архитектур на CPU. Для ответа в 60–100 токенов разница между qwen2.5:3b и qwen2.5:7b — это три секунды ожидания против пятнадцати. Для коротких бытовых ответов компактная модель почти всегда выигрывает у крупной по ощущениям от диалога. Как число потоков num_thread влияет на скорость и почему после восьми ядер выигрыша почти нет — подробно разобрано в отдельной статье: Ollama не использует все ядра CPU.

Системный промпт для голосового бота стоит писать отдельно от текстового: попросите модель отвечать 2–4 предложениями разговорным языком и явно запретите markdown — списки, звёздочки, заголовки. TTS читает их вслух буквально, и «звёздочка звёздочка важно звёздочка звёздочка» в голосовом сообщении производит не лучшее впечатление на собеседника.

Шаг 3. Голос ответа: Piper и сборка Telegram-бота

Piper в каталоге не значится — ставится руками, но это быстро: пакет лёгкий, из тяжёлого — только сама модель голоса.

pip install piper-tts
mkdir -p /opt/piper/voices && cd /opt/piper/voices
wget https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/irina/medium/ru_RU-irina-medium.onnx
wget https://huggingface.co/rhasspy/piper-voices/resolve/main/ru/ru_RU/irina/medium/ru_RU-irina-medium.onnx.json
python3 -m piper.http_server --model ru_RU-irina-medium.onnx --host 127.0.0.1 --port 5000

Установка, выбор голоса, ударения и systemd-юнит — в отдельной статье: как установить и настроить Piper TTS на VPS; здесь важно, что сервис слушает 127.0.0.1:5000 и на запрос с текстом отдаёт WAV. Из трёх звеньев Piper — самое быстрое: короткую фразу озвучивает почти мгновенно.

Дальше — код, который держит всё вместе. Регистрируем бота у @BotFather, получаем токен, ставим aiogram (линейка третья, с роутерами и асинхронными хендлерами) и httpx для запросов к сервисам:

pip install aiogram httpx
# bot.py
import asyncio, os, re, subprocess, tempfile
import httpx
from aiogram import Bot, Dispatcher, F
from aiogram.types import Message, FSInputFile

TOKEN = os.environ["BOT_TOKEN"]
WHISPER_URL = os.environ["WHISPER_URL"]        # https://ваш-адрес/transcribe из кабинета
WHISPER_TOKEN = os.environ["WHISPER_TOKEN"]
SYSTEM_PROMPT = "Ты голосовой ассистент. Отвечай 2-4 предложениями, разговорным языком, без markdown и списков."

bot = Bot(TOKEN)
dp = Dispatcher()

def strip_markdown(text: str) -> str:
    return re.sub(r"[*_#`]", "", text)

@dp.message(F.voice)
async def on_voice(message: Message):
    tg_file = await bot.get_file(message.voice.file_id)
    with tempfile.TemporaryDirectory() as tmp:
        oga, wav = f"{tmp}/in.oga", f"{tmp}/in.wav"
        reply_wav, reply_ogg = f"{tmp}/out.wav", f"{tmp}/out.ogg"
        await bot.download_file(tg_file.file_path, oga)
        await asyncio.to_thread(subprocess.run,
            ["ffmpeg", "-y", "-i", oga, "-ar", "16000", "-ac", "1", wav],
            check=True, capture_output=True)

        async with httpx.AsyncClient(timeout=60) as client:
            r = await client.post(WHISPER_URL,
                                   headers={"Authorization": f"Bearer {WHISPER_TOKEN}"},
                                   files={"file": open(wav, "rb")})
            user_text = r.json()["text"]

            r = await client.post("http://127.0.0.1:11434/api/chat", json={
                "model": "qwen2.5:7b", "stream": False,
                "messages": [{"role": "system", "content": SYSTEM_PROMPT},
                             {"role": "user", "content": user_text}],
                "options": {"num_thread": 8},
            })
            reply_text = strip_markdown(r.json()["message"]["content"])

            r = await client.get("http://127.0.0.1:5000/", params={"text": reply_text})
            open(reply_wav, "wb").write(r.content)

        await asyncio.to_thread(subprocess.run,
            ["ffmpeg", "-y", "-i", reply_wav, "-c:a", "libopus", reply_ogg],
            check=True, capture_output=True)
        await message.answer_voice(FSInputFile(reply_ogg))

asyncio.run(dp.start_polling(bot))

Ключевое решение — long polling (dp.start_polling), а не вебхук: не нужен домен, TLS-сертификат и публичный порт, бот сам ходит в Telegram по исходящему HTTPS. Для личного или командного бота это ровно то, что нужно — вебхук окупается только на заметной нагрузке.

Частая ошибка на этом этапе — запустить второй экземпляр бота, не остановив первый: например, тестируете код руками поверх уже работающего systemd-сервиса.

aiogram.exceptions.TelegramConflictError: Telegram server says - Conflict: terminated by
other getUpdates request, make sure that only one bot instance is running

Telegram отдаёт обновления только одному активному получателю на токен — лечится командой systemctl stop bot перед ручным запуском, а не отладкой кода. Для автозапуска оформите бота systemd-юнитом с Restart=on-failure, как остальные сервисы связки.

Нюансы: задержка, память и что легко сломать

Сложите три звена — и получится оценка полной задержки, посчитанная по известным цифрам, а не снятая секундомером. Faster-whisper в int8 на восьми потоках десктопного CPU обрабатывает 13-минутную запись за 1 минуту 42 секунды — цифра из документации проекта, не наш замер, но пересчитать легко: ×7,6 к реальному времени, значит десятисекундное сообщение уйдёт на распознавание около 1,3 секунды на сопоставимом железе, на слабом vCPU — больше; проверяйте через time. Генерация — от 3 секунд на компактной модели до 15 на крупной, Piper заметной доли не добавляет. Итого: 5–7 секунд на компактной модели, 15–20 на крупной — не медленнее сообщения, набранного руками.

Конкурентность на CPU ограничена сильнее, чем кажется: без OLLAMA_NUM_PARALLEL Ollama сама решает число одновременных запросов, а генерация упирается в память, а не в свободные ядра — два диалога сразу не станут вдвое быстрее просто оттого, что ядро свободно. Для личного бота на 2–5 человек это не проблема; для публичного с десятками пользователей — уже другой разговор, про GPU или очередь запросов.

Файлы копятся незаметно: на сообщение — минимум четыре временных файла (oga, wav, wav, ogg). Код держит их во TemporaryDirectory и подчищает сам, но замените временную папку на постоянную для отладки — заведите cron-очистку, иначе диск за месяц незаметно забьётся аудио, которое никто не слушал.

Отдельно про качество: Whisper на шуме или в паузах домысливает текст, а модель без явного запрета иногда вставляет форматирование в ответ, особенно если в истории уже была таблица или список — держите strip_markdown в коде даже при надёжном промпте, это дешёвая страховка. И держите три сервисных порта — 8001, 11434, 5000 — закрытыми снаружи: ufw allow 22/tcp, и этого достаточно.

Какую конфигурацию и локацию брать в MAATRIX

Три сервиса на одном сервере складываются по памяти арифметически, и это главное число при выборе тарифа.

МинимумКомфортно
vCPU48
RAM8 ГБ16 ГБ
Диск40–60 ГБ NVMe80–100 ГБ NVMe
Модель LLMqwen2.5:3b или 7b по выборуqwen2.5:7b постоянно, запас под вторую
Что получаетеодин диалог за раз, без запасанесколько параллельных диалогов, medium у Whisper при желании

Честно про минимум: 8 ГБ делятся на Whisper small (~1,5–2 ГБ), веса Ollama (2,2–5,6 ГБ по выбору), Piper (сотни МБ) и систему с ботом — запас небольшой, две LLM-модели одновременно там не поместятся. 16 ГБ снимают вопрос и оставляют место под medium у Whisper, если точность важнее экономии.

Ставить всё руками не обязательно. Whisper — приложение из каталога apps.maatrix.io, ставится автоматически при заказе, доступы в кабинете. Ollama там же есть отдельной сборкой — закажете сервер сразу с обоими, и вручную останется только Piper и код бота. Ubuntu 24.04 и Debian 12 поддерживаются оба.

Локация — Великобритания (Лондон). Причина конкретная: веса Whisper и модели Ollama качаются с huggingface.co и из реестра Ollama, а с российских адресов эти хосты отвечают нестабильно — первая загрузка модели рискует зависнуть. С лондонской площадки веса идут напрямую. Telegram Bot API из блокировок не входит и доступен из любой локации — на выбор он не влияет. Если аудитория целиком российская и данные должны оставаться в периметре 152-ФЗ, берите российскую локацию и заранее прогрейте кеш моделей, перенеся HF_HOME и папку моделей Ollama с сервера, где скачивание прошло без проблем.

Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT: сервер в Лондоне не требует зарубежной карты. Живой звонок с ответом за доли секунды эта конфигурация не потянет — там нужен GPU, и это отдельный разбор: голосовой бот на своей модели на сервере. А для голосовых сообщений в Telegram, где несколько секунд ожидания — обычное дело, справляется и рядовой CPU-сервер.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Нужен ли GPU для голосового бота в Telegram?

Нет, для асинхронных голосовых сообщений: цикл распознавание-ответ-синтез укладывается в секунды-десятки секунд на обычном CPU-сервере — привычная задержка для мессенджера. GPU нужен для живого диалога с задержкой в доли секунды, например для телефонного звонка.

Какую модель Ollama ставить для голосового бота?

Смотрите на скорость генерации, а не только на качество: qwen2.5:3b даёт 34,1 ток/с и занимает 2,2 ГБ — короткий ответ готов за пару секунд. Модели крупнее (llama3.1:8b, qwen2.5:7b) звучат увереннее, но ответа придётся ждать 10–15 секунд.

Бот перестал отвечать после перезапуска, в логах Conflict. Что делать?

Это TelegramConflictError — где-то работает второй экземпляр бота с тем же токеном, а Telegram отдаёт обновления только одному активному получателю. Остановите старый процесс (systemctl stop bot) перед новым запуском и проверьте ps aux | grep bot.py, что дублей не осталось.

Готовы перейти к настройке?

Закажите Lite с конфигурацией, предложенной для старта, или сравните все тарифы. Оплата картой РФ и через СБП — в личном кабинете.

Заказать Lite Все тарифы и цены