Современные онлайн-чаты, особенно в Telegram, сталкиваются с серьезными проблемами токсичного контента, спама и оскорблений. Традиционные фильтры по ключевым словам давно устарели: пользователи легко обходят их с помощью замены букв, использования смайликов или специальных символов. В результате ручная модерация отнимает много времени, а автоматическая часто оказывается неточной и излишне жесткой.
Мы уже публиковали туториалы по созданию ботов модераторов:
Но прошлые статьи использовали классические ML-модели, что делало их менее гибкими.
В этой статье я рассмотрю пример, в котором будут использоваться нейронки для выполнения задач модерации.
Большие языковые модели (LLM) способны понимать контекст и смысл сообщения, а не просто искать запрещённые слова. И в отличии от классического ML их не нужно переобучать, достаточно просто изменить промпт. Это делает их идеальным инструментом для интеллектуальной модерации. Вместо примитивных фильтров бот с LLM анализирует семантику текста и принимает взвешенные решения. Однако просто использовать LLM недостаточно — необходимо учитывать уникальные правила, которые могут отличаться для каждого чата. Поэтому мы реализуем гибридный подход:
-
LLM анализирует текст на основе контекста и установленных правил.
-
Бот сверяется с правилами из базы данных для конкретного чата.
-
Система принимает обоснованное решение: оставить сообщение, удалить, забанить пользователя или вынести предупреждение.
Подготовка к разработке бота модератора
Перед началом работы необходимо скачать и установить Python с официального сайта. Рекомендуется использовать версию 3.8 или выше. После установки откройте терминал (командную строку) и введите следующие команды:
pip install aiogram pip install requests
Эти команды установят необходимые для работы библиотеки. Теперь можно приступить к получению токенов для LLM и бота.
Первым делом создадим бота в Telegram. Для этого перейдите в @BotFather и введите команду /newbot. Следуйте инструкциям: укажите имя бота (например, «AI Moderator») и username (например, «aimoder_amvera_bot»).
Теперь необходимо получить токен для доступа к LLM. Регистрируемся в Amvera Cloud и получаем на баланс 111 рублей, чего вполне хватит для теста. После регистрации переходим в раздел LLM. Выбираем одну из четырёх доступных моделей и копирем токен.
Обратите внимание: для использования токена необходимо активировать подписку. В разделе LLM (Preview) найдите кнопку «Подключить пакет токенов» и выберите подходящий тарифный план. Для тестов нам подойдёт бесплатный пакет на 20 000 токенов.
Теперь, имея все необходимые токены, можно приступить к разработке.
Архитектура бота модератора
Как же будет работать система модерации? Процесс модерации организован следующим образом:
-
Пользователь отправляет сообщение в чат.
-
Бот перехватывает сообщение и отправляет его текст на проверку в LLM.
-
Модель анализирует текст на наличие нарушений, основываясь на установленных правилах беседы.
-
В зависимости от ответа модели, бот принимает решение:
-
Если сообщение нарушает правила, то бот выдает соотвествующее наказание, либо удаляет сообщение.
-
Если в сообщении не будет нарушений, то ничего делать не нужно.
-
Как вы могли заметить, в Amvera Cloud доступны четыре модели:
-
LLaMA 3 8B — оптимальна по соотношению цены и скорости, подходит для большинства случаев использования.
-
LLaMA 3 70B — более мощная модель для больших чатов и сложных сценариев модерации.
-
GPT 5 и 4.1 — для самых сложных сценариев.
Для тестирования мы будем использовать LLaMA 3 8B как наиболее сбалансированный вариант по минимальной цене.
Пишем бота модератора
Создайте в удобном месте папку для проекта, в которой создайте файл main.py с кодом нашего бота и папку data для хранения базы данных.
Начнём с реализации команды /start и базового функционала:
import sqlite3 import asyncio import requests import logging import re from contextlib import suppress from datetime import datetime, timedelta from aiogram import Bot, Dispatcher, F from aiogram.filters import Command, CommandObject from aiogram.types import Message, ChatPermissions from aiogram.client.default import DefaultBotProperties from aiogram.exceptions import TelegramBadRequest logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") URL = "https://kong-proxy.yc.amvera.ru/api/v1/models/llama" BOT_TOKEN = "" # ТОКЕН ВАШЕГО БОТА AUTH_TOKEN = "" # ТОКЕН LLM В ФОРМАТЕ Bearer XXXXXXXX logger = logging.getLogger(__name__) connection = sqlite3.connect("/data/database.db", check_same_thread=False) cursor = connection.cursor() bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode="Markdown")) dp = Dispatcher() cursor.execute("CREATE TABLE IF NOT EXISTS rules (id INTEGER PRIMARY KEY AUTOINCREMENT, rules TEXT, group_id BIGINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)") cursor.execute("CREATE TABLE IF NOT EXISTS warnings (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, group_id INTEGER NOT NULL, reason TEXT NOT NULL, moderator_id INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)") connection.commit() @dp.message(Command("start")) async def start(message: Message): await message.answer("Привет! Я бот-модератор, основанный на LLM LLaMA 8b") if __name__ == "__main__": asyncio.run(dp.start_polling(bot))
Теперь добавим базовую интеграцию с API Amvera, где бот будет проверять каждое сообщение и принимать решение об удалении или оставлении:
@dp.message(F.text) async def on_message(message: Message): headers = {"X-Auth-Token": str(AUTH_TOKEN), "Content-Type": "application/json"} data = {"model": "llama8b", "messages": [{"role": "user", "text": "Ты модератор, проверяющий сообщения участников чата. Отвечай строго по форме: Оставить/Удалить"}, {"role": "system", "text": message.text}]} response = requests.post(URL, headers=headers, json=data, timeout=10) result = response.json() decision = result['result']['alternatives'][0]['message']['text'].lower() if decision == "удалить": await message.delete() logger.info(f"УДАЛЕНО | @{message.from_user.username} (ID: {message.from_user.id}) | Текст: \"{message.text}\"") elif decision == "оставить": logger.info(f"ОСТАВЛЕНО | @{message.from_user.username} (ID: {message.from_user.id}) | Текст: \"{message.text}\"") else: logger.warning(f"ОШИБКА | Текст: \"{message.text}\" | Ответ: {result}")
Отлично! У нас есть основа, от которой мы можем отталкиваться в дальнейшем. Приступим к реализации полноценной логики модерации.
Добавим три команды для управления правилами группы. Команда /addrules — добавляет или обновляет правила:
@dp.message(Command("addrules")) async def add_rules(message: Message): if message.chat.type not in ["group", "supergroup"]: await message.answer("Эта команда работает только в групповых чатах!") return rules = message.text.split(' ', 1) if len(rules) < 2: await message.answer("Пожалуйста, укажите правила. Пример:\n/addrules Не использовать мат в чате") return rules = rules[1].strip() cursor.execute("SELECT id FROM rules WHERE group_id = ?", (message.chat.id,)) if cursor.fetchone(): cursor.execute("UPDATE rules SET rules = ?, created_at = ? WHERE group_id = ?", (rules, datetime.now(), message.chat.id)) await message.answer("Правила успешно обновлены!") else: cursor.execute("INSERT INTO rules (rules, group_id) VALUES (?, ?)", (rules, message.chat.id)) await message.answer("Правила успешно добавлены!") connection.commit()
Команда /rules — отображает текущие правила группы:
@dp.message(Command("rules")) async def show_rules(message: Message): if message.chat.type not in ["group", "supergroup"]: await message.answer("Эта команда работает только в групповых чатах!") return cursor.execute("SELECT rules FROM rules WHERE group_id = ?", (message.chat.id,)) if cursor.fetchone(): await message.answer(f"Правила этой группы:\n\n{cursor.fetchone()[0]}") else: await message.answer("Правила для этой группы еще не установлены. Используйте /addrules чтобы добавить правила.")
И команда /clearrules — удаляет текущие правила группы:
@dp.message(Command("clearrules")) async def clear_rules(message: Message): if message.chat.type not in ["group", "supergroup"]: await message.answer("Эта команда работает только в групповых чатах!") return cursor.execute("DELETE FROM rules WHERE group_id = ?", (message.chat.id,)) connection.commit() if cursor.rowcount > 0: await message.answer("Правила успешно удалены!") else: await message.answer("Правила для этой группы не найдены.")
Отлично! Теперь у нас есть функционал управления правилами:
Можем приступить к разработке основной логики модерации.
Для начала усовершенствуем обработку сообщений. Добавим более строгий и детализированный промпт, где чётко определим процедуру проверки, формат ответа и примеры наказаний для случаев, когда правила в группе не установлены:
@dp.message(F.text) async def on_message(message: Message): member = await bot.get_chat_member(message.chat.id, message.from_user.id) if member.status in ['administrator', 'creator']: return cursor.execute("SELECT rules FROM rules WHERE group_id = ?", (message.chat.id,)) rules_result = cursor.fetchone() rules = rules_result[0] if rules_result else "Стандартные правила поведения в чатах" headers = {"X-Auth-Token": str(AUTH_TOKEN), "Content-Type": "application/json"} data = { "model": "llama8b", "messages": [ {"role": "system", "text": f"""Ты — строгий модератор чата. Анализируй сообщения на основе правил этой группы. ПРАВИЛА ДАННОЙ ГРУППЫ: {rules} ПРОЦЕДУРА АНАЛИЗА: 1. ОЧЕНЬ внимательно прочитай правила группы выше 2. Сравни сообщение пользователя с этими правилами 3. Если есть нарушение - определи какое именно правило нарушено 4. Назначь наказание согласно предусмотренному в правилах 5. Если в правилах не указано конкретное наказание - примени стандартное для типа нарушения ФОРМАТ ОТВЕТА ТОЛЬКО ОДНА СТРОКА БЕЗ КАВЫЧЕК: Удалить, действие: [бан/мут/кик/предупреждение], длительность: [секунды], причина: [конкретное правило и нарушение] - ЕСЛИ НАРУШЕНИЕ Оставить, причина: - - ЕСЛИ НЕТ НАРУШЕНИЯ ПРИМЕРЫ ОТВЕТОВ ДЛЯ РАЗНЫХ ПРАВИЛ: Если в правилах: "За оскорбления - бан" Сообщение: "иди на хуй" → "Удалить, действие: бан, длительность: 0, причина: оскорбление (нарушение правила про оскорбления)" Если в правилах: "Спам - мут на 1 час" Сообщение: "купите товар" → "Удалить, действие: мут, длительность: 3600, причина: спам (нарушение правила про рекламу)" Если в правилах: "Флуд - предупреждение" Сообщение: "спам спам спам" → "Удалить, действие: предупреждение, длительность: 0, причина: флуд (нарушение правила про флуд)" Если нарушение не описано в правилах явно, но противорит духу правил: Сообщение: "угрозы" → "Удалить, действие: бан, длительность: 0, причина: угрозы (нарушение общего правила поведения)" Сообщение без нарушений: "привет" → "Оставить, причина: -" Сообщение для анализа:""" }, { "role": "user", "text": message.text } ], "temperature": 0.1, "max_tokens": 70 } response = requests.post(URL, headers=headers, json=data, timeout=10) result = response.json() decision = result['result']['alternatives'][0]['message']['text'].lower().strip() delete_match = re.match(r"удалить,\s*действие:\s*(\w+),\s*длительность:\s*(\d+),\s*причина:\s*(.+)",decision) leave_match = re.match(r"оставить,\s*причина:\s*-", decision) if delete_match: action = delete_match.group(1) duration = int(delete_match.group(2)) reason = delete_match.group(3).strip() await apply_punish(message, action, duration, reason) await message.delete() logger.info(f"УДАЛЕНО | Правила: {rules[:50]}... | Действие: {action} | Длительность: {duration}с | Пользователь: @{message.from_user.username} | Причина: {reason} | Текст: {message.text}") elif leave_match: logger.info(f"ОСТАВЛЕНО | @{message.from_user.username} | Правила: {rules[:30]}... | Текст: {message.text}") else: logger.warning(f"НЕПОНЯТНЫЙ ОТВЕТ: '{decision}' | Правила: {rules[:30]}... | Текст: {message.text}")
Теперь реализуем асинхронную функцию apply_punish, которая отвечает за применение наказаний:
async def apply_punish(message: Message, action: str, duration: int, reason: str) -> str: user_id = message.from_user.id chat_id = message.chat.id if action == "бан": await message.bot.ban_chat_member(chat_id, user_id) text = "Бан (навсегда)" elif action == "мут": until_date = datetime.now() + timedelta(seconds=duration) await message.bot.restrict_chat_member(chat_id, user_id, until_date = until_date, permissions = ChatPermissions(can_send_messages=False, can_send_media_messages=False, can_send_other_messages=False, can_add_web_page_previews=False)) if duration == 0: time_str = "навсегда" elif duration < 60: time_str = f"{duration} с" elif duration < 3600: time_str = f"{duration//60} м" elif duration < 86400: time_str = f"{duration // 3600} ч" else: days = duration // 86400 time_str = f"{days} дн" text = f"Мут ({time_str})" elif action == "кик": await message.bot.ban_chat_member(chat_id, user_id, until_date=datetime.now() + timedelta(seconds=30)) text = "Кик" elif action == "предупреждение": cursor.execute("INSERT INTO warnings (user_id, group_id, reason, moderator_id) VALUES (?, ?, ?, ?)", (user_id, chat_id, reason, message.bot.id)) connection.commit() text = "Предупреждение" return text
Можем попробовать запустить бота. Вводим команду:
python main.py
В консоли мы увидим следующий вывод:
Это означает, что бот успешно запустился и готов к работе. Создадим тестовую группу, добавим бота с правами администратора и пригласим пользователя для тестирования нарушений.
Вот так выглядит вывод в логах, когда сообщение было оставлено:
А так выглядит вывод при обнаружении нарушения правил:

