Свой ChatGPT бот в Telegram в 2026 с генерацией изображений, зрением и веб-поиском

Два года назад я написал статью о том, как сделать ChatGPT-бота в Telegram через ProxyAPI. С тех пор многое изменилось: появились новые модели, новые возможности, а сам ProxyAPI обзавёлся универсальным OpenAI-совместимым API.

Пришло время обновить бота. В этой статье я покажу, как создать Telegram-бота, который умеет:

  • Общаться с любой AI-моделью — OpenAI, Anthropic, Google, DeepSeek и сотни других

  • Переключаться между моделями на лету

  • Анализировать изображения (vision)

  • Генерировать изображения по текстовому описанию

  • Искать в интернете актуальную информацию

  • Запоминать контекст диалога

И всё это — через единый API-endpoint, без VPN, с оплатой в рублях.

Что такое ProxyAPI

ProxyAPI — это сервис, который даёт доступ к API мировых лидеров в области AI (OpenAI, Anthropic, Google, DeepSeek) из России без VPN и блокировок. Оплата в рублях, не нужен иностранный телефон или карта.

Ключевая фишка, которую я использую в этой статье — OpenAI-совместимый API. Это универсальный шлюз, который принимает запросы в формате OpenAI и автоматически маршрутизирует их к нужному провайдеру. На входе вы работаете с единым протоколом, а конвертация форматов выполняется на стороне ProxyAPI.

Базовый адрес:

https://openai.api.proxyapi.ru/v1

Модели адресуются в формате провайдер/модель:

  • openai/gpt-5.2

  • anthropic/claude-sonnet-4-5

  • gemini/gemini-2.5-flash

  • openrouter/deepseek/deepseek-v3.2

Это значит, что один и тот же код работает с любой моделью — достаточно изменить строку с именем модели.

Подготовка

Для реализации проекта понадобится:

1. API-ключ ProxyAPI

Регистрируемся на ProxyAPI, идём в раздел Ключи API и создаём ключ.

Сохраните ключ при создании! В полном виде вы его больше не увидите.

2. Telegram-бот

Создаём бота через @BotFather в Telegram:

  1. Отправляем /newbot

  2. Даём имя и username

  3. Сохраняем токен

3. Аккаунт в Яндекс Облаке

Если аккаунта ещё нет — создайте его на cloud.yandex.ru. Убедитесь, что подключён платёжный аккаунт в статусе ACTIVE или TRIAL_ACTIVE.

Все ресурсы, которые будут использоваться, имеют ежемесячный бесплатный лимит. При личном использовании бота вся инфраструктура обойдётся бесплатно.

4. Облачные ресурсы

Сервисный аккаунт

В консоли Яндекс Облака переходим в Сервисные аккаунты и создаём новый. Присваиваем роли:

  • serverless.functions.invoker

  • storage.uploader

  • storage.viewer

После создания переходим в аккаунт и создаём статический ключ доступа. Сохраняем Key ID и Secret.

Бакет (Object Storage)

Переходим в Object Storage и создаём новый бакет. Настройки по умолчанию.

В этом бакете будет храниться история чатов и настройки пользователей.

Код бота

Бот состоит из трёх файлов:

requirements.txt

openai
aiogram
aiohttp
boto3
  • openai — официальный SDK OpenAI, работает с ProxyAPI благодаря совместимому API

  • aiogram — современный асинхронный фреймворк для Telegram-ботов

  • boto3 — AWS SDK для работы с Yandex Object Storage (S3-совместимое хранилище)

  • aiohttp — для скачивания сгенерированных изображений

bot.py — основная логика

Это главный файл бота. Разберём его по частям.

Инициализация

import os
import json
import asyncio
import logging
import base64
import re

import aiohttp
import boto3
from aiogram import Bot, Dispatcher, types, F
from aiogram.filters import Command, CommandObject
from aiogram.enums import ParseMode, ChatAction
from aiogram.types import BufferedInputFile
from openai import AsyncOpenAI

logging.basicConfig(level=logging.INFO)

TG_BOT_TOKEN = os.getenv("TG_BOT_TOKEN")
PROXYAPI_KEY = os.getenv("PROXYAPI_KEY")
YANDEX_KEY_ID = os.getenv("YANDEX_KEY_ID")
YANDEX_KEY_SECRET = os.getenv("YANDEX_KEY_SECRET")
YANDEX_BUCKET = os.getenv("YANDEX_BUCKET")

PROXYAPI_BASE_URL = "https://openai.api.proxyapi.ru/v1"
DEFAULT_MODEL = "anthropic/claude-sonnet-4-5"
IMAGE_MODEL = "openai/gpt-image-1.5"
MAX_HISTORY_CHARS = 50_000

