Как поднять голосового бота на своём сервере
Голосовой бот в Telegram, который слушает и отвечает голосом, — это не подписка на облачный API за доллары с минуты, а три открытые модели на обычном VPS. Многие материалы про такую связку сразу требуют GPU ради диалога с задержкой в доли секунды, но для голосовых сообщений в мессенджере это лишняя трата. Ниже — путь без видеокарты: порты, конфиги, код бота на Python и тексты ошибок, на которых обычно спотыкаются.
Содержание
- Из чего собран голосовой бот: три сервиса и оркестратор
- Почему обычный путь не работает: облачные API, домашний ПК и живой звонок
- Шаг 1. Распознавание речи: Whisper на входе
- Шаг 2. Мозг бота: LLM через Ollama и выбор модели по скорости
- Шаг 3. Голос ответа: Piper и сборка Telegram-бота
- Нюансы: задержка, память и что легко сломать
- Какую конфигурацию и локацию брать в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Из чего собран голосовой бот: три сервиса и оркестратор
Голосовой бот — это конвейер из трёх независимых сервисов плюс код, который их связывает. Пользователь присылает голосовое сообщение в Telegram: распознавание речи превращает голос в текст, языковая модель формирует ответ, синтез речи превращает ответ обратно в голос, и бот отправляет его назад. Каждое звено — отдельный процесс на одном сервере, общаются они по localhost, наружу торчит только сам бот.
| Звено | Инструмент | Порт | Ориентир по памяти |
|---|---|---|---|
| Распознавание речи (STT) | Whisper (faster-whisper) | 8001 | ~1,5–2 ГБ для модели small |
| Языковая модель (LLM) | Ollama | 11434 | 2,2–5,6 ГБ на весах модели 3–8B в Q4 |
| Синтез речи (TTS) | Piper | 5000 | сотни МБ на голос |
| Оркестратор | 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:3b | 34,1 | 2,2 ГБ |
| llama3.1:8b | 12,8 | 5,6 ГБ |
| mistral:7b | 12,1 | 5,0 ГБ |
| gemma2:9b | 8,8 | — |
| qwen2.5:7b | 7,4 | 5,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
Три сервиса на одном сервере складываются по памяти арифметически, и это главное число при выборе тарифа.
| Минимум | Комфортно | |
|---|---|---|
| vCPU | 4 | 8 |
| RAM | 8 ГБ | 16 ГБ |
| Диск | 40–60 ГБ NVMe | 80–100 ГБ NVMe |
| Модель LLM | qwen2.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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.