Как можно видеть, бот успешно заблокировал пользователя и удалил его сообщение:
За кадром мы также протестировали другие виды наказаний, и все они работают корректно.
Однако ограничиваться только автоматической выдачей наказаний нельзя, поэтому добавим команды для ручного управления: /ban,/unban, /mute и /unmute.
Для начала реализуем функцию для парсинга времени:
def pars_time(time: str | None) -> datetime | None: if not time: return None match = re.match(r"(\d+)([a-z])", time.lower()) if match: value = int(match.group(1)) unit = match.group(2) match unit: case "h": delta = timedelta(hours=value) case "d": delta = timedelta(days=value) case _: return None else: return None new = datetime.now() + delta return new
Теперь последовательно добавим команды модерации:
Команда /ban — блокировка пользователя:
@dp.message(Command("ban")) async def mute(message: Message, command: CommandObject | None = None) -> None: if message.chat.type not in ["group", "supergroup"]: await message.answer("Эта команда работает только в групповых чатах!") return reply = message.reply_to_message if not reply: return None date = pars_time(command.args) with suppress(TelegramBadRequest): await bot.ban_chat_member(chat_id=message.chat.зid, user_id=reply.from_user.id, until_date=date) await message.answer(f"Модератор {message.from_user.mention_markdown(message.from_user.username)} заблокировал пользователя {reply.from_user.mention_markdown(reply.from_user.username)}")
Команда /unban — разблокировка пользователя:
@dp.message(Command("unban")) async def mute(message: Message): if message.chat.type not in ["group", "supergroup"]: await message.answer("Эта команда работает только в групповых чатах!") return reply = message.reply_to_message if not reply: return None with suppress(TelegramBadRequest): await bot.unban_chat_member(chat_id=message.chat.id, user_id=reply.from_user.id) await message.answer(f"Модератор {message.from_user.mention_markdown(message.from_user.username)} разблокировал пользователя {reply.from_user.mention_markdown(reply.from_user.username)}")
Команда /mute — ограничение возможности отправки сообщений:
@dp.message(Command("mute")) async def mute(message: Message, command: CommandObject | None = None) -> None: if message.chat.type not in ["group", "supergroup"]: await message.answer("Эта команда работает только в групповых чатах!") return reply = message.reply_to_message if not reply: return None date = pars_time(command.args) with suppress(TelegramBadRequest): await bot.restrict_chat_member(chat_id=message.chat.id, user_id=reply.from_user.id, until_date=date, permissions=ChatPermissions(can_send_messages=False)) await message.answer(f"Модератор {message.from_user.mention_markdown(message.from_user.username)} выдал мут пользователю {reply.from_user.mention_markdown(reply.from_user.username)}")
Команда /unmute — снятие ограничений:
@dp.message(Command("unmute")) async def mute(message: Message, command: CommandObject | None = None) -> None: if message.chat.type not in ["group", "supergroup"]: await message.answer("Эта команда работает только в групповых чатах!") return reply = message.reply_to_message if not reply: return None with suppress(TelegramBadRequest): await bot.restrict_chat_member(chat_id=message.chat.id, user_id=reply.from_user.id, permissions=ChatPermissions(can_send_messages=True)) await message.answer(f"Модератор {message.from_user.mention_markdown(message.from_user.username)} снял мут пользователю {reply.from_user.mention_markdown(reply.from_user.username)}")
Сохраняем наш файл и проверяем работу, предварительно запустив бота командой:
python main.py
Проверим выдачу и снятие мута:
И проверим выдачу и снятие блокировки:
Отлично! Наш бот полностью готов к использованию. Осталось задеплоить его на хостинг Amvera Cloud.
Деплой бота модератора
Развернём наш скрипт для модерации в Amvera, воспользовавшись встроенным CI/CD. В отличии от VPS, Amvera предоставляет движек приложений, максимально упрощающий развёртывание и администрирование проекта.
Мы рассмотрим 2 способа деплоя:
-
Через веб-интерфейс Amvera
-
Через Git push
Для начала создадим файл зависимостей requirements.txt:
aiogram==3.21.0 requests==2.32.4
Это минимальный вариант файла — дополнительные зависимости будут установлены автоматически. Вы также можете создать полный файл зависимостей с помощью команды:
pip freeze
Теперь создадим конфигурационный файл amvera.yml:
version: null meta: environment: python toolchain: name: pip version: "3.12" build: requirementsPath: requirements.txt run: scriptName: main.py command: null persistenceMount: /data containerPort: 80
Сделать это можно в разделе Конфигурация, выбрав нужные параметры.
Также обязательно убедитесь, что в файле main.py используется правильный путь к базе данных:
connection = sqlite3.connect("/data/database.db", check_same_thread=False)
Это критически важно, так как при неправильном пути бот не сможет найти базу данных и не запустится.
Теперь приступим непосредственно к деплою. Начнём с деплоя через веб-интерфейс.
Идем на сайт Amvera Cloud заходим в свой личный кабиент и идем в раздел Приложения. Создайте новый проект, укажите название и выберите подходящий тарифный план. На этапе загрузки данных выберите опцию «Через интерфейс» и загрузите необходимые файлы (не включая папку data).
На этапе настройки переменных окружения вы можете добавить переменные для токена бота и токена LLM. Для этого предварительно удалите из файла main.py прямые указания токенов:
BOT_TOKEN = "" # ТОКЕН ВАШЕГО БОТА AUTH_TOKEN = "" # ТОКЕН LLM В ФОРМАТЕ Bearer XXXXXXXX
Теперь рассмотрим вариант деплоя через Git.
Откройте терминал в папке с проектом и выполните команду:
git init
Создайте приложение, но на этапе загрузки файлов нажмите «Отменить».
Перейдите в раздел созданного приложения и в разделе «Репозиторий» найдите команду «Подключить к существующему репозиторию». Скопируйте и выполните ее в терминале. Команда будет иметь следующий формат:
git remote add amvera https://git.amvera.ru/имя пользователя/имя проекта;
Эта команда подключит удаленный репозиторий проекта к вашему локальному репозиторию. Теперь добавьте файлы в отслеживаемые:
git add amvera.yml main.py requirements.txt
Выполните коммит изменений:
git commit -m "Обновление 1.0"
Теперь отправьте изменения на сервер Amvera (сборка начнется автоматически):
git push amvera master
Появится запрос на ввод учетных данных:

Введите имя пользователя и пароль от вашего аккаунта Amvera Cloud.
Отлично! Бот успешно запустился в обоих случаях:
На этом наш деплой законечен!
Заключение
Мы успешно реализовали интеллектуального Telegram-бота-модератора, который использует LLM Amvera Cloud для семантического анализа сообщений. В отличие от примитивных фильтров по ключевым словам, наша система способна понимать контекст, учитывать специфические правила каждого чата и автоматически применять адекватные санкции к нарушителям.
Такой подход позволяет:
-
Снизить нагрузку на администраторов
-
Повысить качество модерации
-
Гибко адаптировать поведение бота под разные сообщества
-
Обеспечить проактивное выявление нарушений до эскалации конфликтов
На практике это означает, что администраторам больше не нужно постоянно мониторить чат вручную и разбирать каждый спорный случай — бот берет эту рутинную работу на себя, позволяя модераторам сосредоточиться на стратегических задачах. В результате чат становится более безопасным и комфортным пространством, а пользователи — более дисциплинированными, зная, что нарушения не останутся незамеченными.
И главное, в сравнении с классическим ML, модель не нужно переобучать, достаточно просто изменить промт.
Код проекта доступен по ссылке на GitHub.
Релевантные материалы:
ссылка на оригинал статьи https://habr.com/ru/articles/945724/
Добавить комментарий