Здесь используется AsyncOpenAI — асинхронный клиент OpenAI SDK. Благодаря OpenAI-совместимому API ProxyAPI достаточно указать base_url, и SDK будет работать с любой моделью через ProxyAPI.

bot = Bot(token=TG_BOT_TOKEN)
dp = Dispatcher()

client = AsyncOpenAI(
api_key=PROXYAPI_KEY,
base_url=PROXYAPI_BASE_URL,
)

Список популярных моделей для быстрого переключения через inline-клавиатуру:

MODEL_SHORTCUTS = [
("Claude Sonnet 4.5", "anthropic/claude-sonnet-4-5"),
("GPT-5.2", "openai/gpt-5.2"),
("Gemini 2.5 Flash", "gemini/gemini-2.5-flash"),
("DeepSeek V3.2", "openrouter/deepseek/deepseek-v3.2"),
]

Это лишь шорткаты — пользователь может указать любую модель вручную.

Хранилище данных (S3)

Для хранения истории чатов и настроек используем Yandex Object Storage через AWS SDK:

def get_s3_client():
session = boto3.session.Session(
aws_access_key_id=YANDEX_KEY_ID,
aws_secret_access_key=YANDEX_KEY_SECRET,
)
return session.client(
service_name="s3",
endpoint_url="https://storage.yandexcloud.net",
)

def load_user_data(user_id: int) -> dict:
try:
s3 = get_s3_client()
obj = s3.get_object(Bucket=YANDEX_BUCKET, Key=f"{user_id}.json")
return json.loads(obj["Body"].read())
except Exception:
return {"model": DEFAULT_MODEL, "history": []}

def save_user_data(user_id: int, data: dict):
s3 = get_s3_client()
s3.put_object(
Bucket=YANDEX_BUCKET,
Key=f"{user_id}.json",
Body=json.dumps(data, ensure_ascii=False),
)

Для каждого пользователя создаётся JSON-файл в бакете с его историей и выбранной моделью:

{
"model": "anthropic/claude-sonnet-4-5",
"history": [
{"role": "user", "content": "Привет!"},
{"role": "assistant", "content": "Здравствуйте!"}
]
}

Вспомогательные функции

Индикатор «печатает...» — Telegram показывает его всего ~5 секунд, поэтому отправляем повторно каждые 4 секунды через фоновую async-задачу:

async def keep_typing(chat_id: int):
while True:
await bot.send_chat_action(chat_id, ChatAction.TYPING)
await asyncio.sleep(4)

Разбивка длинных ответов — Telegram ограничивает сообщения 4096 символами:

MAX_MESSAGE_LENGTH = 4096

async def send_long_message(message: types.Message, text: str):
for i in range(0, len(text), MAX_MESSAGE_LENGTH):
chunk = text[i:i + MAX_MESSAGE_LENGTH]
try:
await message.answer(chunk, parse_mode=ParseMode.MARKDOWN)
except Exception:
await message.answer(chunk)

Ответ отправляется с Markdown-форматированием. Если Telegram не может его распарсить (бывает, что модели генерируют невалидный Markdown), отправляем как plain text.

Команды бота

@dp.message(Command("start"))
async def cmd_start(message: types.Message):
await message.answer(
f"Привет! {HELP_TEXT}",
parse_mode=ParseMode.HTML,
)

@dp.message(Command("new"))
async def cmd_new(message: types.Message):
user_data = load_user_data(message.from_user.id)
user_data["history"] = []
save_user_data(message.from_user.id, user_data)
await message.answer("История чата очищена.")

@dp.message(Command("model"))
async def cmd_model(message: types.Message):
user_data = load_user_data(message.from_user.id)
model = user_data.get("model", DEFAULT_MODEL)
await message.answer(f"Текущая модель: <code>{model}</code>", parse_mode=ParseMode.HTML)

Переключение моделей

Команда /setmodel работает двумя способами:

  • /setmodel — показывает inline-клавиатуру с популярными моделями

  • /setmodel anthropic/claude-sonnet-4-5 — устанавливает произвольную модель напрямую

@dp.message(Command("setmodel"))
async def cmd_setmodel(message: types.Message, command: CommandObject):
if command.args:
user_data = load_user_data(message.from_user.id)
user_data["model"] = command.args.strip()
save_user_data(message.from_user.id, user_data)
await message.answer(
f"Модель изменена на: <code>{command.args.strip()}</code>",
parse_mode=ParseMode.HTML,
)
return

keyboard = []
for name, model_id in MODEL_SHORTCUTS:
keyboard.append([types.InlineKeyboardButton(
text=name, callback_data=f"setmodel:{model_id}"
)])
markup = types.InlineKeyboardMarkup(inline_keyboard=keyboard)
await message.answer(
"Выберите модель или отправьте /setmodel <id>:",
reply_markup=markup, parse_mode=ParseMode.HTML,
)

