Регрессионное тестирование промптов: ИИ-модель в роли читателя пропустила три из четырёх текстов, которые я правил

Я правлю промпты агентов, которые пишут и проверяют тексты. Чтобы правка не сломала то, что раньше работало, я собрал набор примеров с известным ответом и прогнал по нему проверку, где ИИ-модель играет читателя. Сильно правленые тексты она чаще пропускала, чем возвращала.
В набор вошли тексты двух видов: одни я потом сильно переписывал руками, другие почти не тронул. Если принять правку промпта по такой проверке, брак уйдёт дальше, а проверка будет гореть зелёным.
Из чего собрать набор для тестирования LLM
Регрессионное тестирование промпта — это прогон одних и тех же примеров до правки и после неё: я смотрю, не стало ли хуже там, где раньше было хорошо. Документация Anthropic советует брать примеры из реальных задач и добавлять пограничные входы: пустые данные, слишком длинный ввод, случаи, где расходятся даже люди. OpenAI предлагает запускать оценку на каждое изменение и брать случаи из логов. Hamel Husain пишет, что тесты растут из найденных отказов.
Эталон у меня — судьба текста: насколько опубликованная версия совпадает с той, что выдал агент. Совпадение — доля одинаковых предложений: SequenceMatcher из модуля difflib сравнивает два списка предложений. Балл модели-судьи в эталон я не беру: эталон должен идти от человека. Совпадение ниже 85% значит, что я правил сильно, это плохой пример. Совпадение от 95% и выше — хороший пример. Всё, что между этими порогами, в набор не попадает: ответ по таким текстам неясен.

Какие две ошибки считать при тестировании ИИ-агентов
Ошибок две, и я считаю их отдельно. Пропущенный брак: плохой пример прошёл проверку. Ложная тревога: хороший пример проверка остановила. Если считать одну общую долю совпадений с человеком, эти ошибки сливаются. Husain приводит расчёт: судья, который пропускает все тексты подряд, при 95% хороших текстов совпадает с человеком в 95% случаев и не находит ни одной ошибки.
Поэтому после прогона у меня два счётчика, и правку промпта я принимаю, только если не вырос ни один из них.
Что показала сверка читателя с моими правками
Я взял 20 материалов, у которых известно совпадение с опубликованной версией, и сравнил его с оценкой читателя. Читатель у меня — три роли, каждая отвечает на четыре вопроса по шкале от 1 до 10. В зачёт идёт минимальная оценка, порог — 7. Автоматическая сводка называет согласием 15 текстов из 20, но пять из них лежат между порогами и без разметки. Среди размеченных читатель сошёлся со мной в 10 случаях: один пойманный брак и девять верно пропущенных хороших текстов. Разошёлся в пяти: три пропущенных брака и две ложные тревоги.
Раздельный счёт даёт два знаменателя. Сильно правленых текстов четыре, и читатель вернул из них один. Совпадение у этих текстов от 64% до 79%. Почти нетронутых одиннадцать, из них он вернул два. Ещё пять текстов попали между порогами и в счёт не пошли.

Ещё один пропуск, вне этой сводки, я разобрал отдельно. На первом проходе читатели поставили статье минимум 5 и вернули её, после доработки поставили 7 и пропустили. Я забраковал её целиком и переписал от первого абзаца до финала. Причина нашлась в анкете: четыре вопроса оценивают, как написано, а не о чём. Треть текста ушла на разбор метода, и оценки от этого не упали. Вопроса, ради того ли человек открыл статью, в анкете нет. Теперь, если бриф задаёт пропорцию частей, читатель обязан посчитать долю каждой части и назвать её, даже когда все оценки выше порога.
Тот же прогон я делаю при смене ИИ-модели. Перед тем как перевести читателей с Opus на Sonnet, я прогнал Sonnet на 12 версиях текстов, у которых уже были оценки Opus, с теми же ролями и тем же промптом. Оценки совпали в пределах одного балла в 89% случаев. В среднем Sonnet ставит на 0,66 балла ниже. Решение вернуть или пропустить совпало только в 7 версиях из 12. Все расхождения в одну сторону: 5 версий, которые Opus пропустил, Sonnet вернул бы. Читателей я оставил на Opus: на Sonnet возвратов было бы больше, и экономии бы не вышло. Это не противоречит находке про пропуски: более строгий Sonnet вернул бы и хорошие версии, а кто прав в этих пяти, без разметки неизвестно.
Сама проверка тоже ломается. Моя проверка понятности заголовков за одну ночь остановила 4 статьи из 7 (хорошие ли они были, я не размечал): читатель не в теме на каждый вариант заголовка находил новое уточнение, и проверка перестала сходиться. Я выключил её одной константой, а логику и 13 тестов к ней оставил. Тесты по-прежнему требуют отказа на устаревший результат, пустой ответ и неверный JSON.
Скрипт на Python: сравнить две версии проверки на одном наборе
Набор лежит в cases.json: список объектов вида {"text": "...", "bad": true}. Функции before и after принимают текст и возвращают True, если проверка с этой версией промпта его остановила.
import json
def score(flags, cases):
bad = [c["text"] for c in cases if c["bad"]]
good = [c["text"] for c in cases if not c["bad"]]
missed = sum(not flags(t) for t in bad) / max(len(bad), 1)
alarms = sum(bool(flags(t)) for t in good) / max(len(good), 1)
return missed, alarms
def compare(before, after, path="cases.json"):
with open(path, encoding="utf-8") as f:
cases = json.load(f)
m0, a0 = score(before, cases)
m1, a1 = score(after, cases)
print(f"пропущенный брак: {m0:.0%} -> {m1:.0%}")
print(f"ложные тревоги: {a0:.0%} -> {a1:.0%}")
return "принять" if m1 <= m0 and a1 <= a0 else "откатить"Скрипт гоняет каждую версию один раз, а проверка на модели недетерминирована: один и тот же текст может пройти в одном прогоне и вернуться в другом. Совет: прогонять каждую версию несколько раз и смотреть разброс. При четырёх плохих примерах один случай сдвигает долю на 25 п. п.
Набор я держу по четырём правилам:
На каждую найденную ошибку добавляю заведомо плохой пример.
Рядом кладу хороший пример, чтобы после правки проверка не начала останавливать живой текст. Так устроены тесты моего детектора стиля ИИ, всего в них 16 проверок. Восемь из них требуют поймать плохой пример, восемь — пропустить хороший. Плохой пример — служебная фраза чат-бота, протёкшая в начало статьи, хороший — цена «1 000 ₽»: проверка ловит в числах узкий неразрывный пробел и не должна путать с ним обычный.
Набор гоняю до и после каждой правки промпта. Отдельно раз в две недели проверяю саму проверку на работоспособность: заведомо плохой пример она обязана поймать.
Оценку модели-судьи сверяю с правками человека и считаю обе ошибки отдельно.
Насколько надёжны числа калибровки читателя
Выборки маленькие: 4 и 11 текстов в разбивке, 12 версий при смене модели, по ним видно устройство счёта, а точность метода из них не оценить. У 18 из 20 текстов оценки ставили прежние роли читателей: набор ролей потом сменился. Эталон — суждение одного человека, меня.
А что служит эталоном в вашем наборе, если плохие примеры вы никогда не правили руками?
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.