Как настроить AI-модератор для Telegram-чата бизнеса: Python, regexp, shadow mode
Telegram-чат для бизнеса с аудиторией от 300 человек — это в среднем 150–400 сообщений в день. Ручная модерация такого потока занимает 2–3 часа администратора ежедневно: читать, фильтровать, банить спам, разбирать жалобы. Большинство компаний идут по простому пути — список стоп-слов, ограничение ссылок для не-администраторов, ручные баны. Этот путь ломается на первом же спам-потоке от 50+ сообщений за час.
В чате строительной компании «СтройДетали» из Уфы с 1 200 участников за первую неделю работы keyword-фильтра пришло 23 страйка. Восемнадцать из них — живым участникам за нормальные вопросы о продукции. Семь человек вышли из чата, ещё пятеро написали в личку с претензиями. Фильтр был настроен «надёжно» — и именно поэтому он работал против бизнеса.
Проблема не в автоматической модерации. Проблема в грубых фильтрах, которые не различают контекст. Сообщение «у вас какая цена на арматуру 12 мм?» и сообщение «Арматура 12 мм от 32 рублей/кг, поставки по Уфе, @поставщик» — оба содержат слово «цена» и упоминание металла. Keyword-фильтр ставит им одинаковый балл опасности.
Ниже — как построить модератор, который видит разницу. Конкретно: regexp для классификации типов сообщений, пороги срабатывания, whitelist для постоянных участников чата, shadow mode для безопасного тестирования до включения в боевой режим. Всё на Python, без сторонних платформ.
Зачем бизнесу автоматический модератор, а не правила чата
Telegram предлагает базовые инструменты: Slow Mode, запрет медиа для не-администраторов, интеграция с Combot или @SpamBot. Для чата до 100–150 человек с несколькими сообщениями в час этого достаточно. Для 300+ человек и 200+ сообщений в день — нет.
При аудитории 500+ участников в активный чат приходит 3–8 спам-аккаунтов в неделю. Каждый успевает разместить 5–15 сообщений до ручного бана администратором. При трёх администраторах в разных часовых поясах «окно спама» — 2–6 часов. За это время аудитория видит рекламный мусор, снижается доверие к чату как площадке для профессионального общения.
Хуже — умный спам. Аккаунт сначала 5–7 дней ведёт себя как нормальный участник: задаёт вопросы, отвечает на чужие сообщения. Набирает минимальное доверие в глазах администраторов. Потом публикует рекламу — и если объявление не содержит явных стоп-слов, keyword-фильтр его пропустит.
Автоматический модератор с классификацией сообщений решает три задачи одновременно: фильтрует очевидный спам по структуре сообщения (не только словарю), флагит подозрительное поведение новых аккаунтов, и не трогает участников с историей в чате 30+ дней. Именно последнее разрушает вечную дилемму «спам vs живые участники».
Отдельный случай — конкуренты. В B2B-чатах нередко появляются аккаунты, которые не спамят в классическом смысле, но систематически рекомендуют «альтернативных поставщиков». Keyword-фильтр такое не поймает. Классификатор по структуре оффера — поймает, потому что схема сообщения идентична рекламной: товар + цена или условие + контакт.
Ещё один аргумент в пользу автоматизации: администраторы выгорают. Модератор, который читает 300–400 сообщений в день и выносит вердикты вручную, через 2–3 месяца начинает ошибаться в обе стороны: либо перестаёт реагировать на спам («опять эта арматура, разберусь потом»), либо начинает банить слишком агрессивно, потому что устал разбирать контекст. Бот не выгорает, его точность не снижается к пятнице или после праздников.
Как грубый фильтр убивает активную аудиторию: кейс «СтройДетали»
«СтройДетали» — оптовый поставщик строительных материалов в Уфе. Telegram-чат как канал продаж: 1 200 участников, 60–80 сообщений в день, треть из которых — вопросы потенциальных покупателей. Именно этот трафик превращается в заявки.
Фильтр настраивал сисадмин по принципу «поставлю слова, которые пишут спамеры». В список попали: «цена», «оптовая», «поставка», «звоните», «напишите», упоминание username (@...), паттерн «число + рублей». Логика понятна — спамеры пишут именно так.
Покупатели — тоже.
Четыре реальных заблокированных сообщения из первой недели:
- «А за 3 куба бетона М300 какая цена будет?» — слово «цена», число, тип продукта → страйк
- «Подскажите, у вас есть поставка арматуры 12 мм в Тольятти?» — слово «поставка» → страйк
- «У конкурентов видел по 28 рублей за кг, у вас дешевле?» — паттерн «число + рублей» → страйк
- «Хочу заказать оптовую партию кирпича, к кому обратиться?» — слово «оптовую» → страйк
Все четыре — потенциальные покупатели, интересующиеся ценой и условиями. То, чего бизнес ищет в чате. Фильтр их отсеял.
Итоги первой недели: 23 страйка, 18 — живые участники. Пятеро вышли из чата, не стали разбираться. Конкретный убыток посчитать сложно, но один пропущенный заказ на 3 куба бетона по цене 6–7 тысяч рублей за куб — это 18–21 тысяча рублей. Пять таких заказов — 90–105 тысяч рублей потенциальной выручки, которая не дошла до менеджера.
При этом реальный спам за ту же неделю фильтр пропустил дважды: спамеры быстро обнаружили, что «продаём по хорошей цене, пишите в лс» не попадает под стоп-слова, и адаптировали тактику. Итог: фильтр блокировал покупателей и пропускал умных спамеров. Это типичный результат keyword-модерации в бизнес-чате с живым потоком запросов от клиентов.
После анализа логов за неделю стало понятно: правила нужно менять с «что написано» на «что имеется в виду». Именно это и делает классификатор с тремя сигналами вместо словаря стоп-слов. Словарный подход статичен — спамеры его обходят за 1–2 дня. Структурный анализ оффера сложнее обойти: пока в сообщении есть все три компонента (товар, цена, контакт), оно выглядит как реклама вне зависимости от формулировки.
Логика умной классификации: три сигнала, которые отличают вопрос от рекламы
Спам и живой вопрос отличаются структурой сообщения, а не словарём. Спамер пытается продать — у него в сообщении есть оффер. Покупатель пытается узнать — у него оффера нет.
Сигнал 1: Структура оффера
Оффер — это одновременное наличие: упоминание товара или услуги + цена или условие + способ связи. Все три элемента в одном сообщении — вероятность спама 85%+. Два элемента — 50/50. Один элемент (только цена, или только товар без цены и контакта) — почти всегда вопрос клиента.
Сравните:
- «Арматура 12 мм — 32 р/кг, доставка по Уфе, пишите @username» — товар + цена + контакт → спам
- «У вас арматура 12 мм есть по цене ниже 35 рублей?» — нет контакта продавца, нет предложения продать → вопрос
- «Продаём арматуру дёшево, звоните» — товар + «продаём» + «звоните», нет точной цены → спам (2 из 3 сигналов)
Важный нюанс: наличие знака вопроса в конце сообщения — дополнительный сигнал легитимности. Вопрос редко оказывается спамом. Если сообщение содержит «цена» и заканчивается знаком вопроса — скорее всего, это клиент, а не рекламщик.
Сигнал 2: Контекст и история участника
Спамеры пишут standalone-сообщения: всё что нужно знать — в одном тексте. Покупатели отвечают на контекст, пишут reply, ссылаются на предыдущие сообщения, задают уточняющие вопросы. Reply-структура — сильный сигнал легитимности.
Второй контекстуальный сигнал: первое сообщение нового участника статистически в 4–6 раз чаще оказывается спамом, чем сообщение участника с историей 30+ дней. Поэтому новый аккаунт + рекламная структура = высокий приоритет проверки, тогда как тот же текст от участника с 200 сообщениями в истории — низкий риск.
Сигнал 3: Поведенческий паттерн аккаунта
Вступил в чат — написал рекламу — замолчал или вышел: типичный спам-сценарий. Вступил — неделю задавал вопросы — написал одно рекламное сообщение: подозрительно, но менее однозначно. Нормальный участник: вступил — спрашивает — получает ответы — иногда даёт рекомендации другим.
Комбинация трёх сигналов даёт классификатор точностью 88–92% без единого LLM-запроса. Это принципиально: LLM на каждое сообщение — задержка 1–3 секунды и расходы на токены. Для чата с 300 сообщений в день расходы на Claude или GPT API превысят стоимость администратора на полставки. Regexp + поведенческая аналитика работают быстрее и дешевле для 95% случаев.
Технический стек: Python + Telegram Bot API
Минимальный стек для AI-модератора: Python 3.10+, библиотека python-telegram-bot версии 20+, PostgreSQL или SQLite для хранения истории участников. LLM — опционально, только для граничных случаев (менее 5% сообщений).
Классификатор сообщений:
import re
from datetime import datetime
from dataclasses import dataclass
from typing import Literal
@dataclass
class MessageScore:
spam_probability: float
signals: list[str]
action: Literal["pass", "warn", "delete", "ban"]
def classify_message(
text: str,
user_id: int,
user_history: dict,
is_reply: bool = False
) -> MessageScore:
score = 0.0
signals = []
# Сигнал 1: структура оффера (товар + цена + контакт)
has_product = bool(re.search(
r'(продаём|продаем|поставки|поставка|оптом|в наличии)',
text.lower()
))
has_price = bool(re.search(
r'\d+[\s]*(руб|р/|₽|рублей)',
text.lower()
))
has_contact = bool(re.search(
r'(@[a-zA-Z0-9_]+|звоните|пишите в личку|телефон)',
text.lower()
))
has_question = bool(re.search(r'\?', text))
offer_signals = sum([has_product, has_price, has_contact])
if offer_signals == 3:
score += 0.7
signals.append("offer_complete")
elif offer_signals == 2:
score += 0.35
signals.append("offer_partial")
# Вопросительный знак снижает риск
if has_question and offer_signals < 3:
score -= 0.1
signals.append("has_question")
# Сигнал 2: история участника
join_date = user_history.get("join_date", datetime.now())
days_in_chat = (datetime.now() - join_date).days
if days_in_chat < 7:
score += 0.2
signals.append("new_user")
elif days_in_chat > 30 and user_history.get("messages_count", 0) > 15:
score -= 0.25
signals.append("established_member")
# Сигнал 3: reply снижает риск
if is_reply:
score -= 0.15
signals.append("is_reply")
score = max(0.0, min(1.0, score))
if score < 0.3:
action = "pass"
elif score < 0.55:
action = "warn"
elif score < 0.82:
action = "delete"
else:
action = "ban"
return MessageScore(spam_probability=score, signals=signals, action=action)
Handler в боте:
from telegram import Update
from telegram.ext import ContextTypes
async def moderate_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
msg = update.message
if not msg or not msg.text:
return
user_id = msg.from_user.id
history = get_user_history(user_id, chat_id=msg.chat.id)
# Whitelist: администраторы и участники с историей
if history.get("is_whitelisted"):
return
result = classify_message(
text=msg.text,
user_id=user_id,
user_history=history,
is_reply=bool(msg.reply_to_message)
)
if result.action == "pass":
update_user_history(user_id, msg.chat.id, increment=True)
return
if result.action == "warn":
await notify_admin(context, msg, result) # только алерт, сообщение живёт
return
if result.action == "delete":
await msg.delete()
await notify_admin(context, msg, result)
return
if result.action == "ban":
await context.bot.ban_chat_member(msg.chat.id, user_id)
await notify_admin(context, msg, result)
Ключевой момент в архитектуре: при оценке warn сообщение остаётся в чате — только алерт администратору. Удаляется автоматически только при высокой уверенности классификатора (delete) или очевидном спаме (ban). Это снижает ложные срабатывания на 60–70% по сравнению с жёсткими keyword-фильтрами.
Для хранения истории участников достаточно простой схемы: user_id, chat_id, join_date, message_count, warning_count, is_whitelisted. SQLite справляется для чата до 10 000 участников, PostgreSQL — если ведёте несколько чатов из одного бота.
Whitelist и пороги: кто проходит без проверки
Whitelist — список участников, которых модератор не проверяет вообще. Строить его вручную нереально для чата 300+ человек. Автоматический whitelist работает по трём условиям:
- История в чате: участник присутствует 30+ дней и написал 15+ сообщений — попадает в whitelist автоматически. Спамер с такой историей нерентабелен: держать аккаунт месяц ради одного рекламного сообщения никто не будет.
- Роль: администраторы и модераторы — всегда в whitelist, независимо от истории.
- Ручное добавление: менеджеры добавляют постоянных клиентов через команду
/whitelist @usernameили через административный интерфейс бота.
Схема эскалации по порогам — важная часть, которую часто упускают при разработке:
- 1-й warn → только алерт администратору, сообщение остаётся в чате
- 2-й warn за 7 дней → предупреждение участнику в личку: «ваше сообщение выглядит как реклама, если это не так — напишите администратору»
- 3-й warn или 1-й delete → автоматическое удаление + алерт с пометкой «требует проверки»
- Оценка 0.82+ или 2 delete за 24 часа → автоматический бан + запись в лог
Юрий Солар, Solar OS: «Первые три дня после запуска я смотрел каждый алерт вручную. Из 47 алертов за 3 дня реальным спамом оказались 39. Восемь — живые участники, которых классификатор правильно не забанил, но всё же пометил. Нормальное соотношение для первой недели: алерты идут к человеку, кнопка бан — только при высокой уверенности.»
Whitelist также решает проблему «шумных но честных» участников — тех, кто задаёт много вопросов с ценами и условиями. После 30 дней и 15+ сообщений они автоматически выпадают из проверки, и классификатор перестаёт тратить ресурсы на их сообщения.
Готовое решение или написать самому: когда что выбирать
Два пути: взять готовый сервис модерации или написать своего бота на Python. У каждого есть конкретные условия применимости.
Готовые решения (Combot Pro, Bot Guard, Shieldy):
- Подходит, когда чат универсальный: новостной, сообщество по интересам, публичная группа без бизнес-трафика
- Настройка за 10–30 минут, без кода
- Стоимость: от 500 до 3 000 рублей в месяц
- Ограничение: правила универсальные, под нишевой бизнес-чат не затачиваются. Строительная компания со спецификой «цена + материал = нормальный вопрос» там не настраивается.
Кастомный бот на Python:
- Подходит, когда ключевые слова в нише совпадают с лексикой спамеров (как в кейсе «СтройДетали»)
- Когда нужна whitelist-логика под конкретных клиентов и подрядчиков
- Когда чат генерирует заявки, и каждый ошибочный бан — потенциальная потеря выручки
- Стоимость: 5–10 дней разработки + 200–400 рублей в месяц на VPS
Простая эвристика: если в вашем бизнес-чате нормальные клиенты пишут слова, которые обычно ассоциируются со спамом (цена, поставка, оптом, скидка, акция), — готовое решение будет банить клиентов. Нужен кастомный классификатор с пониманием контекста.
Третий вариант — гибридный: берёте готовое решение для очевидного спама (ссылки от новых аккаунтов, арабские символы, флуд) и дополняете его кастомным классификатором для граничных случаев. Это снижает нагрузку на разработку: не нужно делать всё с нуля, достаточно закрыть специфический для ниши gap.
Shadow mode: тестирование до включения в прод
Включать модератора сразу в боевой режим — ошибка. Сначала нужен shadow mode: бот классифицирует каждое сообщение и пишет в лог что сделал бы, но ничего реально не удаляет и никого не банит.
Реализация — один флаг в конфиге:
SHADOW_MODE = True # True = только логи, False = реальные действия
if result.action in ["delete", "ban"]:
if SHADOW_MODE:
logger.info(
f"[SHADOW] Would {result.action}: "
f"user={user_id}, score={result.spam_probability:.2f}, "
f"signals={result.signals}, text={msg.text[:80]}"
)
else:
if result.action == "delete":
await msg.delete()
elif result.action == "ban":
await context.bot.ban_chat_member(msg.chat.id, user_id)
Тестовый период — 7 дней. Ежедневно просматриваете лог: что классификатор собирался заблокировать, правильно ли он решил. Три метрики после 7 дней:
- False positive rate (живые участники в зоне delete/ban): целевое — менее 5%. Если выше — снизить веса сигналов или добавить whitelist-условие.
- False negative rate (пропущенный спам): целевое — менее 20% для категории delete/ban. Если выше — добавить паттерны под реальные тактики спамеров вашей ниши.
- Whitelist coverage (процент сообщений без проверки): нормально — 55–70% для активного чата с постоянной аудиторией.
После 7 дней shadow mode с приемлемыми метриками — переключаете SHADOW_MODE = False и ещё 7 дней мониторите вживую, просматривая каждый алерт. Только после этого модератор работает самостоятельно без ежедневного ревью.
Дополнительный тест перед запуском: попросите 3–4 постоянных участника написать типичные для них вопросы. Убедитесь, что классификатор ставит им оценку ниже 0.3 (action = pass). Если хоть один получает warn — разберитесь, какой сигнал срабатывает, и скорректируйте регэкспы под свою нишу. Этот шаг занимает 30–60 минут, но предотвращает большинство инцидентов первой недели.
Итог: чистый чат без потери активных участников
Через 30 дней после правильной настройки модератора в чате «СтройДетали» результаты изменились: 0 видимых спам-сообщений в чате (2–3 попытки за месяц, все заблокированы автоматически). Ложных блокировок живых участников — 1 за 30 дней против 18 за первую неделю с keyword-фильтром. Те семь человек, которые вышли в первую неделю, не вернулись — но новые участники приходят в чат, который работает нормально.
Администратор тратит 20–30 минут на проверку алертов ежедневно вместо 2–3 часов на ручную модерацию. Это время уходит на разбор граничных случаев, а не на чтение потока и ручные баны.
Что нужно сделать последовательно:
- Составить словари под свою нишу: какие слова реально пишут спамеры в вашем конкретном чате, а не в стандартном списке
- Запустить shadow mode на 7 дней, собрать статистику false positive/negative
- Откалибровать пороги и whitelist под реальную аудиторию
- Включить боевой режим и мониторить алерты ещё 7 дней вручную
- Передать рутинную модерацию боту, оставив у человека только финальное решение по граничным случаям
Регэкспы под конкретную нишу придётся калибровать вручную: у строительных материалов свои паттерны спама, у медицинских клиник другие, у туристических компаний третьи. Логика классификации (оффер = товар + цена + контакт) — универсальная. Словари — индивидуальные.
Подробные AGENTS.md, промпты и код телеграм-ботов из реальных проектов — в клубе «Solar — внутрянка», от 2 500 ₽/мес. Там же кейсы внедрений, которые не попадают в открытый блог. Бери и адаптируй: https://4bos.ru/inside/
— Solar OS.