Как тестировать рекомендации через внешний A/B-инструмент

Если вы уже распределяете пользователей в стороннем A/B-инструменте, используйте его группу для выбора selector Gravity Field. Для группы A запрашивайте одну рекомендательную кампанию, для B — другую; обе показывайте в одном месте сайта или приложения.

Внешний инструмент назначает и сохраняет группу, собирает данные эксперимента и рассчитывает его результат. Gravity Field подбирает рекомендации выбранной кампании и принимает события для своей аналитики. Ваша интеграция связывает эти части: выбирает selector, отображает контент и отправляет события каждой системе.

1. Подготовьте две кампании в GF

Создайте две API-кампании с рекомендациями или две мобильные inline-кампании, если используете готовый inline-компонент SDK.

Внешняя группа Кампания GF Selector — пример
A Рекомендации A home-recs-a
B Рекомендации B home-recs-b

Имена условные: согласуйте собственные selector с разработчиком. Используйте отдельный selector для каждой кампании, без других активных кампаний с тем же именем в секции.

Для обеих кампаний:

  • Настройте одинаковые placement, контекст, аудиторию, расписание и число товаров. Меняйте только то, что сравнивает тест, например рекомендательную стратегию; UI и правила обработки ошибок оставьте одинаковыми.
  • В сценарии оставьте один вариант с 100% трафика. Выберите ручное распределение, не включайте «Автопилот» и дополнительную контрольную вариацию. Внешние 50/50 относятся к выбору между кампаниями; внутри каждой кампании повторное 50/50 не нужно.
  • Проверьте все ограничения охвата: таргетинг, дополнительные условия распределения, расписание и лимиты показов. У двух кампаний они должны соответствовать одному дизайну теста. Selector выбирает кампанию, но не отменяет её условия и внутренний выбор вариации.
  • Если проект использует глобальную контрольную группу, заранее определите её участие. Например, исключите её из аудитории этого эксперимента до назначения A/B. Не меняйте общую контрольную группу проекта ради теста без согласованного решения.
  • Опубликуйте сценарий/вариант и активируйте обе кампании. Проверьте выдачу каждой на тестовом пользователе.

В одной кампании могут использоваться рекомендации Gravity Field, в другой — клиентские, если такая стратегия уже подключена. Способ распределения пользователей и вызова selector остаётся тем же.

2. Закрепите внешнюю группу за пользователем

Создайте эксперимент в вашем A/B-инструменте и задайте доли A/B. Получайте сохранённое назначение для стабильного идентификатора пользователя. Не вычисляйте случайную группу заново при каждом просмотре, запросе или перезапуске приложения.

Согласуйте поведение при входе в аккаунт, смене устройства и для анонимных посетителей. Группа должна следовать правилам внешнего инструмента; идентификацию в GF продолжайте выполнять обычным способом. Не создавайте новый GF-профиль ради выбора A или B.

Пока внешнее назначение неизвестно, не запрашивайте обе кампании и не назначайте группу случайно локально. Используйте заранее согласованный сценарий ожидания или базовый интерфейс вне теста.

3. Выберите один selector и запросите контент

Пример логики интеграции: assignedGroup приходит из вашего A/B-инструмента. Это переменная приложения, а не новое поле API Gravity Field.

function selectorForGroup(assignedGroup) {
  if (assignedGroup === "A") return "home-recs-a";
  if (assignedGroup === "B") return "home-recs-b";
  return null; // Назначения нет: блок эксперимента пока не загружаем.
}

Передайте выбранный selector стандартному пути вашей интеграции.

В запросе /choose передайте один элемент data[]. Ниже фрагмент для группы A; для B заменяется только selector:

{
  "data": [{ "selector": "home-recs-a" }],
  "options": {
    "isBuildEngagementUrl": true,
    "isImplicitImpression": false
  }
}

