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

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

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

MAATRIX

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

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

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

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

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

Голосовой бот — это конвейер из трёх независимых сервисов плюс код, который их связывает. Пользователь присылает голосовое сообщение в 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, открывать нечего.

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

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

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

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

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

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Whisper

Шаг 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-сервер.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Whisper

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

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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, что дублей не осталось.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.