MAATRIX / Блог / Как поднять AI-бота в Telegram через OpenAI API

Как поднять AI-бота в Telegram через OpenAI API

Как поднять AI-бота в Telegram через OpenAI API

MAATRIX

Эхо-бот в Telegram собирается за полчаса, а бот с OpenAI внутри — за полчаса плюс неделя граблей. Ключ отдаёт 403 из России, длинный ответ не влезает в сообщение, стриминг упирается в «message is not modified», а к утру на балансе минус двадцать долларов, потому что бота нашли посторонние. Разберём схему целиком: где держать процесс, код на aiogram, память диалога, стриминг и защиту кошелька.

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

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

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

Почему AI-бот в телеграме не запускается из России

Схема простая: Telegram Bot API — ваш процесс — api.openai.com. Первое звено из России доступно, третье — нет. Ключ, купленный любым способом, с российского IP отвечает так:

$ curl -s https://api.openai.com/v1/models -H "Authorization: Bearer sk-proj-..."
{"error":{"message":"Country, region, or territory not supported",
"type":"request_forbidden","param":null,
"code":"unsupported_country_region_territory"}}

HTTP-код — 403, в SDK это openai.PermissionDeniedError. Ошибка гео-политическая, а не сетевая: TCP-соединение с Cloudflare перед api.openai.com встаёт нормально, и ping до него из Москвы даёт вменяемые 25–40 мс. Поэтому причину часами ищут в сети, хотя её там нет.

VPN на ноутбуке не спасает: бот должен отвечать круглосуточно. VPN на сервере спасает, но добавляет звено, которое падает само и роняет бота. Честнее вынести процесс туда, где ключ работает без прослоек.

Из локаций MAATRIX годятся UK (Лондон) и US (Нью-Йорк) — обе страны в списке поддерживаемых OpenAI. Лондон удобнее по второму звену: серверы Telegram Bot API стоят в Амстердаме, и ping -c 5 149.154.167.220 из Лондона даёт 7–9 мс против 75–85 мс из Нью-Йорка. На фоне 600–900 мс до первого токена это не решает судьбу, но при стриминге бот шлёт на ответ десятки editMessageText, и разница копится.

Готовим сервер и ключи

Берём Ubuntu 24.04 LTS: там из коробки Python 3.12, которого хватает и aiogram, и SDK OpenAI. Сначала пользователь и фаервол.

adduser --disabled-password --gecos "" aibot
ufw default deny incoming
ufw allow 22/tcp
ufw enable

Открывать больше нечего: на long polling бот только ходит наружу, 443 понадобится лишь при вебхуках. Это и есть аргумент начать с polling — домен и сертификат не нужны.

Дальше окружение и зависимости:

apt update && apt install -y python3-venv python3-pip redis-server
sudo -u aibot python3 -m venv /home/aibot/venv
sudo -u aibot /home/aibot/venv/bin/pip install "aiogram>=3.15" "openai>=1.60" redis tiktoken

Токен бота получаем у BotFather командой /newbot: он выглядит как 8123456789:AAH..., числа до двоеточия — user id бота. Сразу загляните в /mybots → Bot Settings → Group Privacy: по умолчанию приватность включена, и в группах бот видит только команды и реплаи — AI-ассистенту, читающему обсуждение, её надо отключить.

Ключ OpenAI создаётся в проекте на платформе и начинается с sk-proj-. Оба секрета кладём в /etc/aibot.env:

TG_TOKEN=8123456789:AAH...
OPENAI_API_KEY=sk-proj-...
MODEL=gpt-4o-mini

chmod 600 /etc/aibot.env — читает только root, а systemd отдаст переменные процессу. Проверяем ключ до всякого кода:

curl -s -o /dev/null -w "%{http_code}\n" https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY"

200 — порядок; 401 — ключ битый или отозван; 403 — запрос всё ещё из неподдерживаемой страны; 429 с телом "code": "insufficient_quota" — ключ живой, но баланс нулевой. Последнее ловит почти каждого: API оплачивается отдельно от подписки ChatGPT Plus, Plus не даёт доступа к API ни на доллар. И честно про оплату: российские карты OpenAI не принимает — либо зарубежная карта, либо посредник, а во втором случае вы делите аккаунт с чужой инфраструктурой. Сам сервер у MAATRIX оплачивается картой РФ, СБП или криптой.

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

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

Развернуть бот-стек

Минимальный AI-бот: сорок строк, которые уже работают

Кладём в /home/aibot/bot.py:

import asyncio, logging, os
from aiogram import Bot, Dispatcher, F
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ChatAction
from aiogram.types import Message
from aiogram.utils.chat_action import ChatActionSender
from openai import AsyncOpenAI