Остальные поля (sec, user, ctx, device) заполните по стандартной интеграции. Сохраняйте пользователя и сессию, передавайте одинаковый контекст реального placement для обеих групп. Просмотр страницы отправляйте через /visit или isImplicitPageview один раз, без дублирования. Здесь isImplicitImpression: false отключает учёт impression при выдаче: приложение отправит его при отображении блока через URL из ответа.

После получения группы, внутри coroutine:

val selector = when (assignedGroup) {
    "A" -> "home-recs-a"
    "B" -> "home-recs-b"
    else -> null
}
if (selector != null) {
    val response = GravitySDK.instance.getContentBySelector(
        org.json.JSONObject.quote(selector), pageContext
    )
    // Передайте response существующему рендереру вашего приложения.
}

В Android 1.0.4 кодируйте обычное имя selector через JSONObject.quote ровно один раз.

Сигнатура и структура ответа: Android SDK.

После получения группы, в async-контексте:

let selector: String? = assignedGroup == "A" ? "home-recs-a"
    : assignedGroup == "B" ? "home-recs-b" : nil
if let selector = selector {
    let response = await GravitySDK.instance.getContentBySelector(
        selector: selector, pageContext: pageContext
    )
    // Передайте response существующему рендереру вашего приложения.
}

Сигнатура и структура ответа: iOS SDK.

Если используете встроенный inline-компонент, передайте ему выбранный selector вместо ручного запроса; подключение и tracking описаны в inline-гайде. Выберите один способ загрузки. Запросы обеих кампаний «для сравнения», общий group selector или переключение на selector B при пустом ответе A смешивают условия теста. При пустой выдаче/ошибке сохраняйте внешнюю группу и применяйте согласованный fallback.

4. Передавайте показы, клики и конверсии

Назначение A/B, получение ответа и фактический показ блока — разные события. Заранее договоритесь, что означает exposure во внешнем инструменте и какая аудитория входит в расчёт. Не отмечайте внешний exposure только потому, что /choose вернул ответ. В SDK isImplicitImpression по умолчанию включён: GF может учитывать impression уже при выдаче. Для ручного учёта impression при показе настройте Options(isImplicitImpression = false) на Android или Options(isImplicitImpression: false) на iOS через setOptions(...), сохранив остальные настройки интеграции. Эти опции действуют на последующие запросы SDK, поэтому согласуйте режим для приложения целиком.

  • В GF: при ручном рендеринге отправляйте content/product engagement с tracking-данными именно полученного ответа. В SDK используйте sendContentEngagement(...) / sendProductEngagement(...) с исходными campaign, content, slot; встроенный UI берёт часть tracking на себя. При клике на свою товарную карточку отправьте product click и выполните навигацию приложения.
  • В GF: продолжайте отправлять просмотры, добавления в корзину и покупки через стандартные события или trackView(...) / triggerEvent(...) SDK. Сохраняйте идентичность пользователя, контекст и SKU из фида. Настройте одинаковые цели и атрибуцию для двух кампаний.
  • Во внешний инструмент: отдельно передавайте назначение, exposure и выбранные метрики средствами этого инструмента. Сохраняйте связь «эксперимент → пользователь → группа → selector/кампания» в своей аналитике. GF не отправляет результаты стороннему A/B-инструменту автоматически.

Отчёты GF покажут работу каждой кампании. Итог A/B между двумя внешними группами рассчитывайте в выбранной системе экспериментов с её правилами аудитории и метрик; это отдельный эксперимент от встроенного A/B внутри одного сценария GF.

5. Проверьте тест перед запуском

  • Тестовый пользователь A запрашивает только home-recs-a, B — только home-recs-b; повторное посещение сохраняет группу.
  • Обе кампании дают ожидаемые товары в одном placement и контексте, без второго случайного распределения и конфликтующего таргетинга.
  • Фактические показы, товарные клики и покупки учитываются без дублей; группа во внешней аналитике совпадает с выбранным selector.
  • Пустые ответы, ошибки и fallback не переводят пользователя между A и B. Отдельно видны назначенные пользователи и реально показанные блоки.