Реалізація AI-виправлення опечаток у пошуку мобільного додатка
Мобільна клавіатура промахується. Swipe-введення, автозамінення, маленькі клавіші — середня кількість опечаток при мобільному введенні у 2–3 рази вище, ніж при настільному. При цьому користувач очікує, що пошук зрозумів його, а не видав «0 результатів». «0 результатів» — це вихід з додатка.
Типи помилок та підходи до виправлення
Помилки в мобільному введенні ділять на три категорії, і для кожної потрібен свій інструмент:
Опечатки (transposition, deletion, substitution) — «кроссвки» замість «кроссовки». Обробляються алгоритмами редакційної відстані: Levenshtein, Damerau-Levenshtein (враховує транспозицію сусідніх символів, що типово для мобіля).
Фонетичні помилки — користувач пише як чує: «найк» → «nike». Для різних мов: фонетичні енкодери або спеціалізовані phonetic encoder.
Транслітерація — «krossovki», «кроссовки», «crossovki» повинні давати однаковий результат. Стандартні транслітераційні таблиці + нормалізація перед індексацією.
Elasticsearch: spell correction з коробки та його обмеження
ES надає term suggester з fuzzy matching. Працює, але:
- шукає найближчі терміни в індексі за edit distance — не враховує контекст запиту
- слабо працює для коротких токенів (< 4 символи) через кількість варіантів з ed=1
- немає врахування частотності: опечатка та бренд-термін отримують однаковий приоритет
# ES term suggester — базовий рівень
response = await es.search(
index="products",
body={
"suggest": {
"spell_suggest": {
"text": query,
"term": {
"field": "title",
"suggest_mode": "missing", # тільки якщо термін не знайдено
"max_edits": 2,
"min_word_length": 4,
"string_distance": "jaro_winkler"
}
}
}
}
)
jaro_winkler краще підходить для коротких рядків, ніж levenshtein — він надає більшу вагу збіганням на початку рядка.
SymSpell: на порядок швидше за Levenshtein
Для продакшену при > 1000 запитів/сек стандартний Levenshtein не підходить через O(n²) складність. SymSpell (Symmetric Delete) попередньо обчислює всі можливі видалення до максимальної edit distance та зберігає у хеш-таблиці. Час lookup — O(1) для більшості запитів.
from symspellpy import SymSpell, Verbosity
sym_spell = SymSpell(max_dictionary_edit_distance=2, prefix_length=7)
sym_spell.load_dictionary("frequency_dict.txt", term_index=0, count_index=1)
def correct_query(query: str) -> str:
suggestions = sym_spell.lookup_compound(
query,
max_edit_distance=2,
transfer_casing=True
)
if suggestions and suggestions[0].distance > 0:
return suggestions[0].term
return query
Частотний словник будуємо з пошукових логів вашого додатка — це важливо: «шкіряний ремінь» буде топі частотності саме у контексті вашого домену, а не «шкіряна куртка» з універсального словника.
Контекстне виправлення через N-gram Language Model
SymSpell виправляє кожне слово незалежно. «кросвки адидас» виправить обидва слова правильно. Але «білі кросівки» — SymSpell може запропонувати «білі» або «білішь», не знаючи, який варіант граматично коректний у контексті.
N-gram language model на пошукових логах допомагає вибрати правильний варіант: P("білі кросівки") >> P("білішь кросівки").
Мобільна інтеграція: UX виправлення
// Android: відображення виправлення з можливістю скасування
@Composable
fun SearchResultsHeader(
originalQuery: String,
correctedQuery: String?,
onRevertToOriginal: () -> Unit
) {
if (correctedQuery != null && correctedQuery != originalQuery) {
Row(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = buildAnnotatedString {
append("Результати для: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(correctedQuery)
}
}
)
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onRevertToOriginal) {
Text("Шукати «$originalQuery»")
}
}
}
}
Паттерн «ми виправили — але ви можете повернути оригінал» — стандарт індустрії. Не навзячуйте виправлення без escape hatch.
// iOS: аналогічний підхід через SwiftUI
struct CorrectionNoticeView: View {
let original: String
let corrected: String
let onRevert: () -> Void
var body: some View {
HStack {
Text("Показуємо результати для «\(corrected)»")
.font(.subheadline)
Spacer()
Button("Шукати «\(original)»", action: onRevert)
.font(.subheadline)
}
.padding(.horizontal)
.padding(.vertical, 6)
.background(Color(.systemGray6))
}
}
Процес роботи
Збір частотного словника з пошукових логів додатка.
Аналіз типових опечаток: які символи плутають на конкретній клавіатурі.
Настройка SymSpell + ES phrase suggester.
Інтеграція виправлення в search API + UX на клієнтах.
Метрика якості: zero-result rate до та після впровадження.
Орієнтири за часом
ES fuzzy + SymSpell з готовим словником — 2–4 дні. Кастомний частотний словник з логів + N-gram LM для контекстного виправлення — 1–2 тижні додатково.







