Эвалы для LLM-фич. Часть 1: зачем они нужны
- четверг, 15 октября 2026 г. в 00:00:11
Всем привет!
Меня зовут Тарас Малиновский, я бэкенд‑разработчик, работаю на Go. Последний год почти целиком ушёл на проекты в сфере искусственного интеллекта: делал ассистентов, агентов, разные инструменты и навыки.
Недавно участвовал в хакатоне от Podlodka AI Club: мы собирали систему памяти для агентов. Саму память реализовали довольно быстро, а вот доказать, что она реально работает, оказалось куда сложнее. На это и ушло больше всего времени.
Этот опыт будто щёлкнул выключателем: именно тогда до меня по‑настоящему дошло, в чём секрет качественного AI‑продукта. Я и раньше пользовался эвалами, но в условиях хакатона, с его бешеными темпами и жёсткой гонкой, их ценность стала особенно очевидной. Ведь поменять промпт за вечер или переключить модель в конфиге одной строчкой несложно. А вот понять, стало ли от этого лучше, без чётких критериев не получится. Иначе любые изменения так и останутся просто догадками.
Именно поэтому я решил посвятить этот цикл статей оценке качества (эвалам) разных компонентов искусственного интеллекта: от простых промптов до сложных систем и агентов. Разберём, как создавать эффективные механизмы оценки: начнём с базовых проверок, которые можно быстро реализовать, и дойдём до полноценных пайплайнов, автоматически собирающих данные из production.
Предупреждаю: текст может получиться довольно объёмным, так что запасайтесь терпением. Все примеры взяты из реальных проектов, над которыми я работал.
Примеры в этой статье будут основаны на проекте: Job Radar . Он собирает IT‑вакансии из Telegram‑каналов, отсеивает мусор и скам, а ещё формирует еженедельную статистику по рынку и проводит анализ резюме.
В первой части подробно разберём, зачем нужны системы оценки качества и что происходит с функциональностью на базе больших языковых моделей (LLM), когда таких систем нет.
Оглавление
Чтобы отвечать на вопрос «стало ли лучше» не на глаз, сначала договоримся, что такое эвал.
Эвал включает три ключевых компонента:
Случаи: набор входных данных для проверки.
Проверки: критерии оценки результата.
Оценка: метрики, показывающие, насколько успешно выполнена задача.
Выбирайте самую простую проверку, которая поможет заметить ошибку.
Возьмём проект Job Hunt (бот Job Radar). Он стартовал как пет-проект, а потом стал полноценным сервисом. Сервис собирает IT‑вакансии из Telegram‑каналов и распределяет их по профессиональным направлениям.
Одна из главных фич сервиса: классификация вакансий по профессиональным направлениям (бэкенд, HR, QA и так далее). Если модель ошибается, вакансия оказывается не в той категории, и подписчики её не увидят. При этом ошибки проходят незаметно: без исключений и записей в логах.
Случаи:
Входные данные: текст вакансии.
Критерий успеха: верная метка направления плюс пояснение, почему она подходит.
Проверка:
Код, который забирает ответ от фичи и эталонную метку.
Сравнение метки с эталоном: случай считают пройденным, если ответ совпадает с эталоном.
Итог:
Анализ результатов: сколько случаев прошло, сколько нет.
Сопоставление итогов для разных версий промпта или модели.
Важно прогонять набор случаев в той же конфигурации, что и в проде, с идентичными параметрами, так результаты будут сопоставимы.
Случаи удобно держать в формате jsonl (JSON Lines): каждый JSON‑объект описывает отдельный случай.
{"id": "hr_channel_ops_specialist", "input": "Junior Operations Specialist ... #hr", "expected": "hr", "note": "пост из HR-канала; модель путает с it"}
Поле note поясняет, зачем случай добавили в набор. Спустя полгода именно оно подскажет, можно ли его удалить. Минимальный раннер на Go укладывается в пару десятков строк.
type Case struct { ID string `json:"id"` Input string `json:"input"` Expected string `json:"expected"` Note string `json:"note"` } func main() { f, err := os.Open("cases.jsonl") if err != nil { log.Fatal(err) } defer f.Close() var total, passed int sc := bufio.NewScanner(f) sc.Buffer(make([]byte, 1<<20), 1<<20) for sc.Scan() { var c Case if err := json.Unmarshal(sc.Bytes(), &c); err != nil { log.Fatal(err) } got, err := classify(context.Background(), c.Input) // ваш вызов модели total++ if err == nil && got == c.Expected { passed++ continue } fmt.Printf("FAIL %s: ждали %q, получили %q (%v)\n", c.ID, c.Expected, got, err) } if err := sc.Err(); err != nil { log.Fatal(err) } fmt.Printf("прошло %d из %d\n", passed, total) if passed < total { os.Exit(1) } }
Так эвал даёт объективную оценку качества фичи и помогает вовремя находить и исправлять ошибки.
Проверки делят на четыре основных вида, у каждого своя цена и нюансы:
Детерминированные проверки:
Работают как обычный код: опираются на JSON‑схемы, регулярные выражения, списки запрещённых фраз или набор допустимых значений.
Пример: проверка формата данных или поиск конкретных ключевых слов.
Плюсы: быстрота, точность, простая автоматизация.
Дополнительный пример: детектирование утечки системного промпта с помощью «канарейки». Для этого в промпт заранее встраивают сгенерированный «секретный» код, своего рода метку. Затем каждый ответ модели проверяют на наличие этой метки. Если она появляется в выводе, это сигнал о возможной утечке промпта. Такой подход даёт простой, но надёжный способ отслеживать непреднамеренную выдачу служебных инструкций.
Сравнение с эталоном:
Подходит, когда у ответа есть единственная правильная метка, как в нашем классификаторе.
Пример: сопоставление полученной метки с заранее известным правильным ответом.
Плюсы: высокая точность, несложная реализация.
Человеческая оценка:
Оценку проводит человек по заранее заданным критериям.
Пример: ручная проверка сложных случаев, где нужен глубокий разбор.
Плюсы: максимальная точность.
Минусы: низкая скорость, требуется участие человека, возможна только офлайн.
LLM‑судья:
Оценку делает другая языковая модель.
Пример: привлечение второй модели для проверки результатов.
Плюсы: хорошая масштабируемость.
Минусы: каждый вызов стоит дорого, возможны ошибки. Помните, что LLM‑судья не универсальное решение: ему нужны дополнительные инструменты и пристальное внимание. Ошибки могут появляться из‑за неточности моделей или неудачно подобранного контекста.
Главное правило: берите самую недорогую и эффективную проверку, которой достаточно, чтобы выявить ошибку. Рекомендации:
Детерминированные проверки: для простых и быстрых задач.
Сравнение с эталоном: для задач, где есть один верный ответ.
Человеческая оценка: для критически важных и сложных случаев.
LLM‑судья: когда важна масштабируемость и автоматизация, даже с учётом стоимости.
Выбор проверки определяют специфика задачи, её сложность и доступный бюджет.

Справа показано, где запускают каждый вид проверки; про прод подробнее в 1.4.
Эвал и бенчмарк это разные подходы к оценке качества моделей и фич. Важно видеть разницу.
Бенчмарк
Цель: сравнить модели на типовых задачах и наборах данных.
Применение: публичные рейтинги и сопоставления разных моделей на стандартных данных.
Особенности:
Использует типовые задачи и данные.
Не учитывает особенности конкретной фичи или проекта.
Может давать неточные выводы для узких, специфических случаев.
Пример: по публичным бенчмаркам модель A может выглядеть лучше модели B на общих задачах, но для вашей задачи точнее окажется модель B.
Эвал
Цель: проверить конкретную фичу на ваших данных.
Применение: используют в продуктовой разработке, чтобы оценить качество работы фичи.
Особенности:
Учитывает специфику вашего проекта и данных.
Строится на реальных сценариях использования.
Помогает находить ошибки, характерные именно для вашей задачи.
Пример применения эвала
На примере фичи классификации вакансий:
Эвал: проверяет, верно ли модель относит вакансии к нужным направлениям.
Бенчмарк: сравнивает общую точность модели на стандартных наборах данных.
Эвал подтверждает, что фича корректно работает на ваших данных и в ваших сценариях. Бенчмарк даёт общую оценку модели, не привязываясь к особенностям проекта.
С классическими юнит‑тестами всё понятно: есть вход, есть один верный ответ, тест просто сверяет результат с эталоном. С LLM‑фичами такой подход быстро натыкается на реальность: правильных ответов нередко несколько, а сами ответы ещё и «плавают», то есть меняются даже без ваших правок в коде.
На хакатоне мы проверяли, работает ли у агента память. Вопрос ставили примерно так: «Упомянул ли агент факт из прошлого разговора?» Тут и вылезла неочевидная штука: агент реально учитывал тот факт при построении ответа, но не проговаривал его вслух. Для человека это нормальный, осмысленный ответ, а тест его отклонял, потому что не находил точного упоминания.
Мы долго спорили, пока не переформулировали критерий: стали проверять не «упомянул ли», а «изменился ли ответ при включённой памяти». То есть сравнивали ответы агента с памятью и без неё. Как только перешли на такой критерий, всё встало на свои места: стало видно именно то, что хотели измерить.
С простыми задачами вроде классификации чуть проще: там обычно одна правильная метка, и проверка сводится к простому сравнению строк. Но и тут стабильность лишь видимость. Даже при temperature 0 ответы не обязаны быть одинаковыми. Причина не в самой модели, а в инфраструктуре: провайдер батчит запросы, а под тем же именем модели могут крутить разные версии на разном железе.
Случилась ситуация, которой я вообще не ждал. Классификатор ходил в модель через LiteLLM‑прокси, который раскидывал запросы по бесплатным провайдерам.
Придётся сделать небольшое отступление: дальше я часто буду упоминать этот прокси и связанные с ним проблемы, потому что в итоге они оказались очень поучительными. Идея инструмента простая: есть гипотеза и есть набор бесплатных моделей, элементарный UI. Вставляете свои ключи и работаете. Я собрал там зоопарк из бесплатных моделей. Можно настраивать очерёдность, выбирать модели под задачи. В общем, там заложена база под оркестратор, но у меня до неё руки так и не дошли.
В чём польза такого инструмента? Я заметил: если эвал написан грамотно, а данные подобраны правильно, результат перестаёт «плавать» и стабильно работает на моделях от разных вендоров. Для меня это стало своего рода знаком качества эвала.
Часто сталкивался с неприятной ситуацией: система обвешана тестами, и тут выкатывают новую модель, и вдруг отваливаются целые модули, потому что поведение дефолтной модели изменилось. Это неприятно, но зато приучает к дисциплине и помогает правильно выстраивать направления зависимостей.
Я решил сравнить две модели и прогнал один и тот же набор из 140 постов дважды. В первый раз получил 130 из 140, во второй только 100 из 140. Когда стал разбирать по кейсам, выяснилось неожиданное: вторая модель (Qwen) вообще ни на один пост не ответила: её провайдер был недоступен. Прокси молча переключился на запасные модели. По факту нам отвечала та же модель (Gemma), что и в первом прогоне, а на тридцати постах вообще не было ответа. Я думал, что сравниваю две разные модели, а на деле сравнивал одну модель с самой собой, плюс таймауты.
Ещё один сюрприз подкинул кэш. (Кэш тут нужен и для ускорения ответа и для экономии токенов) Прокси хранил ответы пять минут. Когда я запускал повторные прогоны подряд, они отрабатывали за одну‑пять секунд и возвращали старые ответы. Казалось, что модель идеально стабильна, хотя её даже не спрашивали.
Из этих историй получились простые, но рабочие правила:
Считайте результат по правилу «k из n». Например, «прошёл в двух из трёх прогонов». Это честнее, чем полагаться на один запуск.
На время замеров отключайте кэш. Иначе вы тестируете не модель, а хранилище ответов.
Запускайте прогоны регулярно: в моём CI эвалы запускаются при каждом изменении промпта, а прогон по расписанию ловит смену модели у провайдера.
Главное, что стоит держать в голове: поведение LLM‑фичи может меняться вне вашего контроля. И единственный способ не гадать, а знать: иметь свой набор случаев и регулярно его прогонять.

Под каждым отличием указано, чем его закрывает эвал.
Когда видишь строчку «качество упало с 93 до 71%», первая реакция: паника, кажется, что‑то серьёзно сломалось. Но средний процент похож на среднюю температуру по больнице: он прячет детали. Дифф по конкретным случаям сразу показывает, что именно пошло не так.
Возьмём тот самый прогон: средний процент рухнул на 22 пункта. А если посмотреть по строчкам, картина совсем другая:
Тридцать случаев сломались из‑за того, что модель вообще не дала ответ: это про таймауты и доступность провайдеров, а не про смысл.
Десять кейсов (например, про операционного специалиста) не проходят никогда: это стабильные ошибки выбранной модели. Кейс про операционного специалиста прошёл только у gpt‑oss‑20b в пробном прогоне.
То есть падение среднего скрывало две разные проблемы: инфраструктурную (нет ответа) и смысловую (модель системно ошибается на конкретном типе запросов).
Ещё одна ловушка среднего: вес одного случая. В наборе из 140 примеров один кейс весит примерно 0,7 процентного пункта. Значит, если вы внесли правку, которая улучшила работу на одном сложном кейсе, вы этого просто не увидите: изменение теряется на фоне «шума» от плавающих ответов. Дифф же сразу покажет: «раньше тут был FAIL, теперь PASS».
До релиза эвал запускают в двух местах, и у каждого своя задача:
Офлайн‑прогоны. Их делают вручную до и после правки, чтобы решить, стоит ли вообще коммитить изменение. Это быстрый способ понять: «стало лучше или хуже именно на тех кейсах, которые мы знаем».
CI‑пайплайн. Здесь всё автоматизировано: сборка падает, если ломается случай, который раньше проходил. При этом нестабильные кейсы (которые сами по себе плавают) не должны блокировать сборку: для них ведут отдельный отчёт и разбирают отдельно.
Важно, что и офлайн, и в CI проверяют только то, что уже есть в наборе. Новые типы запросов, которые приносят пользователи каждый день, в этот набор ещё не попали: их ловят уже в проде (об этом в 1.4).
Допустим, у вас есть набор из 14 случаев. Вы сделали правку промпта и прогнали его. Результаты:
Случай | Ожидаемый | Было | Стало | Статус |
|---|---|---|---|---|
english_one_liner_k_range | backend | ✅ backend | ❌ (нет ответа) | Новый FAIL |
office_exact_salary | it | ✅ it | ❌ (нет ответа) | Новый FAIL |
sectional_emoji_markers | frontend | ✅ frontend | ❌ (нет ответа) | Новый FAIL |
hr_channel_ops_specialist | hr | ❌ it | ❌ it | Стабильный FAIL |
остальные 10 | ✅ | ✅ | Без изменений |
Средний процент упал, но из таблицы сразу понятно, где искать проблему: три случая отвалились по инфраструктуре, один оказался системной ошибкой модели. Дальше действия простые: проверить доступность провайдеров и подумать, нужна ли тут смена модели или дробление задачи.
Главный вывод: не доверяйте одному среднему проценту. Он слишком грубо сглаживает картину. Смотрите на дифф по кейсам: так вы сразу увидите, что именно сломалось, сможете отделить инфраструктурные проблемы от смысловых ошибок и принимать решения на реальных данных, а не на ощущениях.
Когда фича попадает в прод, эвал не останавливается, он просто меняет форму: вместо заранее заготовленных кейсов мы оцениваем каждый живой запрос. И тут появляется ключевое отличие: для новых пользовательских запросов у нас нет «правильного ответа», который мы записали заранее. Поэтому в проде делают упор на проверки, которым эталон не нужен: они смотрят на свойства самого ответа и поведение системы.
Каждый запрос к модели нужно сохранять в виде трейса. Минимальный, но критически важный набор полей:
Вход: исходный запрос пользователя.
Ответ модели: полный текст, который вернула LLM.
Какая модель реально ответила: это особенно важно, если вы используете прокси, цепочки моделей и автопереключение на запасные варианты.
Время ответа и количество токенов: чтобы видеть нагрузку и стоимость.
Эти трейсы служат основой для метрик, дашбордов и алертов. Подход привычный по классическому мониторингу, но для LLM‑фич одних «технических» метрик категорически мало.
Такие метрики, как время ответа, HTTP‑коды и потребление памяти, покажут, что сервис «жив». Но LLM‑фича может ломаться тихо: модель уверенно вернёт неверный результат с кодом 200, и обычный мониторинг посчитает запрос успешным.
Поэтому рядом с техническими метриками обязательно добавляют метрики качества, которые смотрят именно на содержание и поведение ответов:
Доля ответов, не прошедших дешёвые проверки (например, не прошёл JSON, сработала регулярка на запрещённую фразу).
Доля пустых ответов и отказов: если модель начала чаще уходить в таймаут или вообще ничего не возвращать, это тревожный сигнал.
Распределение ответов по меткам: очень мощный индикатор без эталона. Если вчера в категорию «HR» попадало 5 % постов, а сегодня 30 %, значит, что‑то сломалось.(я призываю вас работать с эвристиками, словарями, регулярками все что может взвесить быстро и детерминированно нужно использовать)
Доля дизлайков: прямая обратная связь от пользователей.(оценка пользователей: важнейший показатель для расширения эвалов положительными/отрицательными примерами)
Сдвиг распределения относится к самым наглядным признакам деградации. Он виден даже без заранее записанного эталона и часто первым подсказывает, что модель начала вести себя странно.

После обновления модели сервис работает как прежде, и поломку видно только по метрикам, которые считают по самим ответам.
Из раздела 1.2 мы помним ситуацию с двумя моделями: провайдер Qwen стал недоступен, прокси молча переключился на запасные модели, а мы думали, что тестируем именно Qwen. В проде такую подмену можно было поймать в тот же день, просто отслеживая одну метрику: имя модели, которая реально ответила.
На графике это выглядело бы предельно ясно: доля Qwen упала до нуля, а доля запасных моделей (например, Gemma) резко выросла. Офлайн‑прогон эту проблему заметил только потому, что я вручную разбирал случаи. В проде такого ручного разбора нет, поэтому автоматические метрики по фактическому исполнителю критически важны.
Часть проверок запускают ещё до того, как ответ уйдёт пользователю. Это так называемые guardrails (защитные барьеры). Они либо блокируют ответ, либо подменяют его безопасным вариантом.
Яркий пример: маркер‑ловушка (canary token), секретная строка в системном промпте, о которой говорил выше. Если она появляется в ответе модели, это признак утечки промпта.
В двух разных контекстах маркер работает по‑разному:
В офлайн‑наборе мы проверяем, что известные атаки не вытаскивают системный промпт.
В проде маркер работает как сигнализация: он блокирует утечку даже на атаке, которой не было в наборе + наш набор пополнится еще одним кейсом.
Guardrails решают сразу две задачи:
Защищают пользователя от заведомо плохих, некорректных или потенциально опасных ответов.
Дают ранний сигнал о проблемах в поведении модели.
LLM‑судья и ручная оценка остаются сильными инструментами, но прогонять их на всём трафике слишком дорого. Поэтому их применяют на выборке трейсов: например, на случайных 1–5 % запросов или на тех, где сработали алерты либо стоят дизлайки. Так мы получаем качественную оценку без огромных затрат.
Одна из частей системы: замкнутая петля, в которой каждый инцидент становится новым кейсом в офлайн‑наборе. Как это устроено на практике:
На дашборде срабатывает алерт или пользователь ставит дизлайк, это триггер запуска пайплайна.
Мы берём этот трейс и превращаем его в новый случай: сохраняем вход, ожидаемый результат и заметку, откуда он взялся (например, «алерт по резкому росту доли HR», «дизлайк на посте про операционного специалиста»).
Добавляем его в офлайн‑набор.
Следующий прогон проверяет, что ошибка не вернулась.
Так набор постоянно растёт и отражает реальные проблемы, с которыми сталкиваются пользователи, а не только те, о которых мы думали заранее.

Нижняя стрелка и есть петля: без неё набор застывает на том, что вы знали в день его создания.
Главный вывод: мониторинг в проде служит естественным продолжением эвала. Он ловит сбои раньше, чем их заметит пользователь, помогает отличать инфраструктурные проблемы от деградации качества модели и формирует новые тесты из реальных инцидентов. А guardrails и метрики распределения дают быстрые сигналы даже там, где нет заранее известного эталона.
Одна из скрытых суперспособностей эвала: он помогает осознанно «спускаться» на более лёгкие (и дешёвые) модели, не рискуя качеством. Без набора переход держится на ощущении «вроде отвечает так же». С набором это измерение: лёгкая модель либо проходит те же кейсы, либо нет, а дифф сразу показывает, где она спотыкается.
Процесс выглядит как итеративная лестница:
Берём самую сильную из доступных моделей. Под неё собираем набор кейсов, и её результат становится планкой качества.
Разбираем кейсы, где даже сильная модель ошибается. Чаще всего это повод не менять модель, а проверить постановку задачи и эталон: возможно, формулировка неоднозначна или ожидаемый ответ не отражает реальную потребность. Эти кейсы нужно разобрать до того, как двигаться дальше.
Прогоняем тот же набор на модели полегче. Если она проходит на уровне планки, спускаемся ещё на ступень ниже. Если нет, смотрим на проваленные кейсы: они показывают, где задачу пора разбить на шаги.
Дробим задачу. Вместо одного запроса «прочитай пост и реши всё сразу» делаем несколько простых шагов, у каждого своя проверка. Например, сначала «определи, вакансия ли это», потом «выдели ключевые навыки», потом «назначь категорию».
Снова прогоняем набор. И так итерация за итерацией, пока есть куда спускаться.

Каждый круг заканчивается одним из двух исходов: либо модель стала легче (дешевле и быстрее), либо задача стала проще (разбита на понятные шаги с отдельными проверками).
Уверенность в выводе ограничена размером набора. В наборе из 140 кейсов один случай весит примерно 0,7 процентного пункта (см. 1.3). Значит, если лёгкая модель прошла 130 из 140, это ещё не гарантия, что она «справляется»: отдельные успехи могут быть случайными, а отдельные провалы несистемными.
Поэтому вывод делают не по одному прогону, а по правилу «k из n» из 1.2 (например, «прошла 2 из 3 прогонов») и обязательно без кэша, чтобы не тестировать хранилище ответов вместо модели.
Если нет записанных кейсов и регулярного прогона, сбой первым замечает человек, и это почти всегда хуже: в лучшем случае вы сами, в худшем пользователь в публичном посте.
Режим сбоя это повторяющийся тип ошибки фичи: выдуманная политика, несуществующее звание, пропущенная приманка. Каждый такой режим можно записать случаем с проверкой.
Вот несколько реальных историй, которые показывают, как это выглядит на практике:
Cursor, апрель 2025. Бот поддержки сообщил пользователю, что подписка теперь работает только на одном устройстве. Такой политики не было. Сбой заметил пользователь в посте на Reddit, часть людей отменила подписки (incidentdatabase.ai). Эвал поймал бы это с кейсом «вопрос о нескольких устройствах» и эталоном из документа политики. Но вопрос, которого нет в наборе, эвал не спасёт, поэтому набор нужно пополнять по петле из 1.4.
Replit, июль 2025. Агент для программирования удалил рабочую базу и сообщил, что откат невозможен, хотя откат работал. Сбой заметил сам пользователь и рассказал об этом публично (The Register, 21.07.2025). Эвал мог бы поймать ложное утверждение «откат невозможен» через проверку с реальным состоянием базы. Но контроль доступа эвал не заменяет.
OLG Hamm, май 2026. Чат‑бот называл руководителей клиники специалистами по пластической хирургии и придумал им несуществующие звания. Сбой заметила служба защиты потребителей и подала иск. 12 мая 2026 года Высший земельный суд Хамма (дело 4 UKl 3/25) запретил эти заявления, решение вступило в силу. Такой режим сбоя ловится самой дешёвой проверкой: звания в ответе сверяются со списком реальных. Эвал не снимает с компании ответственность за слова бота, но даёт шанс узнать о выдумке раньше суда.
Скам «забирайте 165 000 ₽», август 2026. В нашем агрегаторе из 1.1 перед классификацией пост проходит две стадии: гейт решает, вакансия ли это, а стадия риска решает, не скам ли. Рассылка про «вы победитель» прошла через гейт как вакансия (сумма была воспринята как зарплата) и через стадию риска как «чисто». Сбой заметили глазами при чтении ленты. Тесты были зелёными, потому что проверяли код, а не решения конвейера на реальных объявлениях. Эвал проверяет решение независимо от того, кто его принимает: правила это или LLM.

В трёх историях из четырёх о сбое сообщили снаружи: пользователь или организация, которая подала иск. Это и есть главный риск отсутствия эвала. Скам мы заметили сами, и это лучший из исходов.
Если у вас совпадает хотя бы два пункта из списка, пора собирать эвал:
❌ Правку промпта проверяют глазами на 3–5 примерах.
❌ О сбое узнали от пользователя (как в истории с Cursor).
❌ Нет записанного набора случаев (как в случае со скамом).
❌ Модель за алиасом или прокси меняется без прогона набора (см. 1.2).
❌ Смотрят только средний процент без диффа по кейсам (см. 1.3).
❌ В проде смотрят только на время ответа и коды ошибок, а метрик качества нет (см. 1.4).
Вернусь к хакатону из 1.2: спор о том, работает ли память агента, закончился только тогда, когда мы записали 16 пар кейсов и критерий «изменился ли ответ». В итоговом прогоне агент с памятью прошёл 13 из 16, агент без памяти 0 из 16. Причём ноль был не менее важен, чем 13: он доказывал, что набор нельзя пройти без памяти, значит, он действительно измеряет именно её. До этого «работает» значило «нам показалось».

Важное правило: если контроль проходит почти так же, как сама фича, набор нужно переделывать до любых выводов. Иначе вы измеряете не фичу, а шум.
Откройте последние 50 ответов своей фичи в логах или трейсах и:
Запишите строкой в формате jsonl каждую найденную ошибку, как в примере из 1.1.
Рядом добавьте несколько верных ответов: они помогут сбалансировать набор.
Важно помнить, где эвал не поможет:
Эвал ловит только описанные режимы сбоя. Если в наборе нет кейса про «выдуманные звания», он не поймает эту проблему.
Не заменяет контроль доступа и другие системные меры безопасности. Эвал может проверить, что агент не сообщает «откат невозможен», но не может запретить ему удалять базу.
Не снимает с компании ответственности за слова бота. Даже если эвал вовремя нашёл ошибку, за публичные заявления отвечает бизнес.
Маленький набор шумит. При малом числе кейсов единичные сбои сильно двигают проценты и мешают делать уверенные выводы.
Главный итог: для LLM‑фичи фраза «работает» должна опираться не на ощущение, а на объективный критерий: прохождение записанного набора кейсов. Это превращает субъективную оценку в измеримый процесс и позволяет принимать решения на данных, а не на догадках.
📌 Во второй части разберём, чем писать эвалы: когда хватает простого раннера на Go, когда стоит взять готовый инструмент, и как собрать первый набор за пять понятных шагов.
Агрегатор из этой статьи можно потрогать: Job Radar собирает IT‑вакансии из Telegram‑каналов и отсеивает мусор и скам (про это я обязательно расскажу дальше, любопытно, что детерминированные инструменты справляются лучше с детектированием скама, чем эвалы моделей), также добавил еженедельную статистику по рынку и анализ резюме