AACSearch
Руководства

A/B-эксперименты

Поисковые эксперименты делят живой трафик между двумя профилями поиска с закреплением за посетителем, показывают клики и uplift и позволяют принять или откатить победителя. Также описано старое разделение через settings.abTest.

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

Поисковые эксперименты

Поисковый эксперимент относится к одному индексу и сравнивает две ветки, control и treatment, каждая из которых указывает на сохранённый профиль поиска (или на профиль индекса по умолчанию, если профиль не задан). Пока эксперимент в статусе running, каждый поиск через REST и каждый поиск виджета на этом индексе получает профиль одной из веток.

curl -sS -X POST "$AAC_API/experiments?index=documentation" \
  -H "Authorization: Bearer $AAC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"weights v2","controlProfileId":"prof_control","treatmentProfileId":"prof_weights_v2","treatmentWeight":50,"status":"running"}'
ПолеЗначение
nameОбязательное, до 200 символов
controlProfileId / treatmentProfileIdПрофили этого индекса; null — профиль индекса по умолчанию
treatmentWeightДоля единиц в treatment, 0–100, по умолчанию 50. 0 и 100 отправляют всех в одну ветку, не останавливая эксперимент
controlName / treatmentNameПодписи, по умолчанию A и B
statusdraft (по умолчанию), running, paused, finished

Все маршруты /experiments принимают ?index=<slug> и требуют ключ с settings:write. В консоли то же самое доступно на странице индекса Эксперименты.

МаршрутЧто делает
GET /experimentsСписок экспериментов индекса
POST /experimentsСоздать эксперимент
PATCH /experiments/{id}Изменить поля или статус
POST /experiments/{id}/promoteСделать профиль treatment (или control, если treatment не задан) профилем индекса по умолчанию и завершить эксперимент
POST /experiments/{id}/rollbackВернуть профиль control по умолчанию и завершить эксперимент
GET /experiments/{id}/reportПо веткам: назначения, поиски, клики, CTR, конверсии, доля конверсий, выручка и uplift; days 1–90, по умолчанию 14

Смена статуса отправляет вебхуки experiment.started и experiment.finished.

Кто попадает в какую ветку

Назначение — это хэш ключа единицы, поэтому одна и та же единица всегда получает одну и ту же ветку на всех инстансах и после деплоев. Ключ единицы — первое из:

  1. session_id — ваш постоянный анонимный id посетителя (client_id в виджете),
  2. user_id,
  3. visit_id,
  4. иначе API-ключ (REST) или издатель сессии встраивания (виджет) — весь такой трафик попадает в одну ветку.

Передавайте стабильный session_id с фронтенда; без него весь серверный ключ — одна единица. Первое назначение единицы сохраняется и больше не меняется, так что посетитель остаётся в своей ветке на всё время эксперимента.

На индексе одновременно обслуживается только один эксперимент: если в статусе running несколько, используется один из них. Явный profile= в запросе всегда важнее эксперимента — так консоль показывает вариант в превью, не мешая эксперименту.

Как читать отчёт

Каждый поиск, решение по которому принял эксперимент, записывается в лог вместе с веткой, а каждое событие несёт query_id поиска, из которого оно пришло, — поэтому атрибуция точна для любого вида единицы (ключ, посетитель, пользователь, визит). По каждой ветке отчёт показывает назначения (единицы, попавшие в ветку), поиски, обслуженные веткой, и клики, конверсии и выручку (revenue_cents — сумма value_cents конверсий) по этим поискам. Передавайте query_id в событиях — события без него не атрибутируются. ctr и conversion_rate считаются на поиск; uplift — относительная разница CTR treatment к control, либо null, пока у одной из сторон нет данных. Поиски, записанные до появления этой атрибуции, не учитываются.

Старый механизм: settings.abTest

У индекса по-прежнему может быть старое разделение по ключам в настройках. Оно применяется, только если не запущен ни один эксперимент и запрос не указывает профиль. Для новых задач используйте поисковые эксперименты; остальная часть страницы описывает старый механизм.

Как работает разделение

Эксперимент — свойство индекса, хранится в его настройках:

{
	"abTest": {
		"enabled": true,
		"profiles": ["control", "weights-v2"],
		"split": 50
	}
}
ПолеЗначение
enabledВключает эксперимент
profilesРовно два слага профилей; оба должны существовать у индекса
splitПроцент корзин, отправляемых в первый профиль, по умолчанию 50

