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. Важливо: інференц повинен запускатися в батчах при високому трафіку — 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.
Дообучення базової моделі на domain-специфічних даних.
Розгортання inference API + інтеграція в мобільні клієнти.
Налаштування порогів на основі precision/recall трейдоффа під вимоги продукту.
Моніторинг: доля автоматично заблокованих повідомлень, false positive rate за скаргами користувачів.
Ориентири за часовими рамками
Базова інтеграція готової багатомовної моделі — 4–6 днів. Дообучення на собственному датасеті та розгортання — 2–3 тижні додатково. Повна система з категоризацією, очередю human review та feedback loop — 4–6 тижнів.







