Суть Читаем реальный код eval-харнесса, judge-промпт и его скоринг, diff golden set и CI regression gate — затем выбираем поведение или самый рычажный фикс, который senior-инженер сделает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Evals живут в коде: харнесс, который скорит кейсы, judge-промпт, возвращающий вердикт, diff против прошлого релиза и CI-гейт, блокирующий merge. Прочитай каждый и выбери, что senior-инженер фиксит первым.
Цель
Отработай цикл, который запускаешь при сборке eval-набора: прочитать харнесс, заметить, где оценка лжёт или judge неверно запромпчен, и взять структурный фикс до подкрутки порогов.
Сниппет 1 — eval-харнесс
def run_eval(cases, model): passed = 0 for c in cases: out = model(c["input"]) if out.strip() == c["expected"].strip(): # exact-match scorer passed += 1 return passed / len(cases) # single accuracy number
Викторина
Completed
Этот харнесс оценивает фичу свободного Q&A через exact string match и выдаёт одно число accuracy. Какой фикс самый рычажный?
Heads-up Повтор не чинит скорер — exact match всё равно отвергает валидные парафразы на каждой попытке. Это лишь множит стоимость. Дефект — метод скоринга, а не число семплов на кейс.
Heads-up Сдвиг порога на сломанном скорере прячет проблему вместо измерения качества. Фикс — judge или программная проверка, реально отражающая корректность, а не более мягкая отсечка по бессмысленному числу.
Heads-up Форма нормальна; скорер неверен для типа вывода, и одно глобальное accuracy прячет, какая категория просела. Per-category оценки плюс адекватный типу скорер — вот что делает это actionable.
Сниппет 2 — judge-промпт и скоринг
JUDGE = """Rate the assistant answer from 1-10 for overall quality.Question: {q}Answer: {a}Score:"""def judge_score(q, a, judge_model): text = judge_model(JUDGE.format(q=q, a=a)) # e.g. "I'd say about an 8/10 because..." return int(text.strip()[0]) # take the first char as the score
Викторина
Completed
Две вещи делают этот judge ненадёжным как CI-гейт. Какая пара верна?
Heads-up Дело не в разрешении — более тонкая шкала на непарсимом, безрубричном, невалидированном judge не надёжнее. Дефекты — парсинг вывода и расплывчатая рубрика, а не диапазон.
Heads-up Конкурентность — вопрос производительности, а не корректности. Вердикт judge может быть неверным или непарсимым хоть при sync, хоть при async вызове.
Heads-up Judge — другая стохастическая модель с задокументированными bias, а не ground truth. Неоткалиброванная свободная оценка, парсимая по первому символу, — ровно ловушка «генератор чисел, принятый за тест».
Сниппет 3 — diff golden set
eval run: candidate vs main (golden set, 180 cases) overall: main 0.91 -> candidate 0.90 (-0.01) PASS (threshold -0.02)+ category json_format: main 0.98 -> candidate 0.99 (+0.01)- category refusals: main 0.88 -> candidate 0.61 (-0.27)- category long_context: main 0.84 -> candidate 0.71 (-0.13)
Викторина
Completed
Агрегат сдвинулся на -0.01 и ПРОШЁЛ гейт, но per-category diff рассказывает другое. Как читать правильно?
Heads-up Падение на -0.27 в реальной категории — регрессия, на которую напорются пользователи, независимо от среднего. Агрегирование по категориям — ровно то, как зелёный гейт выкатывает сломанную фичу.
Heads-up Рост одной категории не компенсирует обвал двух. Положительные по сумме агрегаты регулярно прячут тяжёлые per-category регрессии; нужно смотреть распределение, а не среднее.
Heads-up Более жёсткий агрегатный порог всё равно смешивает категории — падение refusals может снова замаскироваться ростом json_format. Гейти per-category, чтобы это всплыло.
Этот «гейт» прогоняет eval на каждом PR и логирует оценку. Почему он на деле не гейт и как починить?
Heads-up Прогон после merge хуже — регрессия уже приземлилась. Нужно на PR, чтобы плохое изменение блокировалось до merge. Дефект в том, что он никогда не валится, а не в триггере.
Heads-up jq читает её нормально — оценка логируется. Job выходит с 0 в любом случае, потому что нет сравнения и нет валящего exit-кода, так что merge ничто не блокирует.
Heads-up Ручной просмотр залогированного числа не масштабируется и не форсится — ревьюеры пропускают его под нагрузкой. Гейт обязан автоматически валить билд на регрессии, чего здесь не происходит никогда.
Итог
Eval-набор читается в коде: скорер должен соответствовать типу вывода (exact match молча валит свободный текст); judge нужен парсимый вердикт, явная рубрика и референс плюс валидация против человеческих меток до доверия; diff golden set читается per-category, потому что агрегаты отмывают регрессии; а CI-гейт — гейт лишь когда сравнивает с baseline и выходит с ненулевым кодом, блокируя merge. Строй харнесс так, чтобы число не могло лгать, затем гейти по худшей категории, а не по среднему.