Изменили промпт в AI-боте - стало лучше или хуже? Если ответ не на данных, а на ощущениях - вы рискуете. Одно неудачное изменение может оттолкнуть клиентов, а вы даже не поймёте, что именно пошло не так.
Разберём на примере интернет-магазина с ботом поддержки на 50 000 запросов в день. Вы добавили в промпт «сначала выразить сочувствие, потом предложить решение». Кажется логичным. Но как проверить, что клиенты стали довольнее, а не просто ждут дольше? Без эксперимента - никак.
A/B-тест - это сравнение двух версий на части пользователей, чтобы понять, какая работает лучше. В AI-приложениях он сложнее, чем для кнопки: нет простого «да/нет», ответы непредсказуемы, один промпт влияет на десятки сценариев.
Почему A/B-тест для AI отличается от обычного
Классический A/B-тест на кнопку прост: показали красную кнопку половине пользователей, синюю - другой, посчитали клики. Чёткий результат, полная предсказуемость.
Для промпта AI всё иначе.
Первое: нет чёткого результата. Качество ответа - это шкала от плохого до хорошего. «Стало лучше» измеряется через оценку AI-судьи (0.0-1.0), количество положительных откликов от пользователей, процент выполненных задач. Эти показатели могут идти в разные стороны одновременно.
Второе: непредсказуемость. При нестрогом режиме генерации один и тот же запрос даёт разные ответы. Для статистически надёжного вывода нужна большая выборка, чем для кнопки.
Третье: отложенный сигнал. Пользователь не всегда сразу понимает, правильный ли ответ дал бот. Реальные данные о качестве приходят через 1-7 дней.
Для бизнеса это значит: не торопитесь с выводами. Дайте эксперименту набрать достаточно данных - обычно от нескольких часов до нескольких суток.
Shadow mode: тестирование без риска
Shadow mode (режим тени) - это когда новый промпт или модель работает параллельно с действующей системой, но пользователь видит только действующий ответ. Оба ответа записываются и сравниваются.
Это позволяет протестировать изменение на реальном трафике без малейшего риска для пользователей. Если новый вариант окажется хуже - никто из клиентов этого не заметит.
Следующий код запускает два запроса к AI одновременно: один действующий (пользователь видит), один экспериментальный (записывается для анализа). Пользователь не ждёт дольше обычного, потому что оба запроса выполняются параллельно:
import asyncio
import anthropic
from langfuse import Langfuse
client = anthropic.Anthropic()
langfuse = Langfuse()
async def shadow_mode_request(user_message: str, user_id: str) -> str:
# Запускаем оба варианта параллельно
production_task = asyncio.create_task(
call_model(user_message, PRODUCTION_PROMPT, "боевая среда")
)
shadow_task = asyncio.create_task(
call_model(user_message, EXPERIMENTAL_PROMPT, "shadow")
)
production_response, shadow_response = await asyncio.gather(
production_task, shadow_task
)
# Записываем оба для сравнения
langfuse.score(trace_id=shadow_task.trace_id,
name="shadow_vs_production",
value=compare_responses(production_response, shadow_response))
# Пользователь видит только действующий ответ
return production_response
Стоимость shadow mode удваивает число запросов к AI. При цене $0.01 за запрос и 10 000 запросов в день - это $100 в день дополнительно. Поэтому обычно запускают только на 10-20% трафика.
После недели в shadow mode система наблюдения покажет распределение оценок для обоих вариантов. Если экспериментальный вариант стабильно лучше - это сигнал для следующего шага.
Canary rollout: постепенный выход с автоматическим откатом
Canary rollout - это постепенное увеличение доли пользователей, которые видят новую версию. Сначала 5%, затем 25%, 50% и наконец 100%. Между шагами - пауза 2-4 часа для наблюдения за показателями.
Название «canary» отсылает к шахтёрской практике: канарейку брали в шахту как первый индикатор опасного газа. Здесь 5% пользователей - это «канарейка»: если что-то пойдёт не так, проблема затронет малую долю аудитории.
Следующий код назначает каждого пользователя в действующую или экспериментальную группу. Назначение детерминированное: один пользователь всегда видит одну и ту же версию, а не случайный микс:
import hashlib
def assign_user_to_group(user_id: str, experiment_percentage: float = 0.05) -> str:
# Детерминированное назначение на основе хэша user_id
hash_value = int(hashlib.md5(f"experiment_v2_{user_id}".encode()).hexdigest(), 16)
normalized = (hash_value % 10000) / 10000.0
if normalized < experiment_percentage:
return "experimental"
return "боевая среда"
def get_prompt_for_user(user_id: str) -> str:
group = assign_user_to_group(user_id, experiment_percentage=CANARY_PERCENTAGE)
return EXPERIMENTAL_PROMPT if group == "experimental" else PRODUCTION_PROMPT
Система автоматически откатывается, если: частота ошибок выросла на 50% по сравнению с действующим вариантом, время ответа превысило 2 секунды, или доля отказов бота выросла на 5 процентных пунктов.
Feature flags: как управлять экспериментами без программиста
Feature flags (флаги функций) - это механизм включения и отключения возможностей приложения без перевыпуска кода. В контексте AI-экспериментов это означает: поменял настройку в панели управления, и через секунды 5% пользователей видят новый промпт. Без команды разработчиков, без деплоя.
Это меняет скорость экспериментирования. Вместо цикла «написали промпт - задеплоили - ждали - откатили» получается: написали - включили через панель - посмотрели - переключили. Цикл сжимается с дней до часов.
Инструменты: LaunchDarkly, Flagsmith (open source), Unleash (open source), либо самодельный вариант через конфиг в базе данных. Для небольших команд достаточно простой таблицы в базе: experiment_name, variant, percentage, is_active.
Следующий код реализует простой feature flag через Redis. Значение хранится в кеше и обновляется без перезапуска приложения:
import redis
import json as json_module
redis_client = redis.Redis(host="localhost", port=6379, db=0)
def get_experiment_config(experiment_name: str) -> dict:
config_json = redis_client.get(f"experiment:{experiment_name}")
if config_json:
return json_module.loads(config_json)
# Дефолт: все пользователи на действующем варианте
return {"experimental_percentage": 0.0, "is_active": False}
def set_experiment_config(experiment_name: str, percentage: float, active: bool):
config = {"experimental_percentage": percentage, "is_active": active}
redis_client.set(f"experiment:{experiment_name}", json_module.dumps(config))
# Можно вызвать из любого места без перезапуска приложения
# Активировать эксперимент на 10% трафика:
# set_experiment_config("new_empathy_prompt", 0.10, True)
# Увеличить до 50%:
# set_experiment_config("new_empathy_prompt", 0.50, True)
# Откатить мгновенно:
# set_experiment_config("new_empathy_prompt", 0.0, False)
При тревожном сигнале - один вызов set_experiment_config, и через секунду 0% пользователей видят экспериментальный вариант. Не нужно ждать деплоя.
Как встроить A/B-тест через Langfuse
Langfuse - инструмент наблюдения за AI-приложениями - поддерживает A/B-эксперименты встроенными средствами. Не нужно писать сложную логику разделения: система делает это автоматически по заданным весам.
Следующий код получает промпт для пользователя. Langfuse автоматически выбирает вариант: 70% пользователей получат действующий вариант, 30% - экспериментальный:
from langfuse import Langfuse
langfuse = Langfuse()
def get_prompt(user_id: str, prompt_name: str) -> str:
# Langfuse автоматически выбирает между версиями промпта
# по заданным весам (70% боевая среда, 30% experimental)
prompt = langfuse.get_prompt(
name=prompt_name,
label="боевая среда", # Langfuse управляет A/B распределением
fetch_timeout_ms=500
)
# Логируем какая версия досталась пользователю
langfuse.trace(
user_id=user_id,
metadata={"prompt_version": prompt.version, "prompt_label": prompt.labels}
)
return prompt.compile(variables={})
В панели Langfuse: раздел «эксперименты» - выбрать промпт - добавить вариант - задать процент - запустить. Результаты доступны в разрезе «действующий vs экспериментальный» по всем показателям.
Статистическая значимость: сколько данных нужно
Это критически важный момент для бизнеса. Не делайте выводов, пока не наберёте достаточно данных.
Для обнаружения улучшения в 5% при стандартном разбросе оценок 0.15 нужно около 350 запросов на каждый вариант. При 1000 запросов в день и 30% трафика на эксперимент - это около 5 часов. Если тестируете сразу 5 показателей, нужно в 2.5 раза больше данных.
Следующий код проверяет, является ли разница между вариантами статистически значимой, а не случайной:
from scipy import stats
import numpy as np
def check_significance(control_scores: list, treatment_scores: list) -> dict:
t_stat, p_value = stats.ttest_ind(control_scores, treatment_scores)
effect_size = (np.mean(treatment_scores) - np.mean(control_scores)) / np.std(control_scores)
return {
"p_value": p_value,
"effect_size": effect_size,
"significant": p_value < 0.05,
"improvement": effect_size > 0,
"control_mean": np.mean(control_scores),
"treatment_mean": np.mean(treatment_scores)
}
Если p_value меньше 0.05 и effect_size положительный - улучшение реальное, а не случайное.
Multi-armed bandit: когда классический A/B слишком медленный
Классический A/B-тест с фиксированным разделением 50/50 имеет слабое место: пока эксперимент идёт, 50% пользователей получают заведомо худший вариант, если один вариант явно лучше.
Multi-armed bandit (MAB) - альтернативный подход. Вместо фиксированного разделения система динамически увеличивает трафик на лучший вариант по мере накопления данных. Если вариант B с первых 200 запросов показывает оценку 0.85 против 0.72 у варианта A - система автоматически перераспределяет трафик: 70% на B, 30% на A. Потом 85/15. Потом 95/5.
Это полезно когда: нужно минимизировать долю пользователей с плохим опытом, эксперимент долгосрочный (недели), вариантов больше двух.
Минусы: сложнее интерпретировать результаты, нужна более сложная инфраструктура. Для большинства команд классический A/B с canary rollout проще и достаточен.
Thompson Sampling - самый распространённый алгоритм MAB. Для каждого варианта поддерживается бета-распределение вероятности успеха, из которого семплируется значение для назначения трафика:
import numpy as np
class ThompsonSamplingBandit:
def __init__(self, n_arms: int):
self.alphas = np.ones(n_arms) # успехи + 1
self.betas = np.ones(n_arms) # неудачи + 1
def select_arm(self) -> int:
samples = np.random.beta(self.alphas, self.betas)
return int(np.argmax(samples))
def update(self, arm: int, reward: float):
self.alphas[arm] += reward
self.betas[arm] += (1 - reward)
bandit = ThompsonSamplingBandit(n_arms=2) # 2 варианта промпта
def get_prompt_bandit(user_id: str) -> tuple:
arm = bandit.select_arm()
return arm, PROMPTS[arm]
def record_feedback(arm: int, quality_score: float):
# quality_score от 0.0 до 1.0
bandit.update(arm, quality_score)
Какие показатели отслеживать
Одного показателя недостаточно. Нужна иерархия.
Главный показатель - оценка качества от AI-судьи (0.0-1.0). Именно он определяет победителя эксперимента.
Охранные показатели: доля ошибок, доля отказов, время ответа, стоимость запроса. Они не могут ухудшиться выше порога - иначе эксперимент останавливается независимо от главного показателя.
Отложенные показатели: положительные отклики пользователей, длина сессии, возвращаемость. Собираются через 1-7 дней, используются для финальной проверки.
Правило принятия решения: если главный показатель вырос на 3%, стоимость не выросла, время ответа в норме и доля отказов не изменилась - переводим на 100% трафика.
Реальный пример: эксперимент на 50 000 запросов в день
Бот поддержки для интернет-магазина с 50 000 запросов в день. Гипотеза: добавление инструкции «сначала выразить сочувствие, потом предложить решение» улучшит удовлетворённость.
Настройка: 80% пользователей остаются на текущем промпте, 20% видят новый. Длительность: 3 дня, 10 000 запросов на экспериментальный вариант.
Результаты: оценка AI-судьи выросла с 3.72 до 3.89 (статистически значимо, p=0.003). Доля отказов не изменилась. Время ответа выросло на 80 мс из-за более длинных ответов. Стоимость выросла на $0.0008 за запрос - это $40 в день при данном объёме. Положительные отклики через сутки: 31% против 38%.
Решение: переводим на 100%. Рост удовлетворённости значимый, время ответа в норме, дополнительные $40 в день полностью оправданы.
Типичные ошибки
Случайное назначение на каждый запрос. Один пользователь получает то один вариант, то другой. Данные загрязнены, выводы неверны. Всегда назначайте пользователя в группу один раз и фиксируйте его там.
Остановка эксперимента при первом хорошем результате. Называется «peeking problem» - ранняя проверка увеличивает шанс ложного срабатывания. Определите срок эксперимента заранее и придерживайтесь его.
Конфликт показателей без разбора. Оценка качества растёт, но пользователи реже возвращаются. Это не повод паниковать, но повод изучить 50-100 реальных диалогов из обоих вариантов.
Тестирование смены модели и промпта одновременно. Нельзя понять, что именно дало эффект. Меняйте только одну переменную за один эксперимент.
Частые вопросы
Как назначать пользователей без их идентификатора?
Используйте хэш от идентификатора сессии или отпечаток первого запроса. Случайный выбор без фиксации приводит к тому, что один пользователь получает разные версии в разных запросах.
Canary по пользователям или по запросам?
По пользователям корректнее. Пользователь всегда видит один вариант, а не случайный микс. Тестирование по запросам создаёт несогласованный опыт: «Почему вчера бот отвечал иначе?»
Сколько трафика минимально для значимого эксперимента?
Минимум 300-500 запросов на вариант для обнаружения улучшения в 5%. При 1000 запросов в день и 20% на эксперимент - это 1-2 суток.
Что делать, если оценка качества растёт, но возвращаемость падает?
Это конфликт показателей. Если возвращаемость - это деньги, она важнее оценки AI-судьи. Проведите ручной разбор 50-100 диалогов из обоих вариантов, чтобы понять причину.
Можно ли тестировать смену модели одновременно с промптом?
Нет. Меняйте только одну переменную за эксперимент. Иначе невозможно понять, что именно повлияло на результат.
Что сделать прямо сейчас
Если у вас уже есть AI-бот в поддержке или продажах - начните с shadow mode на 10% трафика. Инструмент - Langfuse (бесплатный план на старт). За вечер настроите запись двух вариантов ответов. Через неделю увидите, какой промпт работает лучше.
AI Компас (t.me/kosmoslab_ai) - канал для предпринимателей в РФ и СНГ, которые применяют AI в своём бизнесе без программиста. Разбираем инструменты и схемы - без курсов и теории.