Назначение детерминированное, а не случайное для каждого запроса:

const bucket = createHash("sha256").update(`${apiKey.keyId}:${index.id}`).digest()[0] % 100;
const profile = bucket < (abTest.split ?? 50) ? abTest.profiles[0] : abTest.profiles[1];

Отсюда два следствия, и оба важны:

  • Конкретный ключ всегда попадает в одну корзину. Пользователи не перескакивают между вариантами посреди сессии, а закешированная страница не противоречит последнему результату.
  • Единица рандомизации — ключ, а не посетитель. Один серверный ключ означает одну корзину для всего его трафика: годится для сравнения конфигураций между тенантами, но бесполезно для разбиения по пользователям. Для последнего выпускайте по ключу на когорту или передавайте profile явно.

Явно переданный profile всегда побеждает A/B-корзину — так можно посмотреть вариант в консоли, не сбивая эксперимент.

Чтение результатов

curl -sS "$AAC_API/analytics/experiments?days=30&index=documentation" \
  -H "Authorization: Bearer $AAC_KEY"
{
	"days": 30,
	"index": "documentation",
	"buckets": [
		{
			"profile": "control",
			"searches": 8240,
			"zero_rate": 0.11,
			"avg_latency_ms": 18,
			"avg_top_score": 1720.4
		},
		{
			"profile": "weights-v2",
			"searches": 8106,
			"zero_rate": 0.07,
			"avg_latency_ms": 19,
			"avg_top_score": 2145.8
		}
	],
	"request_id": "req_3b5d7f9a1c3e5b7d9f1a3c5e"
}
ПолеЗначение
profileСлаг профиля, с которым выполнялись запросы
searchesЧисло запросов за период, отнесённых к профилю
zero_rateДоля запросов, вернувших пустую выдачу
avg_latency_msСредняя задержка по профилю
avg_top_scoreСредняя оценка первого попадания — грубый прокси релевантности

days ограничивается 1–90. Без index корзины агрегируются по всем индексам организации — удобно, когда одна и та же пара профилей применена везде.

В выборку попадают только запросы, выполненные под профилем, поэтому суммы будут меньше общего числа поисков: органический трафик по ключам вне эксперимента, вызовы semantic с явным профилем, предпросмотр в консоли и запросы подсказок сюда не входят.

Рабочий порядок

  1. Создайте профиль-кандидат. Скопируйте контрольный и измените одну вещь — обычно query_by_weights или num_typos, иногда facet_by.
  2. Посмотрите на него. Отправьте ?profile=weights-v2 на реальных запросах в консоли; GET /indexes/{slug}/widget-config перечисляет профили и их конфигурации, если нужно сравнить их программно.
  3. Включите abTest с обоими слагами и делением 50/50. Дайте ему поработать минимум неделю: за день трафика 11% и 7% пустых выдач не различить.
  4. Прочитайте /analytics/experiments и сверьтесь с /analytics/overview, чтобы убедиться, что структура общего трафика за окно не изменилась.
  5. Решите и уберите за собой. Переведите победителя в профиль по умолчанию, выключите abTest, а проигравшего оставьте как документированный путь отката.

Подводные камни

  • Два профиля, не больше. Схема — это пара плюс процент; для трёх вариантов нужен второй индекс или другой механизм.
  • Неравный трафик нормален. Хеширование равномерно по ключам, а не по запросам: при трёх активных ключах деление 50/50 может дать 2:1.
  • avg_top_score нельзя сравнивать между разными запросами. Он сравнивает два профиля на одном и том же наборе запросов; сравнение с прошлым месяцем — это сравнение с другими запросами.
  • Задержка — не метрика решения. Гибридный профиль будет медленнее и лучше; решайте по релевантности, а потом проверяйте, приемлема ли цена по задержке.
  • Правка профиля посреди эксперимента его обнуляет. Корзины вычисляются по сохранённой конфигурации во время запроса, поэтому изменение применяется к идущему трафику и размывает сравнение.

Где это настраивается

abTest живёт в объекте настроек индекса. Публичный REST API его не предоставляет: настройками индекса владеет консоль, а публичная поверхность по отношению к ним только для чтения. Задавайте его через настройки индекса в консоли или, для автоматизации, через oRPC-процедуры консоли из аутентифицированной сессии.

On this page