Зачем A/B, если есть offline-оценка
Логичный вопрос: мы же уже умеем сравнивать версии на датасете (offline + judge). Зачем ещё и эксперимент на людях? Потому что offline и A/B отвечают на разные вопросы, и второй — решающий перед раскаткой.
| Offline-сравнение | A/B тест | |
|---|---|---|
| На чём | Фиксированный датасет | Живой трафик, реальные юзеры |
| Метрика | Judge/scorers на golden | Поведение: успех, retention, 👍, конверсия |
| Ловит | Регрессию на известных кейсах | Реальный эффект на реальных людях |
| Роль | Быстрый фильтр «не хуже?» | Финальное решение «раскатывать?» |
Правильный порядок — воронка: offline отсеивает заведомо плохие версии дёшево и быстро (не гоняем в прод мусор), а прошедшие проверяем A/B-тестом на реальном трафике. Offline экономит эксперименты, A/B выносит вердикт. Именно поэтому этот урок идёт после offline-метрик и judge.
Метрики всегда колеблются: сегодня успех 71%, завтра 73% — даже без всяких изменений, просто из-за того, какие пользователи пришли. Увидеть «74% против 71%» и решить «новый промпт лучше» — классическая ошибка: разница может быть чистым шумом. Вся статистика A/B-теста — это машинерия, чтобы отличить реальный эффект от случайного колебания. Без неё ты подбрасываешь монетку и называешь это решением.
Постановка эксперимента
Надёжный A/B тест начинается не с кода, а с дизайна. Четыре решения до запуска — иначе результат будет неинтерпретируемым.
- ① Гипотеза. Конкретное, проверяемое утверждение: «новый промпт повысит task success rate». Не «сделаем лучше» — а что именно и на какой метрике.
- ② Одна главная метрика (OEC — overall evaluation criterion). Ты сравниваешь по одной заранее выбранной метрике. Гнаться за десятью — путь к самообману (об этом ниже).
- ③ Control и treatment. Пользователи случайно делятся на группы; между A и B меняется только промпт — иначе не поймёшь, что дало эффект. Control — текущий прод (baseline).
- ④ Размер выборки и длительность — заранее. Сколько нужно данных и как долго крутить — считается до старта (следующий раздел), а не «пока не понравится».
Если в treatment ты поменял и промпт, и модель, и температуру — при любом результате не узнаешь, что сработало (или навредило). Классический принцип эксперимента: одно изменение между control и treatment. Хочешь проверить три идеи — три отдельных теста (или многовариантный тест с бóльшей выборкой), а не всё в кучу.
Статистическая значимость и p-value
Сердце A/B-теста — ответ на вопрос: «разница между A и B реальна или это случайность?». Инструмент — статистический тест, а его результат — p-value. Объясним без формул.
Представь, что на самом деле разницы нет (промпты одинаковы). p-value — это вероятность увидеть наблюдаемую разницу (или больше) чисто случайно, если бы разницы правда не было. Малый p-value = «такое совпадение слишком невероятно, значит разница, скорее всего, настоящая».
Гипотеза H0: «разницы нет» (промпты одинаковы) p-value = P(увидеть такую разницу | H0 верна, т.е. эффекта НЕТ) p = 0.60 → такое запросто бывает случайно → НЕ значимо (не верим в эффект) p = 0.03 → случайно почти не бывает → значимо (эффект реальный) Порог обычно α = 0.05: p < 0.05 → «статистически значимо» ⚠️ p < 0.05 НЕ значит «вероятность 95%, что B лучше» и НЕ значит «эффект большой» — только «вряд ли это чистый шум»
Порог α = 0.05 — соглашение: готовы в 5% случаев принять шум за эффект (ложноположительный). Для бинарной метрики (успех/нет) считают, например, тест пропорций (z-test / хи-квадрат); для непрерывной (латентность, длина) — t-тест.
from statsmodels.stats.proportion import proportions_ztest
# control (A): 710 успехов из 1000 · treatment (B): 745 из 1000
successes = [745, 710] # B, A
totals = [1000, 1000]
stat, p_value = proportions_ztest(successes, totals)
print(f"p-value = {p_value:.4f}") # напр. 0.0083
if p_value < 0.05:
print("Разница значима — B, вероятно, реально лучше")
else:
print("Не значимо — возможно, просто шум. Нужно больше данных или эффекта нет")
# ⚠️ значимость ≠ важность: 74.5% vs 71% значимо на 1000+1000,
# но стоит ли эффект +3.5% усилий/рисков — отдельный продуктовый вопрос
Две ловушки. На огромной выборке значимым станет даже +0.1% — статистически реально, но практически бесполезно (эффект есть, но крошечный). И наоборот: на маленькой выборке даже большая разница может быть незначимой. Поэтому смотри и p-value (реален ли эффект), и размер эффекта с доверительным интервалом (насколько он велик). «Значимо» отвечает «не шум ли», но не «стоит ли оно того».
Сколько нужно данных
Частый провал: запустили тест, через день «B лучше!», раскатали — а на самом деле данных было слишком мало, и разница была шумом. Размер выборки считают заранее, исходя из трёх величин:
- Baseline — текущее значение метрики (например, success 71%).
- MDE (minimum detectable effect) — минимальный эффект, который хочешь уметь замечать (например, +2 п.п.). Чем меньше эффект хочешь ловить, тем больше данных нужно.
- Мощность и α — обычно power 80%, α 0.05.
Грубая интуиция: хочешь ловить БОЛЬШОЙ эффект (+10%) → нужно МАЛО данных хочешь ловить МАЛЫЙ эффект (+1%) → нужно ОЧЕНЬ МНОГО данных редкое событие (конверсия 2%) → нужно больше, чем для частого (успех 70%) → считай N ДО старта (power analysis), фиксируй длительность, и НЕ останавливай тест раньше, даже если «уже видно победителя»
Самая коварная ошибка: смотреть на p-value каждый час и остановить тест, как только он «пересёк 0.05». Так ты почти гарантированно поймаешь случайный момент, когда шум дал значимость — ложное открытие. Правило: зафиксируй N и длительность заранее и дождись конца. Хочешь останавливаться раньше легально — нужны специальные методы (sequential testing / поправки), а не «глазами по дашборду».
Rollout: безопасная раскатка
Тест показал: B значимо лучше. Не спеши включать его всем сразу — раскатывай поэтапно, чтобы поймать проблемы, которые A/B не увидел (редкие кейсы, всплеск latency/cost, деградация на определённом сегменте).
Поэтапный rollout (canary → gradual → full): 1% → наблюдаем guardrails (error rate, latency, cost, quality) → ок? ↓ 10% → дольше, шире аудитория → ок? ↓ 50% → половина трафика → ок? ↓ 100% (полная раскатка) ← в любой момент можно откатиться Guardrail-метрики: даже если главная метрика ↑, откат если error/latency/cost/quality ↓
Ключевые элементы безопасной раскатки:
- Canary — сначала крошечная доля (1–5%): ловим катастрофы малой кровью.
- Guardrail-метрики — набор «предохранителей» помимо главной метрики: рост error rate, latency, cost, падение quality → автостоп/откат, даже если основная метрика выросла.
- Быстрый откат (rollback) — возможность мгновенно вернуть control. Промпты, вынесенные из кода (prompt management из урока про Langfuse), позволяют откатиться без передеплоя.
- Мониторинг всё время — дашборды из раздела Observability работают именно здесь.
A/B тест — это оркестровка всего раздела: метрику считаешь judge'ом/scorer'ом (offline-метрики, LLM-judge), guardrails и rollout смотришь на дашбордах (Observability), а сами версии промптов переключаешь через prompt management. A/B — не отдельный инструмент, а способ принять решение по данным, используя всё, что ты уже построил.
Типичные ошибки
«74% > 71%, значит B лучше» — возможно, чистый шум. Всегда проверяй значимость и учитывай размер выборки, иначе раскатываешь случайность.
Подглядывание за p-value и остановка «когда стало значимо» ловит случайные пики. Фиксируй N и длительность заранее, дожидайся конца.
Смотришь 20 метрик — одна «значима» случайно (проблема множественных сравнений). Меняешь всё сразу — не знаешь, что сработало. Одна главная метрика, одно изменение.
Победа в A/B не гарантирует отсутствие редких катастроф. Раскатывай поэтапно с guardrail-метриками и возможностью мгновенного отката.
Шпаргалка
A/B ТЕСТ = сравнение версий на ЖИВОМ трафике (финальный вердикт «раскатывать?»)
воронка: offline отсеивает плохое дёшево → A/B выносит вердикт на реальных юзерах
главный враг = СЛУЧАЙНОСТЬ (метрики колеблются сами по себе)
ПОСТАНОВКА (4 решения ДО старта):
① гипотеза (что именно, на какой метрике)
② ОДНА главная метрика (OEC)
③ control (старый) vs treatment (новый), случайное деление, ОДНО изменение
④ размер выборки + длительность — заранее
ЗНАЧИМОСТЬ / p-value:
H0 = «разницы нет» · p = P(такая разница | эффекта нет)
p<0.05 → значимо (вряд ли шум) · тест пропорций (бинарн.) / t-тест (непрерывн.)
⚠️ p<0.05 ≠ «95% что B лучше» · значимость ≠ размер эффекта (смотри ещё CI/effect size)
большая выборка → значим даже мусорный +0.1%
РАЗМЕР ВЫБОРКИ: baseline + MDE (мин. эффект) + power(80%)/α(0.05) → power analysis
меньше эффект/реже событие → больше N · считать ДО старта
❌ PEEKING: не смотреть p каждый час и не стопать «когда значимо» → ложное открытие
ROLLOUT (поэтапно): canary 1% → 10% → 50% → 100%, откат в любой момент
GUARDRAILS: откат если error/latency/cost↑ или quality↓ (даже если гл. метрика ↑)
prompt management → откат без передеплоя · дашборды → мониторинг
ГРАБЛИ: 2 числа без теста · peeking · много метрик/изменений · 100% без canary/guardrails
Практическое задание
Задание: спроектируй A/B тест промпта
- Сформулируй гипотезу для новой версии промпта агента из модулей 03–05: что меняешь и какую одну метрику ожидаешь улучшить.
- Опиши дизайн: как делишь трафик, что именно control и treatment, какое единственное изменение между ними.
- Возьми числа: A = 680/1000 успехов, B = 720/1000. Посчитай p-value (
proportions_ztest). Значима ли разница? А если бы было по 100 наблюдений с той же долей? - Объясни своими словами, что означает полученный p-value. Что он НЕ означает?
- Прикинь размер выборки: хочешь уверенно ловить эффект +2 п.п. при baseline 70%. Данных нужно много или мало? А для +10 п.п.?
- Спроектируй rollout: этапы раскатки и 3 guardrail-метрики, при падении которых ты откатишься, даже если главная метрика выросла.
- Со звёздочкой: объясни на примере, почему «подглядывание» (остановка теста, как только p<0.05) завышает долю ложных открытий. Как этого избежать?
Что дальше
A/B ловит, стал ли билд лучше в среднем. Но есть отдельная опасность — регрессия: правка, улучшив общее, тихо ломает то, что раньше работало. Защита от этого — regression testing на golden datasets: финальный урок раздела про то, как не дать качеству деградировать при каждом изменении промпта.