Реализация AI-автодополнения поисковых запросов в мобильном приложении
Автодополнение — одна из самых требовательных к latency функций мобильного приложения. Пользователь ожидает подсказки быстрее, чем успевает заметить их появление: идеал — < 100 мс от ввода символа до появления вариантов. При этом запросы должны быть релевантными, а не просто «популярными».
Почему простой prefix-поиск не работает
Наивная реализация — хранить частые запросы в словаре и искать по префиксу. Это работает для «nike» → «nike кроссовки», но ломается для:
- орфографических ошибок: «найк» вместо «nike»
- транслитерации: «krossovki» vs «кроссовки»
- семантически близких запросов: «беговая обувь» при вводе «кросс»
- персонализации: одинаковый запрос «платье» для разных пользователей должен давать разные топ-подсказки
Архитектура production-ready автодополнения
Trie + нечёткий поиск для скорости
Базовый слой — Trie на популярных запросах с нечётким поиском через BK-tree или симметричное удаление (Symmetric Delete). Elasticsearch с маппингом completion field — готовое решение с fuzzy matching из коробки:
{
"mappings": {
"properties": {
"suggest": {
"type": "completion",
"analyzer": "standard",
"contexts": [
{"name": "category", "type": "category"}
]
},
"weight": {"type": "integer"}
}
}
}
# Поиск автодополнений через ES Completion Suggester
async def get_suggestions(prefix: str, category: str, user_id: str) -> list[str]:
response = await es.search(
index="search_suggestions",
body={
"suggest": {
"query_suggest": {
"prefix": prefix,
"completion": {
"field": "suggest",
"size": 8,
"fuzzy": {"fuzziness": "AUTO"},
"contexts": {"category": [category]}
}
}
}
}
)
return [hit["_source"]["query"] for hit in response["suggest"]["query_suggest"][0]["options"]]
Персонализированный ранжировщик подсказок
Базовые подсказки из ES переранжируются с учётом истории пользователя. Признаки ранжировщика:
-
global_frequency— сколько раз этот запрос вводили все пользователи -
user_query_history_match— вводил ли этот пользователь похожий запрос раньше -
user_category_affinity— насколько категория запроса близка интересам пользователя -
recency_boost— трендовые запросы за последние 24 часа получают буст
On-device кэш для мгновенного отклика
Первые 3–5 символов запроса покрывают ~80% популярных prefix-комбинаций. Кэшируем подсказки для них на устройстве при старте приложения (или в фоне):
// Android: предзагрузка популярных prefix-подсказок
class AutocompleteCache(context: Context) {
private val db = Room.databaseBuilder(context, AutocompleteDatabase::class.java, "autocomplete").build()
suspend fun preload() {
val popularPrefixes = autocompleteApi.getPopularPrefixes(limit = 500)
db.suggestionDao().insertAll(popularPrefixes)
}
suspend fun getSuggestions(prefix: String): List<String> {
// сначала проверяем локальный кэш
val cached = db.suggestionDao().getSuggestions(prefix)
if (cached.isNotEmpty()) return cached
// если нет в кэше — запрос на сервер
return autocompleteApi.getSuggestions(prefix)
}
}
Debounce и cancellation на клиенте
Каждый символ не должен триггерить новый запрос. Debounce 150–200 мс + отмена предыдущего in-flight запроса:
// iOS: debounced автодополнение с cancellation
class SearchViewModel: ObservableObject {
@Published var suggestions: [String] = []
private var searchTask: Task<Void, Never>?
func onQueryChanged(_ query: String) {
searchTask?.cancel()
guard query.count >= 2 else { suggestions = []; return }
searchTask = Task {
try? await Task.sleep(nanoseconds: 150_000_000) // 150ms debounce
guard !Task.isCancelled else { return }
let results = try? await autocompleteService.getSuggestions(query)
await MainActor.run {
suggestions = results ?? []
}
}
}
}
Task.isCancelled проверяется после debounce — если пользователь продолжил ввод, предыдущая задача уже отменена.
Логирование выбора подсказки
Когда пользователь тапает на подсказку, логируем: позицию в списке, prefix при котором она была выбрана, итоговый запрос. Эти данные — обучающая выборка для следующей версии ранжировщика.
Процесс работы
Анализ поисковых логов: топ-1000 запросов, паттерны опечаток, язык/транслитерация.
Настройка Elasticsearch Completion Suggester с fuzzy matching.
Разработка персонализированного ранжировщика подсказок.
Реализация on-device кэша + debounce логики на iOS/Android.
Ориентиры по срокам
ES Completion Suggester без персонализации — 2–3 дня. С персонализированным ранжировщиком и on-device кэшем — 1,5–2 недели.







