Evals та LLM-as-judge
Зміст
Уявіть, що ви змінили один рядок у системному промпті чат-бота підтримки — і хочете зрозуміти, чи стало краще. У звичайному застосунку ви б прогнали регресію: тест або зелений, або червоний. Але LLM на той самий вхід двічі відповідає по-різному, «правильних» формулювань — десятки, а «правильно за змістом, але грубувато за тоном» узагалі не ловиться порівнянням рядків. Клікати руками десять прикладів після кожної правки не масштабується, а відчуття «наче краще» — не доказ. Потрібен повторюваний, вимірюваний спосіб сказати «версія B на 8% кращих відповідей, ніж версія A». Саме це роблять evals.
Ця глава — про вимірювання якості LLM-систем. Якщо «Тестування AI-застосунків» відповідає на питання «що взагалі перевіряти в LLM-фічі», то evals відповідають на «як перевіряти це регулярно й у числах». На співбесідах тема середньої ваги: junior-у про неї не питають, але для AQA, який веде AI-фічу, «розкажіть, як ви міряєте якість генерації» — цілком реальне питання, і відповідь «дивлюся очима» одразу видає стелю.
Що таке eval
Eval (evaluation, оцінювання) — це, по суті, тест-набір для недетермінованої системи. Замість «крок → очікуваний результат → passed/failed» тут «вхід → критерій якості → бал». Ключова відмінність від класичного автотесту: вивід оцінюється не булевою рівністю, а скорером (scorer), який може повертати число, і рішення про «пройдено» ухвалюється на агрегованій метриці, а не на кожному окремому прогоні.
Будь-який eval складається з тих самих частин:
- Набір даних (golden set) — репрезентативні входи з еталоном або критерієм, як має виглядати хороша відповідь.
- Система під тестом — конкретна комбінація промпт + модель + параметри (+ RAG, якщо є). Змінили будь-що з цього — це вже інша система, яку треба переміряти.
- Скорер — те, що перетворює вивід на оцінку: детермінована перевірка, модель-суддя або людина.
- Агрегація й поріг — як зі скорів окремих кейсів зробити один вердикт по набору («частка успішних ≥ 90%») і на його основі пропустити зміну далі чи ні.
Головна думка: eval — це не «спитати модель, чи все добре», а інженерний артефакт із зафіксованими даними, скорером і порогом, який дає порівнянні числа між версіями. Без фіксації всіх трьох частин ви міряєте шум.
Golden set
Golden set (еталонний набір, іноді golden dataset) — серце eval. Це курований набір прикладів, на яких ви ганяєте систему. Правило просте й жорстке: eval рівно настільки хороший, наскільки хороший його golden set. Ідеальний скорер на нерепрезентативних даних дасть красиве, але брехливе число.
Що туди кладуть:
- Репрезентативні кейси — типові реальні запити, у пропорціях, близьких до продакшену. Якщо 40% звернень до бота — про статус замовлення, у наборі їх теж має бути близько 40%.
- Крайові випадки (edge cases) — порожній ввід, дуже довгий, іншою мовою, з друкарськими помилками, провокативний, поза доменом («а розкажи анекдот»).
- Регресійні кейси — конкретні приклади, на яких система колись зламалася. Кожен спійманий у проді дефект стає рядком у golden set, щоб не повернутися. Це прямий аналог того, як баг перетворюють на регресійний тест у звичайному тестуванні.
Для кожного входу треба зафіксувати еталон. Він буває двох сортів: точна очікувана відповідь (коли вона одна — «яка столиця Франції») або критерії («має містити номер замовлення, не має обіцяти повернення грошей, тон — ввічливий»). Для генеративних задач другий сорт зустрічається частіше, бо однієї правильної відповіді не існує.
Практичні застороги senior-рівня. По-перше, golden set старіє: продукт міняється, з'являються нові типи запитів — набір треба ревізувати, інакше ви охороняєте вчорашню поведінку. По-друге, дані з golden set не мають потрапляти в промпт як few-shot приклади — інакше ви тестуєте на тому, що вже показали моделі, і бал завищений (це той самий парадокс, що й «навчання на тестовій вибірці» у ML). По-третє, розмір: навіть 50–100 добре підібраних кейсів дають більше сигналу, ніж тисяча одноманітних — важлива різноманітність, а не обсяг.
Способи оцінювання
Скорери утворюють спектр від дешевих і об'єктивних до дорогих і багатих. Правило вибору: бери найдешевший скорер, який ловить те, що тобі треба.
Детерміновані перевірки. Точний збіг, contains, регулярний вираз, валідність JSON проти схеми, «код скомпілювався / тести зелені». Найдешевше, повторюване, об'єктивне — але працює лише там, де є однозначна істина або чіткий інваріант.
// Детерміновані скорери: там, де є чіткий інваріант
type Scorer = (output: string) => boolean;
const hasOrderId: Scorer = (o) => /ORDER-\d{6}/.test(o);
const isValidJson: Scorer = (o) => {
try { JSON.parse(o); return true; } catch { return false; }
};
const noRefundPromise: Scorer = (o) => !/поверн(емо|у) кошт/i.test(o);
Метрики на основі еталона. Коли валідних формулювань багато, точний збіг не годиться. Тут або поверхневі метрики збігу слів (BLEU, ROUGE — рахують перетин n-грам із еталоном; грубі, але дешеві), або семантична близькість через embeddings: перетворити відповідь і еталон на вектори й порівняти косинусною подібністю (cosine similarity). Останнє ловить «те саме іншими словами», але вимагає порога і не розуміє тону чи фактичної коректності.
Людська оцінка (human evaluation). Золотий стандарт для якості, тону, доречності — людина за рубрикою виставляє бал. Мінуси очевидні: повільно, дорого, суб'єктивно, погано масштабується. Тому людей тримають для калібрування й фінального сампла, а не для кожного прогону.
LLM-as-judge — про нього далі окремо; це масштабований компроміс між дешевим детермінізмом і дорогою людиною.
| Спосіб | Вартість | Об'єктивність | Масштаб | Де доречно |
|---|---|---|---|---|
| Детермінований | дуже низька | висока | будь-який | є чіткий інваріант/істина |
| Метрики з еталоном | низька | середня | високий | багато валідних формулювань |
| LLM-as-judge | середня | середня | високий | якість/тон, немає одного еталона |
| Людина | висока | суб'єктивна | низький | калібрування, спірні кейси |
LLM-as-judge
LLM-as-judge — це коли одна модель оцінює вивід іншої. Судді дають вхід, кандидатну відповідь і рубрику (rubric) — критерії оцінювання, — а він повертає бал чи вердикт із поясненням. Ідея приваблива тим, що масштабується як код, а «розуміє» текст майже як людина: ловить релевантність, тон, суперечності, наявність вигадок — те, що регуляркою не спіймаєш.
Два основні режими:
- Поточковий (pointwise) — оцінити одну відповідь за шкалою (наприклад, 1–5) за рубрикою.
- Попарний (pairwise) — сказати, яка з двох відповідей (A чи B) краща. Часто надійніший за абсолютну шкалу: моделі легше порівняти, ніж поставити «об'єктивну» цифру.
Практика, що відрізняє робочий суддя від іграшкового: вимагайте спершу міркування, потім вердикт (chain-of-thought покращує узгодженість оцінок), давайте структурований вивід для машинного парсингу (див. промптинг для QA) і формулюйте рубрику конкретно, з прикладами хорошого й поганого, а не «оціни якість».
// Суддя повертає структурований вердикт, який можна розпарсити
type Verdict = { score: number; reason: string };
async function judge(input: string, candidate: string): Promise<Verdict> {
const prompt = [
'Оціни відповідь за шкалою 1-5. Критерії: релевантність, ввічливий тон,',
'відсутність вигаданих фактів. Спершу міркування, потім рядок JSON.',
`Питання: ${input}`,
`Відповідь: ${candidate}`,
].join('\n');
const raw = await callModel(prompt, { temperature: 0 });
return JSON.parse(extractJson(raw)) as Verdict;
}
Суддя — теж LLM, тож коштує токенів (див. токени й вартість) і сам недетермінований. І, найголовніше, він має упередження.
Упередження судді
Суддя-модель не безсторонній арбітр. Дослідження LLM-as-judge стабільно фіксують кілька системних перекосів — знати їх на senior-рівні обов'язково, бо саме вони роблять красивий бал брехливим.
- Позиційне упередження (position bias). У попарному режимі суддя схильний віддавати перевагу відповіді на певній позиції (частіше першій) незалежно від змісту. Лікування — прогнати обидва порядки й усереднити; якщо переможець міняється від перестановки, це нічия, а не перемога.
- Упередження на багатослівність (verbosity / length bias). Довша, «розлогіша» відповідь оцінюється вище, навіть коли коротка правильніша. Рубрика має явно карати воду.
- Самовподобання (self-enhancement / self-preference bias). Суддя вище оцінює текст у стилі власної родини моделей — тобто модель схильна хвалити саму себе. Тому судити свій же вивід тією ж моделлю — конфлікт інтересів.
- Форматне упередження (formatting bias). Гарне форматування, впевнений тон, ключові слова з рубрики можуть підняти бал без реальної якості; це споріднено з догідливістю (sycophancy) — суддя погоджується з тим, що звучить авторитетно.
- Кучкування балів і слабка арифметика. Судді часто тиснуться до середини шкали (3–4 з 5) і погано рахують/перевіряють числа й логіку — там детермінована перевірка надійніша.
Мітигації зводяться до кількох принципів: перемішувати позиції та усереднювати; тримати рубрику конкретною й прикладною; брати суддею іншу (бажано сильнішу) модель, ніж та, що генерувала; калібрувати суддю проти людських оцінок на самплі (збіг судді з людиною — це метрика самого судді); і не тягнути суддю туди, де впорається детермінована перевірка. Суддя оцінює якість — він не заміняє факт-чек і виконання коду (див. верифікацію результатів AI).
Eval як регресія
Найцінніша роль evals — регресійний гейт. Один раз зібраний golden set + скорер + поріг перетворюються на набір, який ганяють на кожну зміну промпту, моделі чи RAG-конфігурації — рівно як регресійний прогін ганяють на кожну зміну коду в звичайному тестуванні (тема регресії — у розділі «Основи тестування»). Зафіксували базовий бал (baseline) — і будь-яке падіння нижче порога блокує мердж.
Що це дає на практиці. По-перше, промпти й моделі версіонуються так само, як код: зміна промпту — це зміна поведінки продукту, і вона має проходити гейт, а не котитися в прод «бо виглядало краще». По-друге, це ловить тихі регресії: правка, що покращила один тип запитів, часто ламає інший, і без набору ви цього не побачите, доки не поскаржиться користувач. По-третє, evals стають частиною CI/CD (тема — у розділі «Git і CI/CD»): прогін на PR, звіт із діффом балів між гілками, блок за просідання. Важлива відмінність від звичайного gate: поріг тут — не «100% зелено», а статистичний, бо система недетермінована.
Недетермінізм в evals: повтори й пороги
Оскільки та сама система на той самий вхід дає різні відповіді (чому — див. розділ про роботу LLM), один прогін кейсу — це один зразок із розподілу, тобто шум. Побудувати gate на одному прогоні — гарантований флак: сьогодні зелено, завтра червоно, і ви женетеся за примарою, а не за регресією.
Рішення — повтори й пороги замість точної рівності:
- Кожен кейс ганяють N разів і рахують частку успішних (pass rate; іноді кажуть pass@k у сенсі «хоч раз пройшов», але для якості цікавіша саме стабільна частка).
- Поріг ставлять на двох рівнях: скільки повторів має пройти кейс і скільки кейсів має пройти набір. Наприклад: «кейс вважається пройденим, якщо
score >= 4у ≥80% повторів; набір зелений, якщо пройшло ≥90% кейсів». - Знижують температуру на час eval, щоб зменшити розкид, — але навіть за температури 0 повна повторюваність не гарантована, тож повтори не скасовуються.
// Не один прогін, а частка успішних із N повторів
async function passRate(caseInput: string, runs = 5): Promise<number> {
let passed = 0;
for (let i = 0; i < runs; i++) {
const out = await system(caseInput);
if ((await judge(caseInput, out)).score >= 4) passed++;
}
return passed / runs; // 0.8 означає 4 з 5 прогонів пройшли
}
Звідси спосіб мислення: eval-результат — це розподіл, а не точка. Мале падіння середнього на малій вибірці може бути шумом, а не регресією; велике й повторюване падіння — сигнал. Це рідня статистиці флакі-тестів (тема флакі — у розділі «Автоматизація: стратегія»): відрізнити випадкове коливання від справжньої зміни можна лише через повтори й розумні пороги, а не через один червоний прогін.
Інструменти оглядово: promptfoo і DeepEval
Писати harness руками щоразу не обов'язково — є готові фреймворки. Два поширені (станом на липень 2026), обидва з відкритим кодом:
- promptfoo — конфігурований інструмент (декларативний конфіг) для тестування й порівняння промптів; нині частина OpenAI, але лишається open source під MIT. Дозволяє матрицю «промпти × моделі × тест-кейси», підтримує і детерміновані асерти (
contains, регулярки, JSON-схема, кастомна JS-функція), і оцінку суддею (model-graded). Дає side-by-side порівняння версій і легко вбудовується в CI; має окремий режим для ред-тімінгу LLM. - DeepEval — фреймворк на Python у стилі «pytest для LLM»: тести пишуться як звичайні pytest-кейси, а з коробки йдуть готові метрики — від суддівської G-Eval (LLM-суддя, який оцінює за вашими критеріями, попередньо розгорнувши їх у кроки міркування) до спеціалізованих для RAG (релевантність, faithfulness, виявлення галюцинацій).
Оскільки DeepEval — Python, а promptfoo конфігурують здебільшого через YAML, приклади коду тут не в TS-стеку — і це нормально для оглядового рівня. Головне: інструмент — вторинний. Він економить інфраструктуру, але golden set, вибір скорера, робота з упередженнями судді й пороги на недетермінізм лишаються за вами; поганий набір не врятує жоден фреймворк.
Типові помилки
- Виглядає як «якість виміряна», а насправді golden set нерепрезентативний. Красивий бал на однобокому наборі не каже нічого про прод. Перше питання до будь-якого eval — «на яких даних і чи схожі вони на реальні».
- Виглядає як «суддя об'єктивний», а насправді він упереджений. Позиційне упередження, багатослівність і самовподобання перекошують оцінку; суддя без калібрування проти людини й без перестановки позицій дає число, якому не можна вірити.
- Виглядає як зелений eval, а насправді це один щасливий прогін. Без повторів недетермінізм робить вердикт випадковим; сталість треба вимірювати частотою, а не одним запуском.
- Виглядає як «модель погіршилася», а насправді це шум на малій вибірці. Падіння на 2% на 20 кейсах — не регресія; сигнал — це повторюване падіння з достатньою вибіркою.
- Виглядає як «беремо ту саму модель суддею — зручно», а насправді це конфлікт інтересів. Модель хвалить власний стиль; суддею краще брати іншу, бажано сильнішу.
- Виглядає як «LLM-as-judge замінює перевірки», а насправді факти й код перевіряють детерміновано. Суддя оцінює якість тексту, а не істинність факту й не те, що код запускається.
Підсумок
- Eval — це тест-набір для недетермінованої системи: golden set + скорер + агрегація + поріг, який дає порівнянні числа між версіями.
- Eval рівно настільки хороший, як його golden set: репрезентативність, крайові й регресійні кейси важливіші за обсяг; дані набору не показувати моделі як приклади.
- Скорер бери найдешевший, що ловить потрібне: детермінізм → метрики з еталоном → LLM-as-judge → людина; кожен наступний дорожчий і суб'єктивніший.
- LLM-as-judge упереджений (позиція, довжина, самовподобання): перемішуй позиції, калібруй проти людини, суддя ≠ та сама модель, і не суди тим, що перевіряється детерміновано.
- Evals — це регресійний гейт зі статистичним порогом: недетермінізм лікують повторами й порогами (частка успішних), а не точною рівністю й не одним прогоном.
Що питають на співбесіді
- «Як ви міряєте якість LLM-фічі?» Інтерв'юер перевіряє, чи є в голові система evals, а не «дивлюся очима». Сильна відповідь: golden set + скорер + агрегований поріг, прогін на кожну зміну промпту/моделі.
- «Що таке golden set і що в нього кладуть?» Хочуть почути репрезентативність + крайові + регресійні кейси, еталон або критерії, і застереження про старіння набору й витік даних у промпт.
- «Що таке LLM-as-judge і які в нього проблеми?» Дивляться, чи знаєте ви про упередження (позиційне, багатослівність, самовподобання) і мітигації (перестановка позицій, калібрування проти людини, інша модель-суддя). Відповідь «просто спитати модель, чи добре» — слабка.
- «Як ви робите eval стабільним, якщо LLM недетермінована?» Очікують повтори + частку успішних + пороги замість точної рівності, згадку про зниження температури й що навіть за 0 гарантії немає.
- «Як eval вбудовується в процес розробки?» Червоний прапорець — «пороблю руками перед релізом». Сильний кандидат описує eval як регресійний гейт у CI: baseline, прогін на PR, блок за просідання, версіонування промптів.
Джерела
- ISTQB Certified Tester AI Testing (CT-AI) Syllabus — офіційний силабус ISTQB з тестування AI: недетермінізм, специфіка ML-систем, критерії якості й підходи до оцінювання (evals не покриває класичний CTFL 4.0).
- Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena (Zheng et al., 2023) — стаття, що системно задокументувала упередження LLM-судді: позиційне, багатослівність, самовподобання та слабкість в оцінці міркувань.
- promptfoo — документація — конфігурація тест-кейсів, детерміновані та суддівські асерти, порівняння версій, CI-інтеграція.
- promptfoo на GitHub — вихідний код і приклади конфігів.
- DeepEval на GitHub — фреймворк оцінювання LLM у стилі pytest: готові метрики, G-Eval, метрики для RAG.
Чим eval відрізняється від звичайного автотесту?
Звичайний автотест — це «крок → очікуваний результат → passed чи failed» з булевою рівністю: рядок або збігся з еталоном, або ні. Eval будують для системи, яка на той самий вхід відповідає щоразу трохи інакше, тому замість жорсткого порівняння тут «вхід → критерій якості → бал». Оцінку видає не оператор ===, а скорер (scorer): він може повернути число, а не тільки «так/ні». І вердикт «пройдено» виноситься не по кожному окремому прогону, а по агрегованій метриці всього набору — наприклад, «частка вдалих відповідей не нижче за поріг». Тобто eval — це тест-набір, адаптований під недетермінований вивід: він міряє якість у числах, які можна порівнювати між версіями.
З яких частин складається будь-який eval?
Чотири складові, і кожну треба зафіксувати, інакше ви міряєте шум. Перше — golden set: репрезентативні входи з еталоном або критерієм «як має виглядати добра відповідь». Друге — система під тестом: конкретна зв'язка промпт + модель + параметри (+ RAG, якщо є); поміняли будь-що з цього — це вже інша система, її треба переміряти. Третє — скорер: те, що перетворює вивід на оцінку, від детермінованої перевірки до моделі-судді чи людини. Четверте — агрегація й поріг: як зі скорів окремих кейсів зробити один вердикт по набору й вирішити, пропускати зміну далі чи ні. Головне тут — що eval це не «спитати модель, чи все гаразд», а інженерний артефакт із фіксованими даними, скорером і порогом.
Що таке golden set і що в нього кладуть?
Golden set (еталонний набір) — курований набір прикладів, на яких ганяють систему; це серце будь-якого eval. Кладуть туди три сорти кейсів. Репрезентативні — типові реальні запити в пропорціях, близьких до продакшену: якщо третина звернень до бота про статус замовлення, у наборі їх теж має бути близько третини. Крайові (edge cases) — порожній ввід, надто довгий, іншою мовою, з друкарськими помилками, провокативний, поза доменом. Регресійні — конкретні приклади, на яких система колись зламалася: кожен спійманий у проді дефект стає рядком набору, щоб не повернутися, точно як баг перетворюють на регресійний тест. Для кожного входу фіксують еталон — або єдину точну відповідь, або перелік критеріїв («має містити номер замовлення, тон ввічливий, не обіцяти повернення грошей»).
Чому кажуть, що eval настільки хороший, наскільки хороший його golden set?
Бо скорер міряє відповідь лише на тих входах, які ви йому дали. Ідеальний скорер на нерепрезентативному наборі видасть красиве число, яке нічого не каже про реальний трафік — воно правдиве лише щодо вашої вибірки. Якщо набір однобокий (самі прості питання, тільки одна мова, жодного edge case), зелений eval створює хибне відчуття виміряної якості, а прод падає на тому, чого в наборі не було. Тому перше питання до будь-якого eval — «на яких даних міряли і чи схожі вони на реальні». Скорер, агрегація й пороги — вторинні: погані дані не врятує жодна витончена метрика.
Які пастки з golden set треба тримати в голові на senior-рівні?
Три головні. Перша — набір старіє: продукт міняється, з'являються нові типи запитів, і якщо не ревізувати набір, ви охороняєте вчорашню поведінку. Друга — витік даних: приклади з golden set не можна класти в промпт як few-shot, інакше ви тестуєте систему на тому, що вже їй показали, і бал завищений — це той самий парадокс, що «навчання на тестовій вибірці» в ML. Третя — розмір оманливий: 50–100 різноманітних кейсів дають більше сигналу, ніж тисяча одноманітних, бо цінна саме різноманітність покриття, а не обсяг. Знання цих застережень і відрізняє людину, яка справді веде evals, від тієї, що чула термін.
Які є способи оцінювати вивід LLM і за яким принципом їх обирати?
Скорери шикуються в спектр від дешевих і об'єктивних до дорогих і багатих, а принцип вибору один: бери найдешевший скорер, який ловить те, що тобі потрібно. Найдешевші — детерміновані перевірки: точний збіг, contains, регулярка, валідність JSON проти схеми, «код скомпілювався». Далі — метрики на основі еталона: поверхневі на перетині n-грам (BLEU, ROUGE) або семантична близькість через embeddings і косинусну подібність. Потім LLM-as-judge — масштабований компроміс для якості й тону, де єдиного еталона немає. І найдорожче — людська оцінка за рубрикою, золотий стандарт, але повільний і суб'єктивний. Кожен наступний рівень дорожчий і суб'єктивніший, тому нема сенсу тягнути суддю туди, де впорається регулярка.
Коли детермінований скорер не годиться і що брати натомість?
Детермінований скорер працює лише там, де є однозначна істина або чіткий інваріант: «яка столиця Франції», валідний JSON, наявність номера замовлення в тексті. Щойно валідних формулювань стає багато — «перекажи це ввічливо» має десятки правильних варіантів — точний збіг і contains починають червонити добрі відповіді. Тоді беруть метрики на основі еталона. Поверхневі (BLEU, ROUGE) рахують перетин n-грам із еталоном: дешево, але грубо, бо синоніми й перефразування вони не бачать. Семантична близькість через embeddings перетворює відповідь і еталон на вектори й міряє косинусну подібність — вона ловить «те саме іншими словами», але вимагає порога й не розуміє ні тону, ні фактичної коректності. Коли треба саме якість чи тон, переходять уже до LLM-as-judge.
Що таке LLM-as-judge і навіщо він потрібен?
LLM-as-judge — це коли одна модель оцінює вивід іншої. Судді дають вхід, кандидатну відповідь і рубрику (rubric) — критерії оцінювання, — а він повертає бал чи вердикт із поясненням. Приваблива ідея тим, що масштабується як код, але «розуміє» текст майже як людина: ловить релевантність, тон, суперечності, наявність вигаданих фактів — те, чого регуляркою не спіймаєш. Це заповнює прогалину між дешевим детермінізмом, який не бачить сенсу, і дорогою людиною, яка не масштабується. Але суддя — теж LLM: він коштує токенів, сам недетермінований і має системні упередження, тож його вивід не можна брати на віру без калібрування.
Чим відрізняються pointwise і pairwise режими судді?
Це два способи попросити суддю оцінити. Pointwise (поточковий) — оцінити одну відповідь за абсолютною шкалою, наприклад 1–5 за рубрикою. Pairwise (попарний) — показати дві відповіді, A і B, і сказати, яка краща. Попарний часто надійніший, бо моделі легше порівняти два варіанти, ніж поставити «об'єктивну» цифру на порожньому місці: абсолютна шкала пливе, судді кучкують бали навколо середини. Тому для порівняння двох версій системи pairwise зазвичай дає стабільніший сигнал. Зворотний бік — саме в попарному режимі найсильніше проявляється позиційне упередження, тож порядок доводиться перемішувати.
Що робить LLM-суддю робочим, а не іграшковим?
Кілька практик. По-перше, вимагати спершу міркування, потім вердикт: коли модель проговорює обґрунтування перед оцінкою (chain-of-thought), її бали узгодженіші, ніж коли вона одразу кидає цифру. По-друге, структурований вивід — просити відповідь у форматі, який машина розпарсить без крихких евристик, щоб вердикт можна було агрегувати. По-третє, конкретна рубрика: не «оціни якість», а перелік критеріїв із прикладами хорошого й поганого — інакше кожен прогін судить за іншим внутрішнім стандартом. І температуру судді знижують, щоб зменшити розкид оцінок. Без цього суддя дає гарний на вигляд, але нестабільний і слабо обґрунтований бал.
Які упередження має LLM-суддя?
Суддя-модель — не безсторонній арбітр, і дослідження стабільно фіксують кілька перекосів. Позиційне (position bias) — у попарному режимі суддя схильний хвалити відповідь на певній позиції, частіше першу, незалежно від змісту. Багатослівність (verbosity/length bias) — довша, розлогіша відповідь оцінюється вище, навіть коли коротка правильніша. Самовподобання (self-preference bias) — модель вище цінує текст у стилі власної родини, тобто схильна хвалити саму себе. Форматне (formatting bias) — гарне оформлення, впевнений тон і ключові слова з рубрики піднімають бал без реальної якості; це рідня догідливості (sycophancy). Плюс судді кучкують бали до середини шкали й слабо рахують числа й логіку. Знати ці перекоси обов'язково, бо саме вони роблять красивий бал брехливим.
Як боротися з позиційним упередженням судді?
Прогнати обидва порядки й усереднити: спершу «A перший, B другий», потім «B перший, A другий». Якщо переможець той самий в обох перестановках — вердикт стабільний, йому можна вірити. Якщо від перестановки переможець міняється — це не перемога, а нічия, спричинена позицією, і зараховувати її як реальну різницю не можна. Такий симетричний прогін коштує вдвічі більше токенів, зате знімає найгрубіший перекіс попарного режиму. Це окремий випадок ширшого принципу: суддю треба калібрувати й перевіряти, а не довіряти першому вердикту.
Чому не варто судити вивід моделі тією ж самою моделлю?
Через самовподобання: модель систематично вище оцінює текст, написаний у стилі власної родини моделей, тобто фактично хвалить свій же почерк. Якщо та сама модель і генерує, і судить, це прямий конфлікт інтересів — бал завищений на користь власного виводу, і ви цього в числі не побачите. Тому суддею беруть іншу модель, бажано сильнішу за ту, що генерувала. І окремо: збіг оцінок судді з людськими на калібрувальному самплі — це метрика самого судді; поки він не звірений з людиною, його бал — гіпотеза, а не факт. Хороша практика — тримати невеликий людський сампл саме для перевірки, що суддя не поїхав.
Як зробити eval стабільним, якщо LLM недетермінована?
Оскільки та сама система на той самий вхід дає різні відповіді, один прогін кейсу — це один зразок із розподілу, тобто шум; gate на одному прогоні гарантує флак «сьогодні зелено, завтра червоно». Рішення — повтори й пороги замість точної рівності. Кожен кейс ганяють N разів і рахують частку вдалих (pass rate). Поріг ставлять на двох рівнях: скільки повторів має пройти кейс (наприклад, score >= 4 у 80% прогонів) і скільки кейсів має пройти набір (наприклад, 90%). Температуру на час eval знижують, щоб зменшити розкид, але навіть за температури 0 повна повторюваність не гарантована, тож повтори не скасовуються. Головний спосіб мислення: eval-результат — це розподіл, а не точка; мале падіння на малій вибірці може бути шумом, сигнал — це велике й повторюване падіння.
Як eval вбудовується в процес розробки?
Найцінніша роль eval — регресійний гейт. Один раз зібраний golden set + скорер + поріг перетворюються на набір, який ганяють на кожну зміну промпту, моделі чи RAG-конфігурації — рівно як регресію ганяють на кожну зміну коду. Зафіксували базовий бал (baseline) — і будь-яке падіння нижче порога блокує мердж. Це дає три речі: промпти й моделі версіонуються як код і проходять гейт, а не котяться в прод «бо виглядало краще»; ловляться тихі регресії, коли правка покращила один тип запитів і зламала інший; eval стає частиною CI/CD зі звітом-діффом балів між гілками на PR. Відмінність від звичайного gate — поріг тут не «100% зелено», а статистичний, бо система недетермінована. Слабка відповідь на співбесіді — «пороблю руками перед релізом».
Чому LLM-as-judge не заміняє факт-чек і запуск коду?
Бо суддя оцінює якість тексту, а не його істинність. Він добре бачить, чи відповідь релевантна, ввічлива, несуперечлива, чи не звучить як вигадка, — але він і сам недетермінований, кучкується до середини шкали й слабо перевіряє числа й логіку. Тому там, де є однозначна істина (факт правильний чи ні) або чіткий інваріант (код запускається, тести зелені, JSON валідний), надійніша детермінована перевірка, а не думка судді. Практичний висновок: суддю тримають для якості й тону, факти звіряють факт-чеком, а працездатність коду — його виконанням. Плутати «звучить переконливо» з «є правдою» — типова помилка, і суддя на ній теж спотикається.
Три кейси з роботи AQA, який веде AI-фічу: як обрати скорер під конкретну перевірку бота підтримки, як зібрати попарного суддю, що не піддається позиційному упередженню, і як перетворити eval на статистичний регресійний гейт замість флакі-порівняння рядків. Скрізь — що дивитися й чому саме так.
Кейс 1. Вибір скорера: одна перевірка — один інструмент
Продукт — бот підтримки інтернет-магазину. Змінили системний промпт, треба переміряти якість. Помилка новачка — усе віддати LLM-судді: дорого, повільно й непотрібно там, де є чіткий інваріант. Принцип зворотний: під кожну властивість відповіді береться найдешевший скорер, що її ловить.
| Що перевіряємо | Тип перевірки | Скорер | Чому саме він |
|---|---|---|---|
| У відповіді є номер замовлення | однозначний інваріант | регулярка /ORDER-\d{6}/ | є чіткий формат — детермінована перевірка безкоштовна й стабільна |
| Бот не обіцяє повернення грошей | заборонений патерн | негативна регулярка | політику порушує конкретне формулювання, його видно текстом |
| Відповідь коректний JSON для віджета | структурний інваріант | JSON.parse проти схеми | або парситься, або ні — судді тут нема що робити |
| Зміст збігається з еталонною відповіддю | багато валідних формулювань | embeddings + косинусна подібність | «те саме іншими словами» регулярка не бачить, а вектор ловить |
| Тон ввічливий, без води, релевантно | якість, немає єдиного еталона | LLM-as-judge за рубрикою | тон і доречність детерміновано не виміряти |
| Спірні кейси, калібрування судді | фінальний арбітр | людина за рубрикою | золотий стандарт, але лише на самплі, не на кожному прогоні |
Детерміновані скорери — це прості функції, які видно наскрізь:
type Scorer = (output: string) => boolean;
const hasOrderId: Scorer = (o) => /ORDER-\d{6}/.test(o);
const noRefundPromise: Scorer = (o) => !/поверн(емо|у) кошт/i.test(o);
const isValidJson: Scorer = (o) => {
try { JSON.parse(o); return true; } catch { return false; }
};
Що дивитися й чому:
- Три перші рядки таблиці не варті виклику до LLM. Номер замовлення, заборонена обіцянка й валідність JSON — це інваріанти з однозначною істиною. Детермінований скорер тут дешевший, швидший і повторюваний; суддя лише додав би вартість і недетермінізм на рівному місці.
- Embeddings — не про тон і не про факти. Косинусна подібність скаже, що відповідь семантично близька до еталона, але не побачить ні грубого тону, ні вигаданого факту. Тому «збіг змісту» і «ввічливість» — це два різні рядки з різними скорерами, а не один.
- Людина не масштабується, тож її бережуть. Вона потрібна не на кожному прогоні, а щоб калібрувати суддю й розсудити спірне; поставити людину в цикл кожної правки — це вбити швидкість eval.
Кейс 2. Попарний суддя, що не піддається позиційному упередженню
Порівнюємо дві версії промпту, A і B, на одному наборі. Наївний виклик «ось A, ось B, що краще» дає число, якому не можна вірити: суддя схильний хвалити відповідь на першій позиції незалежно від змісту. Лікування — прогнати обидва порядки й порівняти вердикти. Якщо переможець міняється від перестановки, це не перемога, а нічия, спричинена позицією.
type Winner = 'A' | 'B' | 'tie';
// один прогін судді: повертає, кого він вважає кращим із двох на його позиціях
async function judgeOnce(input: string, first: string, second: string): Promise<'first' | 'second' | 'tie'> {
const prompt = [
'Порівняй дві відповіді на питання за критеріями: релевантність,',
'ввічливий тон, відсутність вигаданих фактів. Спершу міркування,',
'потім рядок JSON виду {"winner":"first"|"second"|"tie"}.',
`Питання: ${input}`,
`Відповідь 1: ${first}`,
`Відповідь 2: ${second}`,
].join('\n');
const raw = await callModel(prompt, { temperature: 0 });
return JSON.parse(extractJson(raw)).winner;
}
// симетричний прогін: обидва порядки, і лише узгоджений вердикт зараховуємо
async function comparePair(input: string, a: string, b: string): Promise<Winner> {
const ab = await judgeOnce(input, a, b); // A перший, B другий
const ba = await judgeOnce(input, b, a); // B перший, A другий
const winnerAB: Winner = ab === 'first' ? 'A' : ab === 'second' ? 'B' : 'tie';
const winnerBA: Winner = ba === 'first' ? 'B' : ba === 'second' ? 'A' : 'tie';
// переможець стабільний лише коли обидва порядки згодні
return winnerAB === winnerBA ? winnerAB : 'tie';
}
Що дивитися й чому:
- Два виклики на одну пару — не марнотратство, а страховка. Одинарний прогін дав би цифру, перекошену позицією. Симетричний коштує вдвічі більше токенів, зате знімає найгрубіше упередження попарного режиму; вартість судді взагалі варто тримати в голові, бо він теж LLM.
- Незгода порядків — це сигнал, а не збій. Коли
winnerABіwinnerBAрозходяться, чесний вихід — нічия: різниця між A і B менша за шум від позиції, і оголошувати перемогу не можна. temperature: 0і структурований вивід — обов'язкові. Нульова температура зменшує розкид вердиктів, а машинно-парсибельний JSON рятує від крихких евристик розбору тексту. Рубрику дано конкретним переліком критеріїв, а не словом «краще».- Суддя тут — інша, бажано сильніша модель, ніж генератор A і B. Судити свій же вивід тією ж моделлю — конфлікт інтересів через самовподобання, і перекіс на власний стиль зіпсує порівняння.
Кейс 3. Від флакі-порівняння до статистичного гейта
Команда зробила eval, який на PR інколи зелений, інколи червоний без змін у промпті. Причина — вердикт будували на одному прогоні кожного кейса, а система недетермінована: один прогін це один зразок із розподілу, тобто шум. Правильний gate міряє частку вдалих із кількох повторів і має пороги на двох рівнях.
// один кейс: скільки з N прогонів набрали потрібний бал у судді
async function caseRate(input: string, runs = 5): Promise<number> {
let passed = 0;
for (let i = 0; i < runs; i++) {
const out = await system(input); // недетермінований вивід
const verdict = await judge(input, out); // pointwise-суддя, score 1..5
if (verdict.score >= 4) passed++;
}
return passed / runs; // 0.8 = 4 з 5 прогонів пройшли
}
// весь набір: кейс зелений при ≥80% вдалих повторів, набір — при ≥90% зелених кейсів
async function suiteGate(cases: string[]): Promise<{ green: boolean; ratio: number }> {
const rates = await Promise.all(cases.map((c) => caseRate(c)));
const passedCases = rates.filter((r) => r >= 0.8).length;
const ratio = passedCases / cases.length;
return { green: ratio >= 0.9, ratio };
}
Що дивитися й чому:
- N повторів перетворюють точку на розподіл. Частка
0.8каже «4 з 5 прогонів пройшли» — це вимір стабільності, а не разовий успіх. Будувати блок мерджа на одному прогоні — гарантований флак: сьогодні зелено, завтра червоно на тому самому коді. - Два пороги, а не один. Нижній (
>= 0.8) вирішує, чи стабільний окремий кейс; верхній (>= 0.9) — чи стабільний набір. Це дозволяє кільком складним кейсам просісти, не завалюючи весь gate, і водночас ловить системну регресію. - Температуру на час eval знижують, але повтори лишаються. Менша температура звужує розкид, проте навіть за 0 повна повторюваність не гарантована, тож відмовлятися від повторів не можна.
- Порівнюють baseline і гілку, а не абсолют. Падіння на 2% на 20 кейсах — імовірно шум; сигнал регресії — повторюване падіння з достатньою вибіркою. Тому в CI показують дифф балів між гілками, а поріг блокує мердж лише за значуще просідання нижче baseline.
Що таке eval
- Можу пояснити, чим eval відрізняється від автотесту: «вхід → критерій → бал» замість «крок → очікуваний результат → passed/failed», оцінку дає скорер (не булева рівність), і дає порівнянні числа між версіями («B на 8% краща»).
- Знаю чотири частини eval: golden set, система під тестом (промпт + модель + параметри + RAG), скорер, агрегація + поріг — зафіксувати треба всі, інакше міряю шум; поміняв щось у системі — переміряй.
Golden set
- Розумію правило «eval настільки хороший, як його golden set»: ідеальний скорер на нерепрезентативних даних дає красиве, але брехливе число.
- Знаю три сорти кейсів (репрезентативні у пропорціях продакшену, крайові, регресійні) і що еталон буває точною відповіддю або переліком критеріїв.
- Розумію дві пастки набору: він старіє й потребує ревізії; дані з нього не можна класти в промпт як few-shot — це витік, аналог навчання на тестовій вибірці.
- Можу пояснити, чому 50–100 різноманітних кейсів сильніші за тисячу одноманітних: цінна різноманітність покриття, а не обсяг.
Способи оцінювання
- Знаю принцип «бери найдешевший скорер, що ловить потрібне», спектр детермінізм → метрики з еталоном → LLM-суддя → людина, і де працює детермінований (
contains, регулярка, JSON-схема, «код зібрався»). - Розумію різницю поверхневих метрик (BLEU, ROUGE — перетин n-грам) і embeddings + косинусної подібності, та обмеження останньої: вимагає порога, не бачить ні тону, ні фактичної коректності.
- Можу пояснити, навіщо тримати людську оцінку: калібрування й фінальний сампл, а не кожен прогін.
LLM-as-judge та його упередження
- Можу пояснити, що таке LLM-as-judge (модель оцінює вивід іншої за рубрикою), і різницю pointwise vs pairwise та чому попарний часто надійніший.
- Розумію три практики робочого судді: спершу міркування → потім вердикт, структурований вивід, конкретна рубрика з прикладами.
- Знаю системні упередження судді: позиційне, багатослівність, самовподобання, форматне, кучкування балів до середини.
- Можу пояснити мітигацію позиційного упередження: прогнати обидва порядки й усереднити; зміна переможця від перестановки — нічия, не перемога.
- Розумію, чому суддя ≠ та сама модель (самовподобання, конфлікт інтересів; беруть іншу, сильнішу) і що збіг судді з людиною на самплі — метрика самого судді.
- Можу пояснити, чому суддя не заміняє факт-чек і запуск коду: він оцінює якість тексту, а не істинність факту.
Eval як регресія та недетермінізм
- Розумію роль eval як регресійного гейта: golden set + скорер + поріг ганяються на кожну зміну промпту/моделі/RAG, а промпти й моделі версіонуються як код.
- Знаю, що один прогін кейсу — це зразок із розподілу (шум); рішення — повтори + пороги: pass rate з N прогонів, поріг на рівні кейса (
score >= 4у ≥80%) і набору (≥90%). - Розумію, що зниження температури зменшує розкид, але навіть за 0 повторюваність не гарантована, тож повтори не скасовуються; шум від регресії відрізняю повторюваністю падіння.
Інструменти (станом на липень 2026)
- Знаю, що promptfoo — декларативний (YAML) інструмент для матриці «промпти × моделі × кейси» з детермінованими асертами й model-graded оцінкою, зручний у CI.
- Знаю, що DeepEval — Python-фреймворк «pytest для LLM» з готовими метриками (G-Eval, RAG), і що інструмент вторинний: golden set, вибір скорера й пороги лишаються за мною.
Чим eval принципово відрізняється від звичайного автотесту?
Питання
Що таке eval і чим він відрізняється від автотесту?