A/B-тест промптов в Langfuse: offline и production canary

Автор: Обновлено

Что такое A/B-тест промптов в Langfuse?

A/B-тест промптов в Langfuse — это production canary, в котором две версии промпта с разными labels получают отдельные когорты трафика, а команда сравнивает их результаты. Это не то же самое, что offline-эксперимент Langfuse на фиксированном датасете. Безопасная последовательность начинается с датасета и только потом переходит к стабильному распределению реального трафика с отдельными outcome- и guardrail-метриками.

TL;DR

  • -Offline experiment и production A/B-тест отвечают на разные вопросы: сначала регрессии на датасете, затем поведение на реальном трафике
  • -Используйте labels для двух версий промпта и закрепляйте один субъект за одним вариантом; случайный выбор при каждом запросе смешивает когорты
  • -Не объединяйте основную метрику, guardrails, latency, стоимость, ошибки и перекос распределения в один средний score
  • -Универсального размера выборки и единственного правильного статистического теста нет: дизайн и метод анализа задают до просмотра результатов

В Langfuse есть два разных механизма сравнения промптов. Offline experiment прогоняет варианты по фиксированному датасету. Production A/B-тест направляет реальные запросы на две версии промпта с разными labels. Если склеить эти этапы в один, легко выпустить вариант, который красиво выглядит в таблице, но ломается на живых данных. Или, наоборот, объявить победителя из-за ошибки распределения.

Рабочая последовательность такая:

  1. прогнать candidate по замороженному датасету;
  2. вручную разобрать расхождения и критические guardrails;
  3. дать candidate ограниченную долю реального трафика;
  4. не переносить один субъект между вариантами;
  5. принять решение по правилам, записанным до просмотра результата.

Ниже — поведение Langfuse по официальной документации, проверенной 30 августа 2026 года, и локальный fixture распределения. Fixture работает без LLM и внешнего API. Он проверяет механику эксперимента, а не качество вашего промпта.

Чем offline experiment отличается от A/B-теста

ВопросOffline experimentProduction A/B-тест
ДанныеФиксированный датасетРеальные запросы
Что меняетсяPrompt, модель, код или весь taskДве версии промпта с labels
ЗачемНайти известные регрессии и разобрать ошибкиПроверить поведение на живом трафике
РаспределениеКаждый item можно прогнать через оба вариантаОдин субъект должен попасть в одну когорту
Главный рискНеполный датасет и слабые evaluatorsВлияние на пользователей и ошибки allocation

Первый механизм Langfuse называет Experiments. SDK runner принимает локальный или размещённый в Langfuse датасет, выполняет task для каждого item, запускает item- и run-level evaluators, изолирует ошибки и создаёт трейсы. Для hosted dataset появляется dataset run, который можно сравнивать в UI.

В отдельном гайде про A/B testing версии получают labels вроде prod-a и prod-b, после чего приложение выбирает одну из них для живого запроса. Langfuse связывает prompt version с latency, token usage, стоимостью и evaluation scores. Но платформа не исправит неверную единицу анализа или потерянные события.

Шаг 1. Соберите датасет из реальных рисков

Хороший датасет — не коллекция удобных примеров. Для support-промпта в него могут войти противоречивые правила, отсутствующий контекст аккаунта, обязательные ссылки, несколько языков, неподдерживаемые запросы и случаи, где правильный ответ — остановка.

До прогона зафиксируйте:

  • точную ревизию датасета;
  • baseline- и candidate-версии промпта;
  • неизменные model, retrieval, tools и generation settings;
  • одну основную метрику и критические guardrails;
  • правила учёта timeout, пустых outputs, retries и evaluator errors.

Текущий JS/TS SDK показывает dataset runner в такой форме:

const dataset = await langfuse.dataset.get("support-answer-regression");

const result = await dataset.runExperiment({
  name: "support-prompt-v4",
  task: runSupportAnswer,
  evaluators: [hasRequiredLink, refusesUnsupportedRequest],
});

console.log(await result.format());

Это форма из официальной документации, а не результат локального integration test. FutureCraft fixture не подключается к Langfuse. Перед реализацией сверяйтесь с актуальным SDK guide: контракт и telemetry setup могут измениться.

Не сводите качество к одному числу

Средний score скрывает локальные провалы. Оставьте три независимых представления:

  1. Primary outcome — то, что candidate должен улучшить.
  2. Guardrails — ошибки, которые запрещают релиз даже при росте primary outcome.
  3. Case review — примеры, где варианты расходятся, evaluator падает или ответ меняется качественно, а не только численно.

Langfuse поддерживает code evaluators, ручную оценку, scores через SDK/API и LLM-as-judge. Они не взаимозаменяемы. JSON Schema детерминирована, но видит только структуру. Человек может обнаружить плохую rubric. LLM judge масштабирует субъективную оценку, однако добавляет собственные prompt, model и calibration risk. Не отдавайте решение одному judge score без отдельной проверки — ограничения разобраны в гайде про LLM-as-judge.

Шаг 2. Назначьте двум версиям разные labels

В Langfuse prompt versions и labels отвечают за deployment. Оставьте текущую версию под baseline label, а candidate — под отдельным canary label:

const promptA = await langfuse.prompt.get("support-answer", {
  label: "prod-a",
});
const promptB = await langfuse.prompt.get("support-answer", {
  label: "prod-b",
});

Передавайте выбранный prompt object в traced generation, иначе observations нельзя надёжно связать с версией. Не переносите label production на candidate только ради старта теста. Механика labels, fetching и rollback описана в Langfuse version control.

