Статический главный экран — компромисс, который не удовлетворяет никого. Пользователи, которые видят нерелевантный контент, уходят на второй экран за 5 секунд. Каждый потерянный пользователь — это потеря потенциальной конверсии. Вместо усреднённого профиля мы создаём динамическую структуру, адаптирующуюся под каждого в реальном времени. Наш опыт — 5+ лет в мобильной разработке и 50+ реализованных проектов. Результат — рост вовлечённости и конверсии на 20–40% в зависимости от ниши.
Мы внедряем AI-персонализацию главного экрана: заменяем жёсткий layout на server-driven UI с ранжированием секций через контекстный бандит. Реализация требует тесной интеграции клиентской логики на Swift/Kotlin и серверного алгоритма ранжирования. Мы используем стек: Vowpal Wabbit для обучения, gRPC для доставки конфигурации, и SwiftUI/Compose для рендеринга. Персонализация касается не только порядка секций, но и контента внутри них, а также промо-баннеров. В итоге пользователь получает интерфейс, созданный специально для него.
Как AI персонализирует главный экран?
Уровни персонализации
Первый уровень — какие секции показывать и в каком порядке. Второй — контент внутри каждой секции. Третий — персонализированные баннеры и CTA.
Секции и их порядок
Пользователь, который никогда не открывал «Акции», не должен видеть промо-баннер на первом экране. Тот, кто регулярно смотрит истории — получает их в топе.
Персонализация порядка секций — задача Contextual Bandit. Каждая секция — это «рука бандита». Награда — клик или время взаимодействия. Алгоритм UCB или Thompson Sampling балансирует exploration (показываем секции, по которым мало данных) и exploitation (показываем секции с высоким историческим CTR).
from vowpalwabbit import pyvw
vw = pyvw.vw("--cb_explore_adf --epsilon 0.1 --quiet")
def get_section_order(user_features: dict, sections: list[str]) -> list[str]:
context = f"|user age_group:{user_features['age_group']} time_of_day:{user_features['hour']}"
actions = "\n".join(
f"|section name:{s} historical_ctr:{user_features.get(f'ctr_{s}', 0.1):.2f}"
for s in sections
)
example = f"{context}\n{actions}"
scores = vw.predict(example)
return [s for _, s in sorted(zip(scores, sections))]
Как внедрить контекстный бандит?
Чтобы внедрить контекстный бандит, выполните следующие шаги:
- Соберите логи взаимодействия: клики, просмотры, время сессии.
- Определите признаки пользователя: возрастная группа, время суток, история покупок.
- Выберите алгоритм: UCB (Upper Confidence Bound) или Thompson Sampling.
- Обучите модель на исторических данных с использованием Vowpal Wabbit.
- Интегрируйте с server-driven UI, передавая ранжированный список секций.
- Запустите A/B-тест для валидации.
Контент внутри секций
«Рекомендованные товары», «Для вас», «Продолжить просмотр» — каждый блок наполняется через recommendation API (коллаборативная фильтрация, контентная фильтрация или гибрид).
Персонализированные баннеры и CTA
Промо-баннеры с разным текстом и изображениями под сегменты. Сегментация через кластеризацию (KMeans) или правиловую логику: частые покупатели видят «Новинки», давно не заходившие — «Мы скучали, вот скидка».
Почему AI-персонализация эффективнее правил?
Правиловая персонализация (сегменты + ручные триггеры) работает, но не масштабируется. AI-персонализация увеличивает CTR в 3 раза по сравнению с правилами, а retention на 7-й день — на 15%. Разница особенно заметна при большом количестве секций (более 5) и разнородной аудитории.
| Параметр | Правиловая персонализация | AI-персонализация (contextual bandit) |
|---|---|---|
| CTR баннеров | 3–6% | 9–15% |
| Количество просматриваемых секций | 2–3 | 4–6 |
| Время адаптации под нового пользователя | 1–2 дня | < 1 дня |
| Сложность поддержки | Низкая | Средняя |
Пример конфигурации параметров бандита
{
"algorithm": "ucb",
"epsilon": 0.1,
"reward": "click",
"exploration_bonus": 1.96,
"update_frequency": "daily"
}
Почему server-driven UI обязателен?
Жёстко хардкодить структуру главного экрана в мобильном клиенте — плохая практика. Server-driven UI позволяет менять набор и порядок секций без выпуска новой версии приложения. Конфигурация приходит с сервера при каждом открытии.
// Android: HomeScreen конфигурация с сервера
data class HomeScreenConfig(
val sections: List<SectionConfig>
)
data class SectionConfig(
val type: SectionType, // BANNER, PRODUCTS, STORIES, CATEGORIES, CONTINUE_WATCHING
val title: String?,
val items: List<HomeItem>,
val layout: LayoutType // HORIZONTAL_SCROLL, GRID, CAROUSEL
)
class HomeViewModel(private val api: HomeApi) : ViewModel() {
private val _config = MutableStateFlow<HomeScreenConfig?>(null)
val config = _config.asStateFlow()
init {
viewModelScope.launch {
_config.value = api.getPersonalizedHome(userId = currentUser.id)
}
}
}
@Composable
fun HomeScreen(config: HomeScreenConfig) {
LazyColumn {
items(config.sections) { section ->
when (section.type) {
SectionType.BANNER -> BannerSection(section)
SectionType.PRODUCTS -> ProductsSection(section)
SectionType.STORIES -> StoriesSection(section)
SectionType.CONTINUE_WATCHING -> ContinueWatchingSection(section)
else -> {}
}
}
}
}
Jetpack Compose + LazyColumn с динамическим рендерингом секций — чистое решение. Добавление нового типа секции — только новая when-ветка без изменения layout-логики. Аналогично на iOS через SwiftUI ForEach и @ViewBuilder фабрику.
Как обеспечить мгновенный старт?
Конфигурация кэшируется локально. При следующем открытии — мгновенно показываем прошлую конфигурацию, параллельно загружаем новую. Это паттерн stale-while-revalidate: пользователь никогда не видит пустой экран.
// iOS: stale-while-revalidate для конфигурации главного экрана
func loadHomeConfig() {
if let cached = configCache.load() {
homeConfig = cached
}
Task {
let fresh = try await api.getPersonalizedHome()
configCache.save(fresh)
homeConfig = fresh
}
}
Что входит в работу
| Этап | Результат | Срок |
|---|---|---|
| Аудит текущей структуры и сигналов персонализации | Отчёт с аналитикой и рекомендациями | 2–3 дня |
| Проектирование server-driven UI протокола | Спецификация формата конфигурации, типы секций | 3–5 дней |
| Реализация алгоритма ранжирования секций | Contextual bandit или правила с A/B-тестом | 1–2 недели |
| Разработка клиентского рендерера | Код на Kotlin/Swift для динамического отображения | 1–3 недели |
| Настройка метрик и дашбордов | Отслеживание CTR, скролла, retention | 2–3 дня |
Результаты и метрики
Сравнение статического и AI-персонализированного главного экрана:
| Параметр | Статический экран | AI-персонализированный |
|---|---|---|
| Количество просматриваемых секций | 1–2 | 4–6 |
| CTR баннеров | 2–5% | 8–15% |
| Возврат на следующий день | 30–40% | 50–65% |
| Время до первого клика | 8–12 с | 3–5 с |
Сроки ориентировочно
Server-driven UI с простой персонализацией на правилах — 1–2 недели. Contextual bandit для ранжирования секций + полный динамический рендерер — 3–5 недель. Стоимость рассчитывается индивидуально по вашему объёму приложения.
Если вы хотите увеличить вовлечённость — закажите аудит персонализации. Получите консультацию — наши инженеры оценят ваш проект за 2 дня. Свяжитесь с нами, чтобы обсудить детали.







