Вісім місяців у черзі злиття (merge queue) GitHub Actions навчають тому, чого ніколи не дадуть матриці порівняння функцій. Фреймворк може пропонувати п'ятдесят метрик, розкішні дашборди та цитати з авторитетних дослідницьких лабораторій. Якщо він блокує ваше розгортання (deploy) через те, що показник «vibe check» змістився з 0.72 до 0.68 на ідентичному коді, він гірший за марний. Він стає реальною загрозою для швидкості вашого релізу (shipping velocity).

Це той фільтр, який пропускає більшість оглядів оцінювання LLM. Вони рахують можливості. Але вони рідко ставлять єдине питання, яке має значення в черзі злиття: чи проходить і не проходить ця перевірка абсолютно однаковим чином кожного разу під час запуску?

Я дізнався про це, виконуючи важку та незручну роботу. Я інтегрував шість open-source фреймворків для оцінювання LLM у реальний CI-конвеєр (pipeline). Вони працювали з реальними production pull requests протягом восьми місяців. Два заслужили право залишатися «gatekeepers» (контролерами). Решта були понижені до ролі консультативних дашбордів, перенесені в нічні завдання (nightly jobs) або повністю видалені. Урок був жорстким і дорогим: детермінована структура перемагає ймовірнісну якість, коли ви охороняєте основну гілку (main branch).

Справжня роль шлюзу злиття (Merge Gate)

CI-шлюз — це не дослідницьке середовище. Це охоронець (bouncer). Його єдина мета — поглянути на конкретну зміну і відповісти «так» або «ні». Так, цей PR може приєднатися до основної гілки. Ні, не може. Ця відповідь має з'являтися за лічені секунди, коштувати копійки та ніколи не змінюватися ретроактивно. Якщо ви перезапустите той самий конвеєр для того самого коміту спокійного вівторка та шаленої п'ятниці, результат має бути ідентичним.

Саме тут спотикається більшість фреймворків для оцінювання LLM. Їх створюють дата-саєнтисти для дата-саєнтистів. Вони оптимізовані для отримання інсайтів, досліджень та нюансованого скорингу. Черга злиття оптимізована для бінарних рішень, швидкості та відсутності нестабільності (flakiness). Ці дві мети перетинаються лише частково.

Чому підхід LLM-as-Judge ламає чергу

Інструменти, які провалилися в моєму тесті, мали один і той самий конструктивний гріх: вони занадто сильно покладалися на виклики LLM-as-judge як на основний механізм шлюзу.

Промпт LLM-as-judge просить модель оцінити результат за шкалою від одного до десяти, обрати кращу з двох відповідей або оцінити фактичну правильність. Цей підхід потужний для розуміння тенденцій якості. Але він є отрутою для блокуючої CI-перевірки. Один і той самий вхідний сигнал може давати різні оцінки в різні дні, оскільки температура (temperature), версіонування моделей та форматування промптів створюють шум. Коли ця оцінка прив'язана до жорсткого порогу та жорсткого коду виходу (exit code), ваша черга блокується через «привидів».

Помилки швидко накопичуються ланцюгово. Недетермінована перевірка створює затримки в черзі. Інженери звикають перезапускати перевірку, поки число не стане сприятливим, що привчає команду ігнорувати «червоні» білди. Витрати на токени зростають, оскільки кожен перезапуск споживає більше API-кредитів. Найгірше те, що сигнал втрачає сенс. «Червоний» білд має означати: «ви впровадили баг». Якщо він означає: «модель-суддя сьогодні прокинулася прискіпливою», довіра руйнується.

Чим відрізняються ті, хто вижив

Promptfoo та DeepEval вижили, тому що вони ставляться до детермінованих перевірок як до першочергових об'єктів (first-class citizens), а до оцінок LLM-судді — як до другорядних, неблокуючих сигналів. Вони розуміють, що шлюзу потрібен код виходу, а не число з плаваючою комою, яке має власну «думку».

Promptfoo, що випущений під ліцензією MIT, створений для командного рядка. Він виконує перевірки (assertions), такі як відповідність regex, валідація JSON schema, перевірка на наявність (contains) та точне порівняння рядків. Це не щось вигадливе. Це просто вдосконалені команди grep та jq. Саме тому вони працюють у CI. Regex або збігається, або ні. JSON schema або проходить валідацію, або видає помилку. Promptfoo повертає стандартні Unix exit codes, тому GitHub Actions нативно розуміє, коли зупинити злиття. Він є мовно-незалежним (language-agnostic), оскільки працює як CLI-інструмент. Вам не потрібно встановлювати екосистему Python у репозиторій Node.js-сервісу лише для того, щоб перевірити вихідні дані.

DeepEval, ліцензований під Apache 2.0, є вибором для Python-команд. Він інтегрується подібно до pytest. Ви пишете тести у звичному синтаксисі, і помилка природним чином блокує весь набір тестів. DeepEval пропонує величезний каталог метрик, але критично важливо використовувати їх обережно. Покладайтеся на детерміновані або евристичні метрики для шлюзів. Якщо ви використовуєте G-Eval або інші скоринги на основі суддів, обгортайте їх у неблокуючі генератори звітів, а не у жорсткі assert-и. При такому використанні DeepEval забезпечує ергономіку фреймворку для тестування без нестабільності дослідницьких блокнотів.

