Реализация AI-детекции токсичности сообщений в мобильном приложении
Токсичность и спам — разные задачи. Спам-сообщение можно обнаружить по паттернам повторения и поведению. Токсичное сообщение уникально, написано живым человеком и нередко формально грамматически корректно — что делает детекцию значительно сложнее.
Главная техническая проблема
Модели общей токсичности типа unitary/toxic-bert хорошо работают на английском Reddit-датасете. В русскоязычном приложении они дают false positive на словах с культурно-специфичной коннотацией и пропускают завуалированный мат с заменой букв (стандартная практика обхода фильтров в СНГ-аудитории). Аналогичная история с украинским и белорусским текстом.
Ещё одна ловушка — синхронный вызов модели перед отправкой сообщения. Пользователь нажал «отправить», ждёт 800 мс — UX сломан. Детекция должна быть либо асинхронной постобработкой, либо настолько быстрой, что задержка незаметна.
Архитектура, которая реально работает
Многоуровневая классификация
Уровень 1 — on-device, быстрый: регулярное выражение + словарь на 2000 очевидных токсичных паттернов, включая leetspeak-варианты. Обрабатывается в < 5 мс, не требует сети. Отсекает 60–65% токсичных сообщений с минимальным false positive.
Уровень 2 — серверный ML: дообученная модель на русскоязычном датасете (RuToxic или аналоги с Hugging Face). Вызывается асинхронно после отображения сообщения — если срабатывает, сообщение скрывается и заменяется плейсхолдером.
// Android: оптимистичная отправка + async toxicity check
fun sendMessage(text: String) {
val tempMessage = Message(text = text, status = MessageStatus.PENDING_REVIEW)
chatAdapter.addMessage(tempMessage) // показываем сразу
viewModelScope.launch {
val result = toxicityRepository.classify(text)
if (result.isToxic && result.confidence > 0.78f) {
chatAdapter.updateMessageStatus(tempMessage.id, MessageStatus.HIDDEN)
showToxicityNotice()
} else {
chatAdapter.updateMessageStatus(tempMessage.id, MessageStatus.VISIBLE)
}
}
messageApi.send(tempMessage)
}
Такой подход — «оптимистичный UI» + постфактум-проверка — снимает проблему задержки. Пользователь видит сообщение мгновенно, а проверка идёт параллельно.
Мультиязычная поддержка через xlm-roberta-base
Для приложений с аудиторией в нескольких странах используем xlm-roberta-base, дообученный на смешанном датасете. Модель в ONNX-формате разворачивается за FastAPI endpoint. Важно: inference нужно запускать в батчах при высоком трафике — onnxruntime поддерживает динамический batching, что даёт ~4x throughput по сравнению с последовательной обработкой.
Granular категории вместо бинарной метки
Вместо простого «токсично/нет» модель возвращает вектор оценок:
| Категория | Порог автоблокировки | Порог human review |
|---|---|---|
| hate_speech | 0.85 | 0.60 |
| insult | 0.90 | 0.70 |
| threat | 0.80 | 0.55 |
| obscenity | 0.88 | 0.65 |
Это позволяет настраивать политику модерации под тип приложения: детское приложение — жёстче пороги, взрослый форум — мягче.
iOS: Core ML для предфильтра
На iOS предфильтр удобно реализовать через Core ML с моделью Text Classifier, сконвертированной через coremltools:
let request = NLModel(mlModel: toxicityModel.model)
let prediction = request.predictedLabel(for: text) ?? "safe"
let confidence = request.predictedLabelHypotheses(for: text, maximumCount: 2)
if prediction == "toxic", let score = confidence["toxic"], score > 0.9 {
return .block
}
NaturalLanguage.framework с кастомной NLModel — самый чистый путь для iOS, не требует сторонних зависимостей в билде.
Процесс работы
Сбор датасета: экспортируем исторические репорты пользователей и размечаем через Label Studio или Toloka.
Дообучение базовой модели на домен-специфичных данных.
Развёртывание inference API + интеграция в мобильные клиенты.
Настройка пороговых значений на основе precision/recall трейдоффа под требования продукта.
Мониторинг: доля автоматически заблокированных сообщений, false positive rate по жалобам пользователей.
Ориентиры по срокам
Базовая интеграция готовой мультиязычной модели — 4–6 дней. Дообучение на собственном датасете и развёртывание — 2–3 недели дополнительно. Полная система с категоризацией, human review очередью и feedback loop — 4–6 недель.