@dp.callback_query(F.data.startswith("setmodel:"))
async def callback_setmodel(callback: types.CallbackQuery):
model_id = callback.data.split(":", 1)[1]
user_data = load_user_data(callback.from_user.id)
user_data["model"] = model_id
save_user_data(callback.from_user.id, user_data)
await callback.message.edit_text(
f"Модель изменена на: <code>{model_id}</code>",
parse_mode=ParseMode.HTML,
)
await callback.answer()

Никаких ограничений на выбор модели нет — если ProxyAPI поддерживает модель, она будет работать. Inline-клавиатура — лишь шорткаты для удобства.

Генерация изображений

@dp.message(Command("image"))
async def cmd_image(message: types.Message, command: CommandObject):
prompt = command.args
if not prompt:
await message.answer("Укажите промпт: /image <описание>",
parse_mode=ParseMode.HTML)
return

typing_task = asyncio.create_task(keep_typing(message.chat.id))

try:
response = await client.images.generate(
model=IMAGE_MODEL,
prompt=prompt,
n=1,
size="1024x1024",
)
image_url = response.data[0].url

if image_url:
async with aiohttp.ClientSession() as session:
async with session.get(image_url) as resp:
image_bytes = await resp.read()
photo = BufferedInputFile(image_bytes, filename="generated.png")
await message.answer_photo(photo)
elif response.data[0].b64_json:
image_bytes = base64.b64decode(response.data[0].b64_json)
photo = BufferedInputFile(image_bytes, filename="generated.png")
await message.answer_photo(photo)
else:
await message.answer("Не удалось получить изображение.")
except Exception as e:
await message.answer(f"Ошибка генерации: {e}")
finally:
typing_task.cancel()

Генерация работает через endpoint /v1/images/generations OpenAI-совместимого API. API может вернуть изображение либо как URL, либо как base64 — обрабатываем оба варианта.

Главная функция — отправка сообщения в AI

Это ядро бота. Здесь используется Responses API вместо привычного Chat Completions — потому что именно Responses API поддерживает встроенные инструменты, такие как веб-поиск:

async def send_to_ai(user_id: int, content, message: types.Message):
user_data = load_user_data(user_id)
model = user_data.get("model", DEFAULT_MODEL)
history = user_data.get("history", [])

if history_chars(history) > MAX_HISTORY_CHARS:
await message.answer(
"История чата слишком длинная. "
"Отправьте /new чтобы очистить и начать заново."
)
return

history.append({"role": "user", "content": content})

typing_task = asyncio.create_task(keep_typing(message.chat.id))

try:
response = await client.responses.create(
model=model,
input=history,
tools=[{"type": "web_search"}],
)
ai_text = response.output_text
history.append({"role": "assistant", "content": ai_text})
except Exception as e:
ai_text = f"Ошибка: {e}"
history.pop()
finally:
typing_task.cancel()

user_data["history"] = history
save_user_data(user_id, user_data)

await send_long_message(message, ai_text)

Обратите внимание на tools=[{"type": "web_search"}]. Это даёт модели возможность искать актуальную информацию в интернете. Модель сама решает, когда нужен поиск — для обычных вопросов она отвечает из своих знаний, а для вопросов о текущих событиях, погоде и т.д. автоматически выполнит поиск.

Через OpenAI-совместимый API ProxyAPI веб-поиск работает с моделями OpenAI, Anthropic и Google. Для моделей через OpenRouter поиск будет проигнорирован без ошибки.

Обработка фотографий (Vision)

@dp.message(F.photo)
async def handle_photo(message: types.Message):
photo = message.photo[-1]
file = await bot.get_file(photo.file_id)
file_bytes = await bot.download_file(file.file_path)
image_b64 = base64.b64encode(file_bytes.read()).decode("utf-8")

caption = message.caption or "Что изображено на этой картинке?"

content = [
{"type": "input_text", "text": caption},
{"type": "input_image", "image_url": f"dаta:image/jpeg;base64,{image_b64}"},
]

await send_to_ai(message.from_user.id, content, message)

Когда пользователь отправляет фото, бот скачивает его, кодирует в base64 и отправляет модели. Если к фото есть подпись — она используется как промпт. Если нет — бот попросит модель описать, что на картинке.

Важно: изображения сохраняются в истории чата в формате base64. Это значит, что при каждом последующем запросе они отправляются повторно, что увеличивает стоимость. Если для вас это критично, можно после получения ответа заменять vision-сообщение на текстовое с подписью.

Обработка текстовых сообщений

@dp.message(F.text & ~F.text.startswith("/"))
async def handle_text(message: types.Message):
await send_to_ai(message.from_user.id, message.text, message)

@dp.message(Command(re.compile(r".*")))
async def handle_unknown_command(message: types.Message):
await message.answer("Неизвестная команда. Отправьте /help для списка команд.")

index.py — точка входа для Cloud Function