Де місце іншим чотирьом

Чотири фреймворки, які не вижили як шлюзи, все ще мають цінність. Вони просто належать до інших частин вашого інструментарію (toolchain).

Future AGI (Apache 2.0) пропонує понад п'ятдесят метрик і орієнтований на команди, що розробляють власні SDK. Метрики є вичерпними. Проблема в тому, що інструмент очікує, що ви напишете власну оболонку (harness) для запуску в черзі CI. У дослідницькому контексті це розумна плата. У черзі злиття (merge queue) кожен шар кастомного зв'язування — це новий джерело нестабільності. Це потужний рушій оцінювання, але не готовий інструмент контролю (gatekeeper).

RAGAS (Apache 2.0) чудово справляється з вимірюванням якості генерації з використанням пошуку (RAG). Його метрики достовірності (faithfulness) та релевантності відповіді справді корисні для розуміння того, як база знань працює з часом. На жаль, ці метрики сильно залежать від LLM-суддів. Вони чудово підходять для нічного завдання з перевірки якості, яке надсилає тренди в Slack. Але вони — погані охоронці для pull request. Перенесіть RAGAS у свій запланований конвеєр аналізу, а не в блокувальники злиття.

Arize Phoenix використовує Elastic License 2.0 і знаходиться зовсім в іншій точці перетину. Він поєднує розподілене трасування з оцінюванням, даючи вам можливість спостерігати (observability), чому модель поводилася саме так. Це потрібно, коли ви відлагоджуєте інцидент у продакшені або відстежуєте галюцинацію до невдалого фрагмента пошуку. Але вам не потрібен інструмент трасування, який вирішуватиме, чи може гілка функціоналу молодшого розробника бути злита. Його архітектура побудована для отримання інсайтів, а не для бінарного контролю.

MLflow Evaluate (Apache 2.0) успадкував свою природу від відстеження експериментів. Він важкий. Додавання його в легкий CI-образ збільшує час запуску та кількість залежностей, що сповільнює кожне завдання. Якщо вам абсолютно необхідно використовувати його всередині конвеєра, зупиніться на його евристичних метриках для структурних перевірок. Навіть тоді ви боретеся з фундаментальним дизайном фреймворку. MLflow хоче логувати запуски та порівнювати експерименти протягом тижнів. Черга злиття хоче вердикту менш ніж за хвилину.

Практичні правила для контролю (Gating)

Якщо ви не винесете нічого іншого з цього експерименту, запам'ятайте ці три правила.

По-перше, контролюйте структуру, а не «вайб». Ви можете забезпечити, щоб вихідні дані були валідним JSON. Ви можете перевірити, чи містять вони обов'язкові ключі. Ви можете переконатися, що мітка класифікації належить до дозволеного переліку (enum). Ці перевірки швидкі, дешеві та детерміновані. Ви не можете надійно перевірити, чи є підсумок «дружнім» або чи є перефразування «креативним». Ці якості належать до сфери людської перевірки або періодичного пакетного оцінювання, а не автоматизованих шлюзів.

По-друге, якщо показник змінюється на незмінних вхідних даних, негайно знижуйте його статус. Запустіть свій набір тестів двічі на одному й тому самому артефакті. Якщо будь-яка метрика змінюється з pass на fail, вона втрачає право блокувати злиття. Переведіть її на консультативну панель (advisory dashboard), де варіативність є очікуваною та допустимою.

По-третє, поважайте код виходу (exit code). Гарний HTML-звіт із червоним банером не зупинить злиття. Ненульовий код виходу — зупинить. Ваш інструмент оцінювання має говорити рідною мовою вашої CI-платформи. Standard out — для людей. Коди виходу — для машин.

Підсумок

Ми все ще перебуваємо на ранніх етапах розуміння того, як тестувати додатки на базі LLM. Спокуса полягає в тому, щоб ставитися до оцінювання як до людської системи оцінок: нюансовано, контекстуально та дещо суб'єктивно. Це працює в науковій статті. Це руйнується в черзі злиття.

Після восьми місяців продакшн-трафіку мій конвеєр тепер використовує Promptfoo для перевірки структури та відповідності схемі в різних сервісах, а DeepEval — для поведінкових перевірок на стороні Python, які чітко відповідають умовам pass-fail. Все інше звітується на нічних панелях моніторингу. Черга стабільна. Сигнал чистий. Команда знову довіряє «червоній» збірці.

Вам не потрібно більше метрик на вашому етапі контролю. Вам потрібно менше метрик, які щоразу говорять правду.

На основі оригінального тестування та статті, опублікованої на Dev.to. Для подальшого обговорення побудови надійних AI-систем приєднуйтесь до спільноти GyaanSetu в Telegram.