SYSTEM = "Ты помощник в Telegram. Отвечай коротко, по-русски, без вступлений."

bot = Bot(os.environ["TG_TOKEN"], default=DefaultBotProperties(parse_mode=None))
dp = Dispatcher()
ai = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"], timeout=60.0, max_retries=2)

@dp.message(F.text)
async def on_text(m: Message):
    async with ChatActionSender(bot=bot, chat_id=m.chat.id, action=ChatAction.TYPING):
        r = await ai.chat.completions.create(
            model=os.environ["MODEL"],
            messages=[{"role": "system", "content": SYSTEM},
                      {"role": "user", "content": m.text}],
            max_tokens=800,
        )
    await m.answer(r.choices[0].message.content)

async def main():
    await dp.start_polling(bot)

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    asyncio.run(main())

Три детали, на которых спотыкаются. В aiogram 3.7+ parse_mode задаётся через DefaultBotProperties, старый Bot(parse_mode=...) бросает TypeError. ChatActionSender нужен потому, что индикатор «печатает…» живёт ровно 5 секунд, а модель думает дольше — без него он гаснет, и пользователь решает, что бот умер. timeout=60.0 ставим явно, иначе зависший запрос держит хендлер очень долго.

Запускаем вручную, чтобы увидеть трейсбек: set -a; . /etc/aibot.env; set +a, затем sudo -u aibot -E /home/aibot/venv/bin/python /home/aibot/bot.py.