import json
import asyncio
from aiogram.types import Update
from bot import bot, dp

loop = asyncio.new_event_loop()

async def process(event):
body = json.loads(event["body"])
update = Update(**body)
await dp.feed_update(bot, update)

def handler(event, context):
loop.run_until_complete(process(event))
return {"statusCode": 200, "body": "ok"}

Этот файл — адаптер между Yandex Cloud Functions и aiogram. Облачная функция получает HTTP-запрос от Telegram через API Gateway, парсит его в объект Update и передаёт диспетчеру aiogram для обработки.

Нюанс с event loop: здесь asyncio.new_event_loop() создаётся на уровне модуля, а не через asyncio.run(). Это важно, потому что Yandex Cloud Functions переиспользует процесс между вызовами. asyncio.run() закрывает event loop после завершения, и при следующем вызове aiohttp-сессия внутри aiogram окажется привязана к закрытому loop. Создание loop на уровне модуля решает эту проблему.

Деплой на Yandex Cloud

Облачная функция

Создаём новую функцию в разделе Cloud Functions:

Выбираем среду выполнения Python 3.14:

Здесь доступен весь код этого проекта. Можно клонировать его локально, собрать ZIP-архив и загрузить его в облачную функцию. Либо вручную создать одноименные файлы в редакторе облачной функции c таким же содержимым, как в репозитории:

  • bot.py

  • index.py

  • requirements.txt

Приведу пример с клонированием и ZIP-архивом:

git clone https://gitlab.com/evrovas/chatgpt-telegram-bot-proxyapi-2026.git
cd chatgpt-telegram-bot-proxyapi-2026
zip -r function.zip bot.py index.py requirements.txt

Настраиваем параметры:

  • Точка входа: index.handler

  • Таймаут: 3 минуты (генерация изображений и веб-поиск могут быть долгими)

  • Память: 256 МБ

  • Сервисный аккаунт: выбираем созданный ранее

  • Переменные окружения: TG_BOT_TOKEN, PROXYAPI_KEY, YANDEX_KEY_ID, YANDEX_KEY_SECRET, YANDEX_BUCKET

После создание функции надо перейти в раздел Обзор и скопировать оттуда идентификатор функции. Он понадобится нам в следующем шаге.

API-шлюз

Создаём API Gateway со следующей спецификацией:

openapi: 3.0.0
info:
title: Telegram Bot API
version: 1.0.0
paths:
/:
post:
x-yc-apigateway-integration:
type: cloud-functions
function_id: <ID_ФУНКЦИИ>
service_account_id: <ID_СЕРВИСНОГО_АККАУНТА>

После сохранения на странице появится Служебный домен - копируем его для следующего шага.

Настройка Webhook

Последний шаг — сообщить Telegram, куда пересылать сообщения:

curl -X POST "https://api.telegram.org/bot<ТОКЕН>/setWebhook" \
-H "Content-Type: application/json" \
-d '{"url": "<ДОМЕН_API_ШЛЮЗА>"}'

Должны получить ответ:

{"ok": true, "result": true, "description": "Webhook was set"}

Тест

Всё готово! Проверяем работу бота:

Текстовый чат, переключение моделей и веб-поиск

Бот запоминает контекст, позволяет переключать модели и автоматически ищет актуальную информацию в интернете, когда это нужно.

Генерация изображений

Команда /image кот в сапогах сгенерировала изображение через модель openai/gpt-image-1.5.

Анализ изображений (Vision)

Отправляем фото — бот описывает, что на нём изображено.

Стоимость ресурсов

Яндекс Облако

Бесплатные лимиты за каждый месяц:

  • Cloud Functions: 1 млн вызовов, 10 ГБ/ч использования памяти

  • Object Storage: 1 ГБ хранения, 10 000 PUT/POST, 100 000 GET

  • API Gateway: 100 000 запросов

При личном использовании бота в эти ограничения можно укладываться с большим запасом.

ProxyAPI

Стоимость зависит от выбранной модели и объёма использования. Актуальные цены — на странице тарифов ProxyAPI.

Итог

Получился полноценный AI-бот в Telegram, который за два года эволюции ушёл далеко от простого ChatGPT-прокси:

  • Переключение между моделями OpenAI, Anthropic, Google и сотнями моделей через OpenRouter — одной командой

  • Анализ изображений, генерация картинок, автоматический веб-поиск

  • Всё через один API-endpoint — не нужно разбираться в форматах каждого провайдера отдельно

  • Бесплатная serverless-инфраструктура на Yandex Cloud

Для меня ProxyAPI оказался самым удобным способом быстро подключить сразу все модели AI без возни с VPN, иностранными картами и отдельными аккаунтами у каждого провайдера.

Весь код — на GitLab.


Внимание!

Официальный сайт бота по ссылке ниже.

Официальный сайт