Telegram-бот без контексту — марний. LLM не пам'ятають діалог, кожен запит ізольований. Ми вирішуємо це через FSM на Redis та автоматичну компресію історії. Результат: бот, який пам'ятає вас, обробляє голос та картинки, а ви контролюєте бюджет. Наш досвід — 5+ років в AI/ML та більше 30 успішних проєктів.
Які проблеми вирішує Telegram AI-бот
Контекст діалогу. LLM не пам'ятають історію — кожне повідомлення обробляється ізольовано. Ми зберігаємо діалог в Redis через aiogram FSM. Обмежуємо історію останніми 20 повідомленнями (налаштовується). Для довгих сесій автоматично компресуємо контекст за допомогою summarization — стискаємо старі повідомлення в одне резюме.
Мультимодальний ввод. Telegram підтримує текст, голос та фото. Голос транскрибуємо через Whisper (через Groq — затримка <1 сек). Зображення обробляємо через vision API Claude або GPT-4o. Користувачеві не потрібно перемикатися між інструментами — все в одному чаті.
Rate limiting та бюджет. Кожен запит до LLM коштує грошей (токени). Зловмисник може спамити та обнулити рахунок. Ми ставимо обмеження: не більше 10 запитів за хвилину на користувача. При перевищенні бот пише: «⏳ Занадто багато запитів. Зачекайте хвилину.» Додатково — daily cap на токени з повідомленням адміністратора.
Як ми це робимо: стек та код
Стек: aiogram 3.x (асинхронний фреймворк), Redis (FSM та кеш), Anthropic SDK (Claude), Groq SDK (Whisper), Anthropic Vision API. Конфігурація через змінні середовища, деплой в Docker.
Базовий AI-бот з aiogram 3.x
import asyncio from aiogram import Bot, Dispatcher, Router, types, F from aiogram.filters import CommandStart, Command from aiogram.fsm.context import FSMContext from aiogram.fsm.storage.redis import RedisStorage from anthropic import AsyncAnthropic BOT_TOKEN = "BOT_TOKEN" ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY" bot = Bot(token=BOT_TOKEN) storage = RedisStorage.from_url("redis://localhost:6379") dp = Dispatcher(storage=storage) router = Router() dp.include_router(router) thropic_client = AsyncAnthropic(api_key=ANTHROPIC_API_KEY) # Історія діалогу — зберігається в FSM context @router.message(CommandStart()) async def start(message: types.Message, state: FSMContext): await state.update_data(history=[]) await message.answer( "Привет! Я AI-ассистент на базе Claude. Задай любой вопрос.", reply_markup=get_main_keyboard() ) def get_main_keyboard(): from aiogram.utils.keyboard import ReplyKeyboardBuilder builder = ReplyKeyboardBuilder() builder.button(text="🗑 Очистить историю") builder.button(text="ℹ️ О боте") builder.adjust(2) return builder.as_markup(resize_keyboard=True) @router.message(F.text == "🗑 Очистить историю") async def clear_history(message: types.Message, state: FSMContext): await state.update_data(history=[]) await message.answer("История диалога очищена.") @router.message(F.text & ~F.text.startswith("/")) async def handle_text(message: types.Message, state: FSMContext): data = await state.get_data() history = data.get("history", []) # Додаємо повідомлення користувача в історію history.append({"role": "user", "content": message.text}) # Показуємо "друкує..." await bot.send_chat_action(message.chat.id, "typing") # Streaming відповідь full_response = "" sent_message = await message.answer("...") async with anthropic_client.messages.stream( model="claude-haiku-4-5", max_tokens=2048, system="Ти — полезный ассистент. Отвечай кратко и по существу.", messages=history, ) as stream: async for text in stream.text_stream: full_response += text # Оновлюємо повідомлення кожні 50 символів if len(full_response) % 50 == 0: await sent_message.edit_text(full_response) await sent_message.edit_text(full_response) # Зберігаємо відповідь в історію history.append({"role": "assistant", "content": full_response}) # Обмежуємо історію — 20 повідомлень await state.update_data(history=history[-20:]) Обробка голосових повідомлень
import io from groq import AsyncGroq groq_client = AsyncGroq(api_key="GROQ_API_KEY") @router.message(F.voice) async def handle_voice(message: types.Message, state: FSMContext): await bot.send_chat_action(message.chat.id, "typing") # Завантажуємо голосове повідомлення voice = message.voice file = await bot.get_file(voice.file_id) file_bytes = await bot.download_file(file.file_path) # Транскрибуємо через Whisper на Groq (швидко) transcription = await groq_client.audio.transcriptions.create( file=("voice.ogg", file_bytes.read()), model="whisper-large-v3", language="ru", ) text = transcription.text await message.answer(f"📝 Розпізнано: {text}") # Обробляємо як текстове повідомлення await handle_text_with_content(message, state, text) Обробка зображень
import base64 @router.message(F.photo) async def handle_photo(message: types.Message, state: FSMContext): await bot.send_chat_action(message.chat.id, "typing") # Беремо фото найкращої якості photo = message.photo[-1] file = await bot.get_file(photo.file_id) file_bytes = await bot.download_file(file.file_path) image_data = base64.standard_b64encode(file_bytes.read()).decode() caption = message.caption or "Что на этом изображении?" response = await anthropic_client.messages.create( model="claude-haiku-4-5", max_tokens=1024, messages=[{ "role": "user", "content": [ {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": image_data}}, {"type": "text", "text": caption}, ] }] ) await message.answer(response.content[0].text) Rate limiting та billing захист
from aiogram.filters import BaseFilter from collections import defaultdict import time class RateLimitFilter(BaseFilter): def __init__(self, rate_limit: int = 10, period: int = 60): self.rate_limit = rate_limit self.period = period self.user_requests = defaultdict(list) async def __call__(self, message: types.Message) -> bool: user_id = message.from_user.id now = time.time() # Видаляємо старі запити self.user_requests[user_id] = [ t for t in self.user_requests[user_id] if now - t < self.period ] if len(self.user_requests[user_id]) >= self.rate_limit: await message.answer("⏳ Занадто багато запитів. Зачекайте хвилину.") return False self.user_requests[user_id].append(now) return True # Застосовуємо фільтр @router.message(RateLimitFilter(rate_limit=10), F.text) async def handle_with_limit(message: types.Message, state: FSMContext): await handle_text(message, state) Як забезпечити контекст діалогу в Telegram-боті?
Для постійного контексту ми використовуємо Redis Storage. Стан FSM зберігається між запитами. Історія діалогу — масив повідомлень з ролями user та assistant. Обмежуємо його останніми 20 повідомленнями, щоб не перевищити контекстне вікно моделі (у Claude Haiku — 200k токенів). Якщо діалог довший — автоматично компресуємо старі повідомлення в одне резюме через LLM.
Чому Redis, а не PostgreSQL?
Redis дає latency p99 < 10 мс, PostgreSQL — ~50 мс. Для FSM, де кожен запит перезаписує стан, швидкість критична. Крім того, Redis автоматично видаляє застарілі дані (TTL), що спрощує підтримку.
Процес роботи
| Етап | Тривалість | Результат |
|---|---|---|
| Аналіз вимог | 1 день | Технічне завдання, вибір LLM, архітектура |
| Прототип бота | 2-3 дні | MVP з текстовим діалогом |
| Інтеграція голосу/зображень | 2-3 дні | Мультимодальний ввод |
| Rate limiting, адмін-панель | 3-5 днів | Захист від зловживань, моніторинг |
| Деплой та тестування | 2-3 дні | Публікація, навантажувальне тестування |
Порівняння моделей LLM для Telegram-бота
| Модель | Швидкість (latency p50) | Якість відповідей | Вартість за 1M токенів |
|---|---|---|---|
| Claude Haiku | 300 мс | Хороша | $0.25 |
| GPT-4o | 800 мс | Відмінна | $5.00 |
| LLaMA 3 (local) | 150 мс | Середня | $0.02 (електроенергія) |
Claude Haiku в 2.5 рази швидше GPT-4o при порівнянній якості для типових завдань, що робить його оптимальним для чат-ботів.
Практичний кейс: клієнтська підтримка
З нашої практики: для SaaS-продукту розробили Telegram-бота на Claude Haiku. Результат: 73% звернень оброблялися без участі підтримки. Час відповіді: 24 години → миттєво. Клієнт заощадив ~40% бюджету підтримки в перший місяць. Ми налаштували rate limiting та денний ліміт токенів — щоденна витрата не перевищувала $1.5, що підтверджує ефективність підходу.
Типові помилки при розробці AI-бота
- Зберігання історії в пам'яті процесу. При перезапуску бота втрачається контекст. Використовуйте Redis.
- Ігнорування rate limiting. Один користувач може спамити та обнулити бюджет. Обов'язково ставте ліміти.
- Відсутність обробки помилок LLM. Модель може впасти або повернути некоректну відповідь. Обгортайте виклики в try-except.
- Жорстка прив'язка до однієї моделі. Якщо модель стане недоступною, бот перестане працювати. Передбачте fallback на іншу LLM.
Що входить в роботу
- Технічне завдання та вибір стеку.
- Розробка бота (діалог, голос, зображення).
- Налаштування rate limiting та адміністрування.
- Інтеграція Redis та деплой в Docker.
- Документація, доступи та навчання команди.
- Підтримка протягом місяця після запуску.
Строки орієнтовно
- Базовий бот з діалогом: 2–3 дні
- Голос + зображення: 2–3 дні
- Rate limiting + admin panel: 1 тиждень
- Публікація + моніторинг: +2–3 дні
Зв'яжіться з нами для безкоштовної консультації та точного розрахунку вартості. Замовте розробку під ключ з гарантією якості.







