Заметки из релизного цикла — часть 7
В пятницу в 16:47 вы выкатили правку промпта в один символ. Агрегированный балл оценки сдвинулся с 0.873 до 0.871 — глубоко внутри уровня шума. В понедельник утром ваша служба поддержки горит из-за класса запросов, на который вы перестали смотреть полгода назад, потому что он был стабилен.
В модели ничего не регрессировало. Модель — та же модель. Из-под вас уплыла оценка. Полгода медленного роста в одном клиентском сегменте так и не попали в эталонный датасет, промпт судьи в последний раз калибровался по людям в октябре, а индекс ретривала тихо пересобрался на обновлённой эмбеддинг-модели в прошлую среду.
Об этом и говорилось в шестом посте — модель оказывается правильным ответом примерно в одном алерте из семи. Значит, ваш регрессионный набор обязан детектировать дрейф в самом себе, а не только в модели. Этот пост — про такой набор.
Что такое регрессионное тестирование для кастомной LLM на самом деле?
Программные регрессионные тесты утверждают output == expected для фиксированных входов. Они работают, потому что функция детерминирована.
Языковая модель не является функцией в этом смысле. Один и тот же промпт при температуре > 0 порождает распределение валидных дополнений, и «валидно» — многомерное понятие: ответила ли модель на вопрос, опирается ли ответ на извлечённый контекст, осталась ли в рамках безопасности, уложилась ли в бюджет латентности. Поэтому регрессионное тестирование кастомной LLM означает измерение распределения поведения относительно зафиксированного базового распределения — по значимым для вас срезам, судьями, откалиброванными против людей, на входах, которые похожи на ваш продакшен-трафик.
Прежде чем всё это станет осмысленным, должно быть в наличии три вещи:
- Эталонный датасет, который похож на продакшен на уровне срезов, а не в агрегате.
- Откалиброванный судья — не «мы используем GPT-5 как судью», а «мы измерили Spearman ρ ≥ 0.7 против трёх человеческих оценщиков, последнее обновление — на прошлой неделе».
- Манифест базовой линии — точные веса модели, шаблон промпта, индекс ретривала и версия судьи, которые получили те баллы, которые получили. Без этого вы не сможете сказать, сдвинулся ли балл из-за изменения модели или из-за изменения линейки.
Divinci работает со всеми тремя как с объектами первого класса, связанными хешами, скоринг — на каждом коммите. Остаток поста — о том, как их собрать.
Почему большинство регрессионных наборов LLM не ловят настоящие регрессии
Доминирующий в 2026 году режим отказа кастомных LLM — это то, что команда Sigma Inference Тяньпаня в апрельском постмортеме 2026 года назвала Semver Lie[1]: агрегированная метрика остаётся плоской или улучшается, в то время как один-два продакшен-среза тихо регрессируют. Срез был менее 5% трафика на момент проектирования теста, поэтому не попал в эталонный датасет; полгода спустя он составляет 12% трафика, модель на нём деградировала, а агрегированный показатель этого никогда не заметит.
Мы просмотрели каждый публичный постмортем релиза LLM за последние восемнадцать месяцев, и шаблон повторяется: набор показал зелёный, потому что мерил не то. А именно:
- Эталонный датасет был написан командой вручную при запуске и больше не пересстратифицировался против сдвинувшегося распределения трафика.
- Промпт LLM-as-judge был задан один раз и больше никогда не калибровался по человеческим разметкам. Согласие судьи тихо деградировало[2].
- Базовые баллы хранились как сырые числа, а не как кортежи
(model_sha, prompt_sha, judge_sha, dataset_sha, score)— поэтому когда что-то регрессировало, никто не мог сказать, какой из четырёх элементов сдвинулся.
Регрессионный набор, который не решает все три проблемы сразу, — это просто CI-шаг, который зеленеет при деплое и даёт вам ложную уверенность. Решение — не «больше кейсов». Решение — срез-ориентированное, привязанное к версиям, откалиброванное по судье измерение на каждом релизе.
Соберите эталонный датасет, переживающий срез-ориентированный анализ
Четырёхбакетная композиция, которую мы поставляем по умолчанию — продакшен-сэмплы 60%, состязательные 15%, кураторские пограничные случаи 15%, реплеи отказов 10% — это разумная отправная точка. То, что реально позволяет ей ловить регрессии, — это метаданные срезов, прикреплённые к каждому кейсу.
Каждая запись в датасете несёт: вход, ожидаемое поведение (рубрика, а не точная строка), контекст ретривала (если есть) и тег slice — домен, пользовательский сегмент, намерение запроса, язык, корзина длины — какие декомпозиции значимы для вашего продукта. Набор скорит по срезам, и любой срез, который опустится ниже своего порога, блокирует релиз, даже если агрегированный балл вырос.
Два операционных правила, которые мы научились соблюдать:
Пересэмплируйте ежеквартально. Распределение продакшен-трафика смещается быстрее, чем большинство команд измеряет. Мы рестратифицируем бакет продакшен-сэмпла против последних 90 дней трафика каждый квартал; если какой-либо срез вырос выше 5% трафика и был менее 2% эталонного датасета, он добавляется до отправки следующего релиза.
Каждый постмортем добавляет кейс. Регрессия, которая дошла до продакшена и не была поймана, — это кейс, отсутствовавший в датасете. Мы добавляем его в бакет реплеев в течение 48 часов после постмортема и помечаем тегом среза, на котором он всплыл.
Как обнаружить дрейф раньше пользователей?
Существует четыре различных типа дрейфа, и регрессионный набор, который следит только за последним, — это набор, который пропускает большинство регрессий.
| Тип дрейфа | Что меняется | Сигнал детекции | Действие |
|---|---|---|---|
| Дрейф качества | Балл судьи для фиксированного среза | Спирмен ρ по срезу vs baseline падает | Блокировать релиз; диагностика по дереву из поста 6 |
| Дрейф покрытия | Распределение продакшен-трафика vs распределение эталонного датасета | KL-дивергенция между долями срезов | Пересэмплировать эталонный датасет |
| Дрейф судьи | Согласие судьи с людьми | Спирмен ρ против замороженного аудит-сета с человеческой разметкой | Перекалибровать промпт судьи или заменить судью |
| Продакшен-дрейф | Живые продакшен-баллы vs офлайн-баллы на той же модели | Разрыв скоринга реплея продакшен-трейса | Расследовать ретривал / препроцессинг / рантайм |
Дрейф качества — это то, что измеряет большинство наборов; остальные три — это места, где обычно прячутся пятничные регрессии. Divinci отслеживает все четыре относительно манифеста базовой линии, с разбивкой балла по срезам, видимой на каждом PR, и еженедельной задачей калибровки судьи, которая отмечает дрейф до его накопления.
Многомерная оценка — скорьте четыре вещи одновременно, по срезам
Один композитный балл — худший сигнал, чем четыре скалярных. Мы выставляем ворота по четырём измерениям:
- Выполнение задачи — действительно ли ответ ответил на вопрос, оценивается откалиброванным судьёй по рубрике. Срез-ориентированно.
- Достоверность — для любого ответа, ссылавшегося на извлечённый контекст, опирается ли каждое утверждение на этот контекст. Галлюцинации проявляются здесь в первую очередь.
- Безопасность — корректность отказов, устойчивость к джейлбрейкам, утечки PII / политик. Почти всегда ворота при pass-rate ≥ 0.99; безопасность — это жёсткая стена, а не мягкий трейд-офф.
- Бюджет латентности — p95 в рамках SLA среза. Изменение промпта, удвоившее токены на ответ, — это регрессия, даже если качество выросло.
У каждого измерения свой baseline по срезам и свой порог по срезам. Мы никогда не сводим их в один взвешенный скаляр на этапе ворот; мы показываем их как четыре балла на срез и блокируем по тому, который первым пересёк порог. Модель, набравшая 4 пункта по выполнению задачи ценой 1 пункта достоверности на медицинском срезе, — это всё ещё регрессия.
Какие ворота должны блокировать деплой кастомной LLM?
Мы запускаем трёхслойную архитектуру, каждый слой контролирует свою стадию пайплайна (таксономия стадий — в посте 1).
Слой 1 — Smoke (каждый коммит, ~90 секунд). Двадцать-тридцать критических кейсов, отобранных из срезов с наибольшим влиянием. Ловит катастрофические регрессии до того, как полный набор потратит вычисления. Если smoke падает, остальное не запускается.
Слой 2 — Полный набор (каждый PR, ~12 минут). Полный эталонный датасет, скоринг по срезам по всем четырём измерениям. Срез-ориентированный Spearman ρ относительно манифеста базовой линии. Пробой порога блокирует мерж. Комментарий к PR перечисляет, какой именно срез по какому измерению сдвинулся и насколько, с пятью примерами провалившихся кейсов.
Слой 3 — Сравнение с baseline (релиз-кандидаты, ~25 минут). Кандидат-модель прогоняется на трейсах продакшена за последние 14 дней — это замкнутый реплей продакшен-трейсов, который мы выкатили в посте 1. Тот же откалиброванный судья, что скорит эталонный датасет, оценивает и выходы реплея. Любой срез, чьи баллы реплея расходятся с офлайн-баллами больше, чем на величину его порога, блокирует релиз. Этот слой ловит дрейф, о котором эталонный датасет ещё не знает.
Откалибруйте судью прежде, чем доверять хоть одному балл, который он выдаёт
LLM-as-judge — это то, что позволяет всему этому масштабироваться выше нескольких сотен кейсов. Это же и место, где регрессионный набор тихо перестаёт работать, потому что у судьи нет обязательства оставаться откалиброванным, пока он обновляется или пока ваше распределение данных смещается.
Мы калибруем каждый промпт судьи против замороженного аудит-сета с человеческой разметкой минимум на 100 кейсах, стратифицированного по тем же срезам, что и эталонный датасет, и перезапускаем калибровку еженедельно. Планка, на которой мы выпускаем, — Spearman ρ ≥ 0.7 против медианы человеческих оценщиков и κ Коэна ≥ 0.6 для бинарных суждений о безопасности. Оба значения выше порога, на котором, как было показано, судьи в стиле MT-Bench соответствуют человеческим оценщикам на уровне меж-человеческого согласия[2].
Когда еженедельная калибровка опускается ниже порога, судья автоматически снимается, и инженер eval по дежурству получает пейдж. Релизный пайплайн держит кандидатов открытыми, вместо того чтобы пропускать их через судью, который больше не измеряет то, что измерял.
# Запустить еженедельное задание калибровки судьи
curl -X POST https://api.divinci.ai/v1/regression/judges/calibrate \
-H "Authorization: Bearer $DIVINCI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"judge_id": "rubric-v7",
"audit_set": "human-labels-2026-04",
"min_spearman": 0.70,
"min_kappa": 0.60,
"on_fail": "retire_judge_and_page"
}'Дифференциатор Divinci — замкнутый реплей продакшен-трейсов
Ворота слоя 3 — это та часть, которой у большинства регрессионных наборов нет. Поток тот же, что мы выкатили в посте 1, с одной специализацией для регрессионного тестирования: у каждого релиз-кандидата балл на офлайн-эталонном датасете сравнивается, срез за срезом, с баллом на 14-дневном окне реплея продакшен-трейсов. Эталонный датасет измеряет, что мы ожидали от модели. Реплей измеряет, что модель сделала бы на самом деле на прошлой неделе.
Когда эти два балла расходятся больше, чем на бюджет разрыва по срезу, релиз блокируется. Несовпадение — это сигнал: либо эталонный датасет больше нерепрезентативен (дрейф покрытия), либо кандидат ведёт себя иначе на трейсах, оформленных продакшен-препроцессингом и ретривалом (продакшен-дрейф). Так или иначе, вы узнаёте об этом раньше пользователей.
Судья, скорящий офлайн-прогон, — тот же судья, что скорит реплей-прогон. Аудит-лог фиксирует оба набора баллов, обе версии судьи, ID трейсов, которые были воспроизведены, и разрыв, активировавший блок. Сам разрыв — самый полезный диагностический сигнал, который у нас есть, и именно он передаётся тому, кто берётся за диагностическое дерево из поста 6.
Привяжите эталонный датасет vindex-квитанцией
Любой балл в наборе бессмыслен, если вы не можете воспроизвести его позже. Мы хешируем эталонный датасет на каждом релизе и встраиваем этот хеш в vindex-квитанцию вместе с SHA модели, SHA промпта, SHA судьи и записью калибровки. Квитанция привязывается внешне — аудиторы могут воспроизвести наш точный регрессионный прогон шесть месяцев спустя и проверить заявленные баллы.
{
"release_id": "rel_3f1a-2026-05-26",
"model": { "sha": "0c1f9…", "weights_uri": "r2://models/custom-v7.2", "open_weights": true },
"prompt": { "sha": "c4a8e…", "template_id": "support-v3.4" },
"retrieval": { "index_sha": "b21f0…", "embedder": "e5-mistral-7b-instruct" },
"judge": { "sha": "d8e21…", "rubric_id": "rubric-v7", "spearman_vs_humans": 0.74 },
"dataset": { "sha": "a90b1…", "n": 512, "slices": 17, "stratified_at": "2026-04-30" },
"scores": { "aggregate": 0.872, "by_slice": { "/* … */": "/* per-slice scalars */" } },
"replay": { "trace_window_days": 14, "n_traces": 8430, "max_gap": 0.018 },
"vindex_anchor": "sha256:f0bfd2…",
"verifiable_at": "https://vindex.divinci.ai/rel_3f1a-2026-05-26"
}Оговорка про open-weights. Квитанция выше несёт провенанс весов только тогда, когда модель — open-weights: vindex привязывает фактические байты весов. Для бэкендов на закрытом API (управляемые модели OpenAI / Anthropic / Google) квитанция всё ещё несёт цепочку решений — каждый балл ворот, каждый результат судьи, запись калибровки, — но поле весов пустое, и независимо проверить артефакт модели нельзя. Мы говорим об этом и в квитанции, и в документации по комплаенсу, чтобы у аудиторов не сложилось ложного впечатления. Релизы, которые больше всего выигрывают от полной vindex-цепочки, — это те, где вы контролируете веса.
Четырёхфазный план внедрения, который мы реально выкатили
Команды, пытающиеся выпустить всю архитектуру за неделю, застревают на инструментарии. Порядок ниже — это порядок, который работает.
Фаза 1 — Базовая линия (неделя 1). Возьмите стратифицированный сэмпл продакшен-трейсов за последние 30 дней. Пусть два инженера разметят вручную выполнение задачи на 100 кейсах каждый. Посчитайте меж-оценочное согласие (целевое κ Коэна ≥ 0.6). Полученное число — ваша стартовая человеческая базовая линия; всё остальное калибруется против неё.
Фаза 2 — Хорнесс (недели 2–3). Поднимите оценочный хорнесс на датасете в 100 кейсов. Добавьте откалиброванного судью против ваших человеческих разметок. Убедитесь, что хорнесс воспроизводит человеческие баллы с ρ ≥ 0.7. Большинство команд обнаруживают, что их первый промпт судьи не проходит этого, и переписывают его дважды — это нормально.
Фаза 3 — Ворота (недели 3–4). Подключите хорнесс к CI как предупреждение, не как блок. Понаблюдайте за ним две недели. Пороги, которые вы открываете, наблюдая за частотой ложных срабатываний, — единственные пороги, которые приживутся. Переключайте на блокирующий режим только тогда, когда частота ложных срабатываний ниже 5%.
Фаза 4 — Контур реплея (постоянно). Когда ворота начнут блокировать надёжно, включите слой реплея продакшен-трейсов. Это место, где всплывает пробел в покрытии срезов, и где каждый постмортем начинает возвращать кейсы в эталонный датасет.
Чего это не решает
Три честных ограничения — в том же ключе, в каком мы формулировали их в каждом посте этой серии.
- Дрейф набора — бесконечная работа. Регрессионное тестирование — это инфраструктура, а не проект. Эталонный датасет нужно рестратифицировать ежеквартально, судью перекалибровывать еженедельно, бюджеты порогов перенастраивать после каждого постмортема. Версии, в которой вы выкатываете набор и уходите, не существует.
- Идеально откалиброванный судья — всё равно модель. Spearman ρ = 0.74 против человеческих оценщиков означает, что примерно четверть вызовов судьи расходится с медианой людей. Этот остаточный разрыв — пол шума на любом балле. Мы явно показываем его в каждом отчёте по релизу; команды, забывающие о нём, рано или поздно удивятся.
- Бэкенды на закрытом API ограничивают, сколько вы можете верифицировать. С моделью на закрытом API регрессионный набор измеряет поведение, но не может верифицировать провенанс весов. Если вам нужна полная воспроизводимость — регулируемые отрасли, аудируемые деплои, — трейд-офф лежит в выборе модели, а не в наборе.
Дальше
Пост 8, последний в этой серии, замыкает контур внутри CI. Этот пост и пост 5 были о том, что запускается у ворот; следующий — о слое CI, который производит кандидатов, оцениваемых воротами: pre-merge оценка, контрактные тесты для шаблонов промптов и как масштабировать парк CI под 12-минутный набор оценок, не разорив бюджет. Это инженерный слой под всем, что мы писали выше.
FAQ
В чём разница между оценкой LLM и регрессионным тестированием LLM?
Оценка измеряет, соответствует ли модель планке качества в точке времени по абсолютной рубрике. Регрессионное тестирование измеряет, ведёт ли себя кандидат так же, как замороженная базовая линия, по срезам, по нескольким измерениям. Именно базовая линия делает это регрессионным тестированием — Divinci поставляет оба режима, и в режиме регрессии фиксируются (model_sha, prompt_sha, judge_sha, dataset_sha), чтобы сдвинувшийся балл идентифицировал, какой именно вход сдвинулся.
Сколько кейсов должно быть в эталонном датасете?
Меньше, чем вы думаете, но стратифицированных лучше, чем вы думаете. Мы выпускали полезное регрессионное покрытие на 200 кейсах по пяти чётко определённым срезам и видели нестратифицированные датасеты в 5 000 кейсов, упускавшие всё важное. Начинайте с 200, стратифицированных, затем растите бакет реплеев по одному кейсу из постмортемов.
Использовать ли человеческих ревьюеров или LLM-as-judge?
И тех, и других — люди калибруют судью. Люди не успевают за объёмом, который должен скорить CI-шлюз релизного цикла. Судья заполняет объём, люди калибруют судью — измеряется еженедельно при Spearman ρ ≥ 0.7. Любое из двух по отдельности — режим отказа.
Как тестировать недетерминированные выходы?
Скорьте распределение, а не строку. Используйте рубрику, которую судья сможет применить к разным формулировкам, и запускайте каждый вход три-пять раз при температуре > 0, чтобы срез-ориентированный балл считался по распределению дополнений, а не по одному сэмплу. Сжимайте температуру только для кейсов, которым действительно нужен детерминированный вывод (вызовы инструментов со структурированным выходом, классификация).
Какие метрики поставить в приоритет для первых ворот качества в CI?
Выполнение задачи и одни ворота безопасности. Обе — по срезам. Добавление новых измерений до того, как откалиброваны первые два, порождает шум; команды, которые ставят больше, обычно начинают шлюзовать на этом шуме. Добавляйте достоверность следом, когда включаете ретривал; добавляйте латентность, когда первые две стабильны.
Ссылки
- Pan, Tianpan. "The Semver Lie: how a minor LLM update broke production." 29 апреля 2026 г. Названный режим отказа 2026 года для срез-ориентированного регрессионного анализа: агрегированные баллы держатся плоско, а низкообъёмный срез тихо регрессирует.
- Zheng et al. "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena." arXiv:2306.05685. Эмпирические свидетельства того, что сильные LLM-судьи согласуются с человеческими оценщиками примерно на уровне меж-человеческого согласия (≈ 80%) на открытых задачах, с описанием режимов отказа, которые аудит «калибровка-против-людей» и должен ловить.
- Kirkpatrick et al. "Overcoming catastrophic forgetting in neural networks." PNAS / arXiv:1612.00796. Основополагающий результат по катастрофическому забыванию в дообучаемых нейросетях — почему дообученная кастомная LLM должна регрессионно тестироваться на потерю общих способностей, а не только на прирост по целевой задаче.
- Amazon Web Services. "SageMaker Deployment Guardrails — blue/green deployments and canary monitoring." Контраст с закрытым API: ворота на инфраструктурных метриках (латентность, ошибки, CPU), а не на семантическом качестве по срезам.
- Spearman, C. "The proof and measurement of association between two things." American Journal of Psychology, 15(1):72–101, 1904. Коэффициент ранговой корреляции, на котором держатся срез-ориентированные ворота — устойчив к дрейфу шкалы скоринга у судьи, что нам и было нужно.
- DORA / Google Cloud. "Accelerate State of DevOps — change-failure-rate and time-to-restore-service metrics." Межотраслевой baseline для «как часто деплои вызывают инциденты» и «как быстро вы восстанавливаетесь». Регрессионные наборы, блокирующие у ворот, опускают первую метрику; мгновенный откат ([пост 5](/ru/blog/automated-llm-ci-cd-pipelines-with-instant-rollback/)) опускает вторую.
Ready to Build Your Custom AI Solution?
Discover how Divinci AI can help you implement RAG systems, automate quality assurance, and streamline your AI development process.
Get Started Today