Грабля при смене модели: у reasoning-моделей серии o параметр зовётся max_completion_tokens, а temperature они не принимают вовсе — ошибка 400 Unsupported parameter: 'max_tokens' означает ровно это. Список доступных ключу моделей честнее документации: curl -s https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY" | grep -o '"id": "[^"]*"'.

Память диалога и сколько это стоит в деньгах

Бот выше отвечает с чистого листа — «а подробнее?» он не поймёт. Историю держим в уже установленном Redis, в /etc/redis/redis.conf хватит трёх строк:

bind 127.0.0.1
maxmemory 128mb
maxmemory-policy allkeys-lru

После systemctl restart redis-server держим диалог списком: LPUSH chat:{user_id}, затем LTRIM chat:{user_id} 0 19 (последние 20 реплик) и EXPIRE chat:{user_id} 86400.

Теперь про деньги — главный сюрприз AI-ботов. Каждый запрос отправляет всю историю заново, поэтому входные токены растут лавиной. Считаем по ставкам gpt-4o-mini (0,15 $ за миллион входных и 0,60 $ за миллион выходных):

Что отправляемВходных токеновЦена ответа10 000 ответов
Только вопрос, без истории~250$0,00028

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

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

,8
10 последних реплик~1600$0,00048$4,8
50 реплик, без обрезки~7000$0,00129

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

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

Развернуть бот-стек

Минимальный AI-бот: сорок строк, которые уже работают

Кладём в /home/aibot/bot.py:

import asyncio, logging, os
from aiogram import Bot, Dispatcher, F
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ChatAction
from aiogram.types import Message
from aiogram.utils.chat_action import ChatActionSender
from openai import AsyncOpenAI

SYSTEM = "Ты помощник в Telegram. Отвечай коротко, по-русски, без вступлений."

bot = Bot(os.environ["TG_TOKEN"], default=DefaultBotProperties(parse_mode=None))
dp = Dispatcher()
ai = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"], timeout=60.0, max_retries=2)

@dp.message(F.text)
async def on_text(m: Message):
    async with ChatActionSender(bot=bot, chat_id=m.chat.id, action=ChatAction.TYPING):
        r = await ai.chat.completions.create(
            model=os.environ["MODEL"],
            messages=[{"role": "system", "content": SYSTEM},
                      {"role": "user", "content": m.text}],
            max_tokens=800,
        )
    await m.answer(r.choices[0].message.content)

async def main():
    await dp.start_polling(bot)

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    asyncio.run(main())

Три детали, на которых спотыкаются. В aiogram 3.7+ parse_mode задаётся через DefaultBotProperties, старый Bot(parse_mode=...) бросает TypeError. ChatActionSender нужен потому, что индикатор «печатает…» живёт ровно 5 секунд, а модель думает дольше — без него он гаснет, и пользователь решает, что бот умер. timeout=60.0 ставим явно, иначе зависший запрос держит хендлер очень долго.

Запускаем вручную, чтобы увидеть трейсбек: set -a; . /etc/aibot.env; set +a, затем sudo -u aibot -E /home/aibot/venv/bin/python /home/aibot/bot.py.

Грабля при смене модели: у reasoning-моделей серии o параметр зовётся max_completion_tokens, а temperature они не принимают вовсе — ошибка 400 Unsupported parameter: 'max_tokens' означает ровно это. Список доступных ключу моделей честнее документации: curl -s https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY" | grep -o '"id": "[^"]*"'.

Память диалога и сколько это стоит в деньгах

Бот выше отвечает с чистого листа — «а подробнее?» он не поймёт. Историю держим в уже установленном Redis, в /etc/redis/redis.conf хватит трёх строк:

bind 127.0.0.1
maxmemory 128mb
maxmemory-policy allkeys-lru

После systemctl restart redis-server держим диалог списком: LPUSH chat:{user_id}, затем LTRIM chat:{user_id} 0 19 (последние 20 реплик) и EXPIRE chat:{user_id} 86400.

Теперь про деньги — главный сюрприз AI-ботов. Каждый запрос отправляет всю историю заново, поэтому входные токены растут лавиной. Считаем по ставкам gpt-4o-mini (0,15 $ за миллион входных и 0,60 $ за миллион выходных):

Что отправляемВходных токеновЦена ответа10 000 ответов
Только вопрос, без истории~250$0,00028$2,8
10 последних реплик~1600$0,00048$4,8
50 реплик, без обрезки~7000$0,00129$12,9

Выход везде — 400 токенов. Разница между первой и третьей строкой почти пятикратная, а качество впятеро не растёт. Обрезайте историю не по числу сообщений, а по токенам: enc = tiktoken.get_encoding("o200k_base") и len(enc.encode(text)). Именно get_encoding, а не encoding_for_model — последний не знает новых имён моделей и падает с KeyError. Бюджет контекста держите в 2000–3000 токенов.

Стриминг ответа и лимиты Telegram, о которые все спотыкаются

Ждать 15 секунд молча никто не будет, поэтому ответ печатают по мере генерации: stream=True и правка одного сообщения. Здесь три подводных камня.

Частота правок. Telegram даёт примерно одно сообщение в секунду на чат. Правка на каждом чанке приносит TelegramRetryAfter с полем retry_after, а при упорстве — временную блокировку бота. Рабочий интервал между editMessageText — 1,2 секунды.

Пустая правка. Если текст между двумя правками не изменился, прилетит Bad Request: message is not modified. Это нормальный исход гонки, гасить его надо точечно, а не глушить весь except:

try:
    await bot.edit_message_text(buf[:4000], chat_id=m.chat.id, message_id=sent.message_id)
except TelegramBadRequest as e:
    if "message is not modified" not in str(e):
        raise

Длина. Предел сообщения — 4096, но считается он не в питоновских символах, а в кодовых единицах UTF-16: эмодзи весят по две. Ответ с len(text) == 4050 спокойно улетает в Bad Request: message is too long. Отсюда порог 4000 и нарезка по строкам, чтобы не рвать блоки кода.

Отдельно разметка. Модель охотно возвращает markdown, и при parse_mode="Markdown" одна незакрытая звёздочка даёт:

Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 217

Универсального лечения нет: либо конвертируйте вывод в MarkdownV2 с экранированием всех _*[]()~>#+-=|{}.!, либо — проще — держите parse_mode=None. Поэтому он и стоит в примере выше.

Чтобы бот не разорил вас за одну ночь

Telegram-бот публичен: имя вида @something_ai_bot находится поиском, и через сутки в нём окажутся незнакомые люди — то есть прямой расход баланса. Минимум защиты выглядит так.

  • Белый список или квота. Личному боту хватает проверки if m.from_user.id not in ALLOWED: return. Публичному нужен счётчик в Redis: INCRBY quota:{uid}:{YYYY-MM-DD} {tokens} с EXPIRE 172800 и отказ при превышении лимита.
  • Жёсткий потолок в OpenAI. В настройках проекта задаётся месячный budget limit — единственная защита, которая работает, даже когда код упал или ключ утёк.
  • Гигиена ключа. Секреты только в /etc/aibot.env с правами 600, .env в .gitignore, никаких токенов в логах. Утёкшие sk-proj- выгребают ботами с публичных репозиториев за минуты, так что при подозрении отзывайте ключ сразу.
  • Автозапуск. В юните /etc/systemd/system/aibot.serviceUser=aibot, EnvironmentFile=/etc/aibot.env, Restart=always, RestartSec=5 и обязательно TimeoutStopSec=30, чтобы запросы к OpenAI доигрывали при рестарте.
  • Логи. Болтливый бот пишет сотни мегабайт в journald: поставьте SystemMaxUse=200M в /etc/systemd/journald.conf.

Два риска настройками не закрываются. Диалоги проходят через OpenAI — для чувствительных данных это неприемлемо, там нужна локальная модель. И тарифные лимиты аккаунта на первом уровне невысоки: при всплеске прилетит 429 с телом Rate limit reached ... on requests per min (RPM): Limit 500, Used 500, обрабатывайте его отдельно от insufficient_quota и ставьте экспоненциальный бэкофф.

Какой сервер брать в MAATRIX под AI-бота в телеграме

Вся тяжёлая работа идёт на стороне OpenAI, ваш сервер только пересылает текст. По замерам живого бота на polling с Redis: Python с aiogram занимает 95–120 МБ RSS, Redis в простое — около 12 МБ, CPU между запросами не нагружен.

ВариантКонфигурацияДля чего хватает
Минимум1 vCPU, 1 ГБ RAM, 10 ГБ NVMeБот на long polling, Redis 128 МБ, до нескольких сотен пользователей
Комфортно2 vCPU, 2 ГБ RAM, 20–30 ГБ NVMeВебхук с nginx и Let's Encrypt, голосовые через ffmpeg, PostgreSQL под историю
С запасом4 vCPU, 8 ГБ RAM, 60 ГБ NVMeНесколько ботов, RAG с векторной базой, очередь задач, обработка файлов

На минимуме реально жить, но своп на 1 ГБ подключите сразу — он спасает от OOM при обновлении зависимостей. Сомневаетесь — берите второй вариант: разница в цене меньше, чем вечер разбора Killed в dmesg. Подробнее про память — в разборе сколько ресурсов нужно VPS под Telegram-ботов. GPU не берите: через API он простаивает.

По локации берите UK (Лондон): ключ OpenAI работает без прослоек, до Telegram в Амстердаме 7–9 мс, до пользователей в ЕС низкий пинг, площадка в GDPR-периметре. FR (Франция) по параметрам совпадает с Лондоном, US (Нью-Йорк) предпочтительнее, если бот вдобавок ходит в американские сервисы с требованием чистого US-IP. RU напрямую не годится — оттуда прилетит тот самый 403 и понадобится прокси-звено, о нём отдельно в статье про API-прокси к OpenAI из России. Сравнение площадок — в материале про VPS в Великобритании для Telegram-бота.

Оплата сервера — картой российского банка, по СБП, криптой или токеном MAAT; зарубежная карта не нужна. Машина разворачивается за минуты, так что от заказа до первого ответа бота — около часа вместе с кодом.

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

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

Развернуть бот-стек
2,9

Выход везде — 400 токенов. Разница между первой и третьей строкой почти пятикратная, а качество впятеро не растёт. Обрезайте историю не по числу сообщений, а по токенам: enc = tiktoken.get_encoding("o200k_base") и len(enc.encode(text)). Именно get_encoding, а не encoding_for_model — последний не знает новых имён моделей и падает с KeyError. Бюджет контекста держите в 2000–3000 токенов.

Стриминг ответа и лимиты Telegram, о которые все спотыкаются

Ждать 15 секунд молча никто не будет, поэтому ответ печатают по мере генерации: stream=True и правка одного сообщения. Здесь три подводных камня.

Частота правок. Telegram даёт примерно одно сообщение в секунду на чат. Правка на каждом чанке приносит TelegramRetryAfter с полем retry_after, а при упорстве — временную блокировку бота. Рабочий интервал между editMessageText — 1,2 секунды.

Пустая правка. Если текст между двумя правками не изменился, прилетит Bad Request: message is not modified. Это нормальный исход гонки, гасить его надо точечно, а не глушить весь except:

try:
    await bot.edit_message_text(buf[:4000], chat_id=m.chat.id, message_id=sent.message_id)
except TelegramBadRequest as e:
    if "message is not modified" not in str(e):
        raise

Длина. Предел сообщения — 4096, но считается он не в питоновских символах, а в кодовых единицах UTF-16: эмодзи весят по две. Ответ с len(text) == 4050 спокойно улетает в Bad Request: message is too long. Отсюда порог 4000 и нарезка по строкам, чтобы не рвать блоки кода.

Отдельно разметка. Модель охотно возвращает markdown, и при parse_mode="Markdown" одна незакрытая звёздочка даёт:

Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 217

Универсального лечения нет: либо конвертируйте вывод в MarkdownV2 с экранированием всех _*[]()~>#+-=|{}.!, либо — проще — держите parse_mode=None. Поэтому он и стоит в примере выше.

Чтобы бот не разорил вас за одну ночь

Telegram-бот публичен: имя вида @something_ai_bot находится поиском, и через сутки в нём окажутся незнакомые люди — то есть прямой расход баланса. Минимум защиты выглядит так.

  • Белый список или квота. Личному боту хватает проверки if m.from_user.id not in ALLOWED: return. Публичному нужен счётчик в Redis: INCRBY quota:{uid}:{YYYY-MM-DD} {tokens} с EXPIRE 172800 и отказ при превышении лимита.
  • Жёсткий потолок в OpenAI. В настройках проекта задаётся месячный budget limit — единственная защита, которая работает, даже когда код упал или ключ утёк.
  • Гигиена ключа. Секреты только в /etc/aibot.env с правами 600, .env в .gitignore, никаких токенов в логах. Утёкшие sk-proj- выгребают ботами с публичных репозиториев за минуты, так что при подозрении отзывайте ключ сразу.
  • Автозапуск. В юните /etc/systemd/system/aibot.serviceUser=aibot, EnvironmentFile=/etc/aibot.env, Restart=always, RestartSec=5 и обязательно TimeoutStopSec=30, чтобы запросы к OpenAI доигрывали при рестарте.
  • Логи. Болтливый бот пишет сотни мегабайт в journald: поставьте SystemMaxUse=200M в /etc/systemd/journald.conf.

Два риска настройками не закрываются. Диалоги проходят через OpenAI — для чувствительных данных это неприемлемо, там нужна локальная модель. И тарифные лимиты аккаунта на первом уровне невысоки: при всплеске прилетит 429 с телом Rate limit reached ... on requests per min (RPM): Limit 500, Used 500, обрабатывайте его отдельно от insufficient_quota и ставьте экспоненциальный бэкофф.

Какой сервер брать в MAATRIX под AI-бота в телеграме

Вся тяжёлая работа идёт на стороне OpenAI, ваш сервер только пересылает текст. По замерам живого бота на polling с Redis: Python с aiogram занимает 95–120 МБ RSS, Redis в простое — около 12 МБ, CPU между запросами не нагружен.

ВариантКонфигурацияДля чего хватает
Минимум1 vCPU, 1 ГБ RAM, 10 ГБ NVMeБот на long polling, Redis 128 МБ, до нескольких сотен пользователей
Комфортно2 vCPU, 2 ГБ RAM, 20–30 ГБ NVMeВебхук с nginx и Let's Encrypt, голосовые через ffmpeg, PostgreSQL под историю
С запасом4 vCPU, 8 ГБ RAM, 60 ГБ NVMeНесколько ботов, RAG с векторной базой, очередь задач, обработка файлов

На минимуме реально жить, но своп на 1 ГБ подключите сразу — он спасает от OOM при обновлении зависимостей. Сомневаетесь — берите второй вариант: разница в цене меньше, чем вечер разбора Killed в dmesg. Подробнее про память — в разборе сколько ресурсов нужно VPS под Telegram-ботов. GPU не берите: через API он простаивает.

По локации берите UK (Лондон): ключ OpenAI работает без прослоек, до Telegram в Амстердаме 7–9 мс, до пользователей в ЕС низкий пинг, площадка в GDPR-периметре. FR (Франция) по параметрам совпадает с Лондоном, US (Нью-Йорк) предпочтительнее, если бот вдобавок ходит в американские сервисы с требованием чистого US-IP. RU напрямую не годится — оттуда прилетит тот самый 403 и понадобится прокси-звено, о нём отдельно в статье про API-прокси к OpenAI из России. Сравнение площадок — в материале про VPS в Великобритании для Telegram-бота.

Оплата сервера — картой российского банка, по СБП, криптой или токеном MAAT; зарубежная карта не нужна. Машина разворачивается за минуты, так что от заказа до первого ответа бота — около часа вместе с кодом.

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

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

Развернуть бот-стек

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

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

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

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

Можно ли обойтись российским VPS и прокси?

Работает, но у вас два процесса вместо одного, лишние 30–80 мс на запрос и вторая точка отказа: при падении прокси бот молча перестаёт отвечать.

Сколько стоит содержать такого бота?

Сервер начального уровня плюс токены: для бота на 20–50 ответов в день расход на API — единицы долларов в месяц. Риск не в трафике, а в отсутствии лимитов: публичный бот без квот съедает месячный бюджет за ночь.

Polling или вебхук для AI-бота?

Начинайте с polling: не нужны домен, сертификат и открытые порты, а пропускную способность всё равно ограничивает модель. На вебхук переходите, когда одновременных диалогов станет много.

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

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