vyvchy
    Теми розділу

    14 · AI для QA

    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%») і на його основі пропустити зміну далі чи ні.

    Так

    Ні

    Golden set
    вхід + критерій

    Система під тестом
    промпт + модель

    Скорер
    детермінований / суддя / людина

    Агрегація
    pass rate, середній бал

    Поріг пройдено?

    Зелено

    Червоно: регресія

    Так

    Ні

    Golden set
    вхід + критерій

    Система під тестом
    промпт + модель

    Скорер
    детермінований / суддя / людина

    Агрегація
    pass rate, середній бал

    Поріг пройдено?

    Зелено

    Червоно: регресія

    Головна думка: 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) і погано рахують/перевіряють числа й логіку — там детермінована перевірка надійніша.

    Так

    Ні

    Кандидати A і B

    Суддя: A перший, B другий

    Суддя: B перший, A другий

    Однаковий переможець?

    Вердикт стабільний

    Позиційне упередження:
    усереднити або нічия

    Так

    Ні

    Кандидати A і B

    Суддя: A перший, B другий

    Суддя: B перший, A другий

    Однаковий переможець?

    Вердикт стабільний

    Позиційне упередження:
    усереднити або нічия

    Мітигації зводяться до кількох принципів: перемішувати позиції та усереднювати; тримати рубрику конкретною й прикладною; брати суддею іншу (бажано сильнішу) модель, ніж та, що генерувала; калібрувати суддю проти людських оцінок на самплі (збіг судді з людиною — це метрика самого судді); і не тягнути суддю туди, де впорається детермінована перевірка. Суддя оцінює якість — він не заміняє факт-чек і виконання коду (див. верифікацію результатів 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.