Проблема: промпт меняет всё сразу
В обычном коде функция изолирована: поправил calculate_tax() — затронул расчёт налога, остальное не тронуто. С промптом иначе: это глобальная инструкция для модели, и любая правка меняет поведение агента целиком и непредсказуемо. Добавил строчку «отвечай кратко» — и агент внезапно стал обрезать важные детали в совершенно других сценариях.
Отсюда специфика LLM-регрессий:
- Нелокальность. Правка «для одного кейса» задевает все остальные — нет изоляции, как у функций.
- Незаметность. Регрессия не роняет систему с ошибкой — агент продолжает бодро отвечать, просто хуже. Без проверки её не видно.
- Накопление. Каждая правка чинит одно и чуть ломает другое; без защиты качество медленно «эродирует» от релиза к релизу.
Термин из классической разработки: тесты, которые проверяют, что новое изменение не поломало старую функциональность. В отличие от A/B (стало ли в среднем лучше), regression фокусируется на конкретных известных кейсах, которые обязаны продолжать работать. A/B смотрит вперёд («лучше?»), regression — назад («не хуже на том, что уже умели?»). Оба нужны: A/B — про прогресс, regression — про сохранность.
Golden dataset
Сердце regression testing — golden dataset: курируемый набор эталонных примеров {вход → ожидаемое поведение}, представляющих то, что агент обязан уметь. Это «контрольный список функциональности», зафиксированный в данных. При каждом изменении промпта агент прогоняется на нём, и если что-то из закреплённого сломалось — тест падает.
Что кладут в golden dataset:
- Ключевые сценарии — то, что агент должен уметь всегда (основные типы запросов).
- Edge cases — пустой ввод, очень длинный, на другом языке, провокации, попытки джейлбрейка.
- Прошлые баги — каждый найденный в проде дефект превращается в golden-кейс, чтобы никогда не повториться (это и есть суть regression: «не наступить на те же грабли»).
- Критичные для бизнеса кейсы — то, где ошибка дороже всего (юридические формулировки, безопасность).
Главная практика ведения набора: нашёл баг в проде (через трейсы/жалобы) — не просто почини, а добавь этот случай в golden dataset. Тогда любая будущая правка, которая воскресит этот баг, будет поймана автоматически. Так набор растёт вместе с продуктом и становится всё надёжнее — это дистиллированная история всех ошибок, которые вы обязались не повторять. Trace-to-dataset из уроков LangSmith/Langfuse — ровно этот механизм.
Почему «точное совпадение» не работает
Соблазн: сравнивать выход агента с эталоном через ==, как в обычных unit-тестах. Для LLM это ломается сразу: «Столица Франции — Париж» и «Париж является столицей Франции» — семантически идентичны, но == вернёт False. Строгое сравнение даст лавину ложных срабатываний, тест станет бесполезным шумом.
Поэтому в regression для LLM используют нестрогое (semantic) сравнение — от дешёвого к гибкому:
| Способ сравнения | Когда подходит |
|---|---|
| Точное / regex | Структурированный выход: JSON-поле, число, статус, вызванный инструмент |
| Проверка свойств | «Содержит слово X», «есть ссылка на политику», «нет запретных тем» |
| Semantic similarity | Эмбеддинги: близок ли смысл к эталону (порог косинуса) |
| LLM-as-judge | «Совпадает ли по смыслу с эталоном?» — самое гибкое (урок про судью) |
На практике — смесь: детерминированные проверки для формальных требований (дёшево, стабильно) + judge/semantic для смысла. Это ровно палитра scorers из урока про offline-метрики, применённая к фиксированному эталонному набору.
Раз сравнение опирается на LLM-judge, он наследует его свойства: небольшой разброс и стоимость (уроки про judge). Следствия: держи судью на temperature=0, а «падение» одного кейса при пограничном score перепроверяй, прежде чем блокировать релиз, — чтобы regression-гейт не флапал из-за шума самого судьи. И проверяй формальное кодом там, где можешь, оставляя judge только для смысла.
Порог прохождения
Ключевой вопрос: когда считать, что regression-набор «упал»? Два режима, и выбор зависит от критичности кейсов.
- Строгий (per-case gate). Каждый golden-кейс обязан пройти; хоть один сломан → релиз блокируется. Для критичных наборов (безопасность, юридические, «баги, которые не должны вернуться»).
- Пороговый (aggregate). Набор проходит, если доля успешных ≥ порога (например, ≥ 95%). Допускает мелкие колебания на больших, менее критичных наборах.
- Без снижения (no-regression). Новая версия не должна набрать меньше предыдущей: сравниваешь score билда с baseline и падаешь при просадке.
Практичный приём: держи критический набор (мало кейсов, строгий per-case gate — безопасность, ключевая функциональность, воскресшие баги) и расширенный набор (много кейсов, пороговый режим — общее качество). Тогда мелкий шум на большом наборе не блокирует релиз, но провал в критичном кейсе останавливает всё. Не суй всё в один режим — либо задолбают ложные падения, либо пропустишь важное.
Интеграция в CI/CD
Regression testing работает, только если запускается автоматически при каждом изменении — иначе про него забудут ровно тогда, когда он нужен. Место — CI/CD: тот же гейт, что для обычных тестов, но для качества LLM. Промпт меняется через PR → CI гоняет golden dataset → красный тест блокирует мёрж.
import json, pytest
GOLDEN = json.load(open("golden_dataset.json")) # закреплённые кейсы
@pytest.mark.parametrize("case", GOLDEN)
def test_no_regression(case):
output = run_agent(case["input"]) # прогоняем НОВУЮ версию промпта
# нестрогое сравнение (НЕ ==): свойства + judge
if case["type"] == "exact":
assert case["expected"] in output # формальное — кодом
else:
score = llm_judge_similarity(output, case["expected"]) # смысл — судьёй (temp=0)
assert score >= case.get("threshold", 0.8), \
f"Регрессия в кейсе '{case['id']}': {output!r}"
# в CI: промпт меняется в PR → эти тесты гоняются → красный блокирует мёрж
# критический набор — per-case gate; расширенный — порог ≥95% успешных
Это замыкает весь раздел оценки в единый защитный контур: offline-метрики и scorers дают способ измерить кейс, judge — сравнить нестрого, golden dataset фиксирует эталон, A/B проверяет прогресс на трафике, а regression в CI не даёт откатиться назад. Вместе — это машина, которая позволяет менять промпты смело, но безопасно.
Regression-набор гоняет реальные LLM-вызовы — это токены (деньги) и время на каждый PR, плюс возможная нестабильность (модель/судья недетерминированы). Держи набор компактным и репрезентативным, кэшируй где можно, запускай тяжёлый расширенный набор реже (ночью/перед релизом), а быстрый критический — на каждый PR. Иначе CI станет медленным и дорогим, и его начнут отключать — что убивает всю защиту.
Типичные ошибки
Точное совпадение для свободного текста даёт лавину ложных падений — семантически верные ответы «не проходят». Используй проверку свойств / semantic / judge, а == только для структурированного выхода.
Если найденные баги не превращаются в golden-кейсы, они возвращаются снова и снова. Каждый прод-дефект → новый кейс, иначе regression не выполняет свою главную функцию.
Ручной regression не запускается ровно перед тем релизом, где нужен. Автоматизируй в CI на каждое изменение промпта — только тогда он ловит регрессии.
Строгий per-case gate на огромном наборе → флап из-за шума судьи; мягкий порог на критичном → пропустишь опасный баг. Разделяй наборы по критичности с разными режимами.
Шпаргалка
REGRESSION = «не сломал ли новый промпт то, что РАБОТАЛО» (назад) ≠ A/B (вперёд)
промпт = ГЛОБАЛЬНАЯ настройка: правка меняет всё поведение непредсказуемо
регрессия НЕзаметна (агент отвечает, просто хуже) → без теста не видно
GOLDEN DATASET = курируемый набор {вход → ожидаемое поведение}, «что агент ОБЯЗАН уметь»
кладём: ключевые сценарии · edge cases (пустой/длинный/джейлбрейк) ·
ПРОШЛЫЕ БАГИ (чтобы не вернулись) · критичные для бизнеса
★ каждый прод-баг → новый golden-кейс (trace-to-dataset)
СРАВНЕНИЕ (НЕ ==! семантика):
точное/regex — структурир. выход (JSON/число/tool) · проверка свойств («содержит X»)
semantic similarity (эмбеддинги) · LLM-judge («совпадает по смыслу?») — гибче всего
⚠️ судья недетерминирован → temp=0, перепроверять пограничные, формальное — кодом
ПОРОГ:
строгий per-case — критичные (хоть 1 сломан → блок) · пороговый ≥95% — большие наборы
no-regression — не ниже baseline
★ РАЗДЕЛЯЙ: критический набор (строго) + расширенный (порог)
CI/CD: автоматом на КАЖДОЕ изменение промпта (PR → golden → красный блокирует мёрж)
⚠️ стоит токенов/времени + flaky → набор компактный, тяжёлый реже, критический на каждый PR
СВЯЗЬ: offline (измерить) + judge (сравнить) + golden (эталон) + A/B (прогресс) +
regression в CI (не откатиться) = менять промпты СМЕЛО, но БЕЗОПАСНО
ГРАБЛИ: == сравнение · не пополнять из багов · ручной прогон · один порог на всё
Практическое задание
Задание: собери regression-защиту
- Для агента из модулей 03–05 собери мини golden dataset из 8–10 кейсов: ключевые сценарии, 2–3 edge case, 1 «прошлый баг». Для каждого — вход и ожидаемое поведение.
- Для каждого кейса выбери способ сравнения (точное / свойства / semantic / judge) и обоснуй, почему
==тут не годится. - Испорти промпт так, чтобы сломать один кейс, но не другие. Ловит ли твой набор именно эту регрессию? Какие кейсы прошли?
- Раздели набор на критический (per-case gate) и расширенный (порог ≥ 90%). Какие кейсы куда и почему?
- Напиши псевдокод pytest-гейта, который блокирует «мёрж» при регрессии. Как отличишь настоящую регрессию от шума судьи?
- Прикинь стоимость: набор из 200 кейсов на каждый PR — это сколько LLM-вызовов? Как удешевить (что гонять на каждый PR, что реже)?
- Со звёздочкой: возьми «баг» из прод-трейса (LangSmith/Langfuse), преврати его в golden-кейс и убедись, что регрессия по нему теперь ловится автоматически.
Что дальше
Раздел «Оценка качества LLM-систем» закрыт: ты умеешь мерить качество offline и человеком, масштабировать оценку судьёй, сравнивать версии через A/B и защищать от деградации regression-набором. Дальше — раздел «Тестирование агентов»: как тестировать агента как программу (unit, integration, оценка траектории), а не только как «генератор ответов». Golden dataset из этого урока станет там evaluation dataset.