Шаг 3. Сделайте распределение стабильным

В официальном A/B-примере вариант выбирается случайно. Для демонстрации prompt fetch этого достаточно. Для повторных запросов одного аккаунта — нет. Math.random() при каждом вызове отправит один и тот же аккаунт то в A, то в B. Контекст диалога, retries и уже полученный ответ смешают treatment.

Fixture хеширует experiment key и subject ID в один из 10 000 buckets:

const assignment = await assignVariant(
  "support-prompt-v4",
  privacySafeAccountId,
  0.1,
);

const selectedPrompt = assignment.variant === "candidate"
  ? promptB
  : promptA;

Единицей может быть account, workspace, session или request — выбирайте ту, которая реально получает воздействие. Если пользователи аккаунта делят историю и состояние, request-level assignment не даёт независимых наблюдений. Для анонимного one-shot трафика, наоборот, account ID может отсутствовать.

Не отправляйте в трейсы email или сырой database ID. Создайте scoped identifier, который нельзя обратить во внутренний ID, и примените правила privacy и retention проекта.

Шаг 4. Разделите outcomes и guardrails

Наблюдение должно позволять проверить когорту без экспорта приватного prompt content:

  • experiment key и prompt version;
  • variant и bucket;
  • безопасный subject identifier;
  • primary outcome;
  • результаты guardrails;
  • latency, token usage или стоимость, если они важны;
  • provider, tool, timeout и evaluator failures;
  • timestamp и окно анализа.

summarizeExperiment() из fixture отдельно возвращает outcome rate, guardrail failure rate, среднюю latency, фактическую долю candidate и отклонение от плана. Поля winner там нет.

Это принципиальное ограничение. Нельзя автоматически выпускать prompt из-за роста одного среднего, когда ухудшился safety guardrail или потерялась половина observations candidate.

Шаг 5. Выберите анализ до просмотра данных

Правило «возьмите 200 примеров и посчитайте t-test» не универсально. Метод зависит от единицы анализа, типа outcome, распределения, повторных наблюдений, baseline, минимального полезного эффекта и stopping rule.

До запуска запишите:

  • какой минимальный эффект оправдывает риск и стоимость изменения;
  • допустимые false-positive и false-negative errors;
  • независимы ли observations или сгруппированы по субъекту;
  • как учитывать пропуски и retries;
  • фиксировано ли окно или используется корректный sequential design;
  • какой guardrail немедленно останавливает canary.

После этого рассчитайте размер выборки для выбранного дизайна. Если нужного трафика нет, маленький тест нельзя выдавать за доказательство. Оставьте результат описательным, продлите canary, улучшите outcome signal или принимайте решение по offline evidence и границе риска.

Шаг 6. Запишите решение и rollback

У эксперимента не два, а четыре нормальных результата:

  • promote — candidate прошёл основную метрику и guardrails;
  • continue — запланированное окно ещё не закончилось;
  • reject — candidate нарушил guardrail или не достиг полезного эффекта;
  • inconclusive — данных недостаточно или их качество сомнительно.

Сохраните prompt versions, dataset revision, commit assignment-кода, окно запроса и analysis output. Для rollback верните прошлой версии production label. Затем проверьте фактически выдаваемую версию и новые traces: label update — это операция, а не гарантия мгновенного обновления каждого кэша.

Что действительно проверено

TypeScript fixture опубликован под MIT и прошёл Deno format, type check, lint и семь тестов. Проверены:

  • стабильный bucket для одного experiment и subject;
  • новый bucket при смене experiment key;
  • ограниченное распределение на детерминированном наборе из 1 000 субъектов;
  • отказ на неверных входах и duplicate subject;
  • раздельные сводки outcome, guardrail, latency и sample ratio;
  • отсутствие автоматического winner.

Fixture не доказывает совместимость с Langfuse SDK, качество модели, statistical significance или безопасность production rollout. В пакет входит EN/RU checklist под CC BY 4.0, чтобы тестируемая механика не отрывалась от ручных release gates.

Короткая честная схема выглядит так: offline dataset → разбор ошибок → sticky canary → заранее выбранный анализ → явное решение. Langfuse хранит prompt versions, traces, experiment runs и scores. Смысл этим данным всё равно придаёт дизайн эксперимента.

Часто задаваемые вопросы

Dataset experiment в Langfuse и A/B-тест промптов — одно и то же?
Нет. Dataset experiment прогоняет task по фиксированным тест-кейсам до релиза и сравнивает outputs или scores. A/B-тест распределяет живой трафик между версиями промпта. Датасет нужен для известных регрессий, а production canary — для поведения в реальных условиях, если продукт допускает такую вариативность.
Зачем закреплять пользователя или аккаунт за одним вариантом?
Если сегодня аккаунт получает prompt A, а в следующем запросе prompt B, история диалога, повторы и поведение пользователя пересекают границу эксперимента. Стабильный hash от experiment key и безопасного идентификатора сохраняет единицу анализа. Для анонимных одноразовых запросов понадобится другой дизайн.
Сколько запросов нужно для A/B-теста промпта?
Это зависит от baseline, дисперсии или частоты события, минимального полезного эффекта, распределения трафика, выбранных ошибок и зависимости наблюдений. Размер рассчитывают для конкретного дизайна до запуска. Диапазон вроде 100–500 нельзя одинаково применять к бинарной конверсии, непрерывному score, повторным запросам одного аккаунта и редким safety-ошибкам.