Оцінка беззалежного LLM із суддею, якому можна дійсно довіряти
Створіть невелику систему оцінки LLM на основі реальних журналів подій, перевірок коду та судді з одним критерієм, а потім налаштуйте цього суддю відносно людських міток, щоб його оцінки мали сенс.
Юніт-тести працюють тому, що детермінований код завжди дає однакову відповідь: якщо перевірити, чи функція повертає чотири, вона завжди це робить. Вихід моделі мови змінює формулювання при кожному виклику, тому немає очікуваного рядка для порівняння, і команди часто вирішують цю проблему, використовуючи платформу оцінювання, яка застосовує іншу модель для оцінки результатів. Саме в моделі оцінювання більшість процедур оцінки фактично припиняють вимірювати щось, оскільки ніхто не підтверджував, що вона узгоджується з оцінкою людини. Цей посібник дозволяє створити повну систему оцінювання за допомогою звичайного Python протягом кількох годин, присвячуючи найважливішу частину налаштуванню „судді“, щоб отриманий результат можна було обґрунтувати.
Три складові будь-якого оцінювання
Якщо видалити інструментальне забезпечення, оцінювання складається з трьох компонентів, жоден з яких не потребує бібліотек:
- Набір даних вхідних значень, бажано отриманих з реального використання, а не вигаданих.
Панелі керування, відстеження та хостовані набори даних — це зручності, створені на основі цих елементів. Деякі з них справді корисні, але сама основа складається приблизно з вісімдесяти рядків коду на Python, і такий короткий код буде продовжувати працювати ще довго після того, як будь-яка конкретна платформа зміниться чи зникне.
Крок 1: Зібрати сто реальних вхідних даних
Візьміть дані з ваших продакшн-логів. Уникайте синтетичних прикладів, випадків, успішність яких ви вже знаєте, а також очищених зразків. Сто випадків — це достатньо, щоб виявити справжні проблеми, і достатньо мало, щоб пізніше можна було їх вручну класифікувати, що виявляється надзвичайно важливим. Навмисно додавайте складні та незручні дані: набір з простих випадків дає оцінку, яка залишається стабільною навіть тоді, коли якість вашої системи погіршується.
Завантаження цих даних є простим:
import json
with open("requests.jsonl") as f:
cases = [json.loads(line) for line in f][:100]
Зверніть увагу, що вибір перших ста рядків означає включення всього, що знаходиться на початку файлу, і це можуть бути дані за один день або від одного клієнта. Шифрування даних перед їх вибором або відбір зразків з різних періодів часу дає більш репрезентативний набір.
Якщо у вас ще немає логів, самі створіть ці сто випадків, але очікуйте, що результати будуть виглядати краще, ніж насправді, поки їх не замінить справжній трафік.
Крок 2: Розділити перевірку коду від ухвалення рішень
Це крок, який команди найчастіше пропускають, і він впливає на все, що відбувається далі. Багато характеристик можна перевірити детерміновано, і їх ніколи не слід надсилати до моделі:
- Результат є коректним JSON.
- Категорія належить до відомого набору.
- Число знаходиться в допустимому діапазоні.
- Є обов’язкові поля.
- Довжина відповіді не перевищує встановленого ліміту.
Кожен з цих пунктів є твердженням. Наступна функція (Python, незалежно від її позначення) виконує кілька з них та повертає словник із іменованими результатами:
def code_checks(output):
checks = {}
try:
parsed = json.loads(output)
checks["valid_json"] = True
checks["has_fields"] = all(k in parsed for k in ("answer", "confidence"))
except json.JSONDecodeError:
checks["valid_json"] = False
checks["has_fields"] = False
checks["under_limit"] = len(output) < 2000
return checks
Повернення названих перевірок замість одного булевого значення дозволяє побачити, яка властивість не пройшла перевірку у разі помилки. Те, що залишається після детерміністичних перевірок, — це та частина, яка потребує оцінки: чи відповіла відповідь на справжнє запитання, чи містила вона факти, відсутні у вихідних даних, чи відмовилась вона там, де мала погодитися? Ці властивості потребують оцінювача, а оцінювач — підтвердження.
Крок 3: Написати оцінювача, який оцінює одну річ
Кожному виклику оцінювача слід передати саме один критерій. Рубрика, яка вимагає оцінки п’яти якостей одночасно, дає змішану оцінку, і у разі невдачі неможливо визначити, яка саме якість була пропущена. Наведений нижче оцінювач перевіряє одну властивість, непідтверджені твердження та вимагає фіксованого формату відповіді:
JUDGE = """You are grading one property of a response.
PROPERTY: Does the response contain any claim that is not supported by the
source text provided?
SOURCE:
{source}
RESPONSE:
{response}
Answer with exactly one word, PASS or FAIL, then a new line, then one
sentence explaining your verdict. Do not explain anything else."""
def judge(source, response, call_model):
out = call_model(JUDGE.format(source=source, response=response))
verdict = out.strip().split("\n")[0].strip().upper()
return verdict == "PASS", out
Обмеження щодо формату мають таке ж значення, як і сам критерій. Вимога до використання одного слова – PASS або FAIL – у першому рядку дозволяє легко парсувати результат. Вердикти у вільному форматі мають різну структуру в залежності від кожного виклику, і якщо помилка під час парсингу буде сприйнята як успіх, усі подальші оцінки будуть завищені, без того щоб хтось це помітив. Ця реалізація обирає безпечний підхід: все, крім точного значення PASS, включаючи PASS. чи вердикт, оформлений у певному форматі, вважається невдачею. Варто фіксувати, як часто це трапляється, адже різкий зрост кількості непарсованих відповідей сам по собі є сигналом.
Крок 4: Запустіть усе та збережіть первинний вихід
Зберігайте повний вихід та пояснення оцінювача для кожного випадку, а не лише вердикт. Коли оцінка змінюється, вам знадобиться дізнатися, що саме змінилося, а збережене булеве значення цього не покаже.
results = []
for case in cases:
output = call_model(case["prompt"])
passed, reasoning = judge(case["source"], output, call_model)
results.append({
"id": case["id"],
"output": output,
"code_checks": code_checks(output),
"judge_pass": passed,
"judge_reasoning": reasoning,
})
rate = sum(r["judge_pass"] for r in results) / len(results)
print(f"{rate:.1%} pass on {len(results)} cases")
Цикл фіксує детерміністичні перевірки разом із результатом оцінювача, хоча основний показник враховує лише оцінювача. На практиці слід вказувати обидва показники, адже відповідь, яка не пройшла перевірку JSON, є невдалою, незалежно від думки оцінювача. Також зазначимо, що тут одна й та сама функція call_model генерує дані та оцінює їх; поширеним вибором є використання іншої або потужнішої моделі для оцінювача, але яку б ви не обрали, наступний крок є тим, що робить її надійною.
На цьому етапі у вас є відсоток. Він поки що нічого не означає.
Крок 5: Калібрування оцінювача за допомогою власних міток
Це крок, який більшість посібників пропускають, і він займає близько тридцяти хвилин. Виберіть п’ятдесят із ста випадків та самостійно позначте кожен як PASS або FAIL, не дивлячись спочатку на вердикт оцінювача. Потім порівняйте ці два набори міток:
agree = sum(1 for r, human in zip(results[:50], human_labels)
if r["judge_pass"] == human)
print(f"judge agrees with me on {agree}/50 = {agree/50:.0%}")
both_fail = sum(1 for r, h in zip(results[:50], human_labels)
if not r["judge_pass"] and not h)
judge_fails = sum(1 for r in results[:50] if not r["judge_pass"])
human_fails = sum(1 for h in human_labels if not h)
print(f"judge caught {both_fail}/{human_fails} of the failures I found")
print(f"judge flagged {judge_fails - both_fail} things I considered fine")
Скрипт відображає три показники: загальну згоду, кількість ваших помилок, які також були виявлені суддею, та кількість випадків, у яких суддя припустився помилки, але які ви вважаєте прийнятними. Код передбачає, що human_labels — це список булевих значень у тому самому порядку, що й перші п’ятдесят результатів, і він спричинить помилку ділення на нуль, якщо ви зовсім не знайшли жодних помилок, що саме по собі є ознакою того, що ваш набір даних занадто простий.
Чому чиста згода вводить в оману
Загальна згода є найменш надійним показником, хоча саме її найчастіше цитують. Якщо дев’яносто відсотків ваших випадків проходять перевірку, суддя, який на все відповідає «ПРОХОДИТЬ», досягне дев’яноста відсотків згоди, не виявивши при цьому нічого.
Найважливіший показник: виявлені помилки
Те, що заслуговує вашої уваги, — це частка ваших власних міток FAIL, які також позначив суддя. З точки зору класифікації, це показник точності судді щодо класу помилок. Суддя, який виявляє дві з одинадцяти ваших помилок, насправді нічого не оцінює; він схвалює все, додаючи переконливе пояснення. Третій показник — хибні спрацювання — також має значення, адже суддя, який позначає правильні відповіді, змусить вас „виправляти“ те, що ніколи не було пошкоджене.
Коли ви та суддя не погоджуєтесь
Прочитайте кожен випадок, де ваша мітка відрізняється від мітки судді. Існує лише три пояснення, і кожне з них має своє рішення.
Критерії оцінювання є неоднозначними
Суддя помиляється, тому що опис властивостей залишає простір для інтерпретації. Звузьте його. Гарним тестом є можливість сформулювати цю властивість у одному реченні, яке новий колега міг би застосувати точно так само, як ви.
Ваші власні позначки є неправильними
Це трапляється частіше, ніж очікують люди. Людське класифікування змінюється протягом п’ятдесяти випадків, особливо щодо прикладів на межі класифікації наприкінці сеансу. Перед тим, як звинувачувати суддю, переоцініть спірні випадки з нової точки зору.
Ця властивість є суттєво суб’єктивною
Деякі якості не можна послідовно оцінювати будь-хто. Якщо ситуація така, жоден суддя не буде надійним. Або розділіть цю властивість на менші, піддаємі перевірці підвластивості, або прийміть те, що її неможливо оцінити.
Ітеруйте: скоригуйте критерії оцінювання, перейменуйте елементи за потреби та запустіть процес знову, поки суддя не виявить достатню кількість ваших помилок, щоб ви змогли їх обґрунтувати під час зустрічі. Потім зафіксуйте критерії та збережіть їхню версію разом із кожним наступним результатом. Результати, оцінені за різними критеріями, не можна порівнювати. Щоб дізнатися, як підтримувати „здоров’я“ судді під час постійної роботи в продакшені, дивіться управління LLM як суддею як живою системою продакшену.
Що дає вам калібрована оцінка
Результатом є число, яке можна розмістити поруч із зміною та в яке можна повірити. Перед тим, як змінити запит, модель чи етап пошуку, ви проводите вимірювання; після цього — ще одне. Різниця між ними містить інформацію лише тому, що спочатку ви підтвердили, що інструмент узгоджується з думкою людини.
Оскільки код залежить лише від функції, яка викликає вашу модель, він продовжує працювати під час зміни постачальників або коли екосистема інструментів змінюється, що є перевагою порівняно з більшістю рекомендованих для цієї роботи методів.
Обмеження оцінки за сто випадків
Перед тим, як використовувати цей підхід, варто зрозуміти три обмеження.
Дрібні зміни залишаються непомітними
Зниження з 91 до 89 відсотків означає два випадки зі ста, що при такому розмірі вибірки неможливо відрізнити від шуму. Щоб виявити незначні ефекти, потрібно або значно більше даних, або парне порівняння: запустити обидві версії на одних і тих самих вхідних даних та підрахувати лише ті випадки, у яких результат змінився. Парні порівняння усувають більшу частину варіації, яка зумовлена складністю випадків.
Ручна маркування не є масштабованим
Ручне позначення стає непрактичним при кількості більше кількох сотень рядків, а якість погіршується з часом роботи. Ваша оцінка справи номер сорок вісім не збігається з оцінкою справи номер два, що створює справжні похибки у самій калібруванні. Позначення в кількох коротких сеансах замість одного довгого зменшує цю девіацію.
Калібрування закінчується терміном дії
Суддя, який був перевірений на сьогоднішніх результатах, може вже не підходити для завтрашніх. Коли ви змінюєте модель, яка використовується для обробки даних, змінюються також способи її несправностей, тому суддю потрібно перевіряти знову, а не використовувати повторно. Це клопітка робота, і саме вона відрізняє вимірювання від ритуалу.
Два доповнення після запуску
- Зберігайте версії всього. Зберігайте версію моделі та версію критеріїв оцінювання разом із кожним результатом. Оцінка без одного з цих елементів не може бути порівняна з іншою оцінкою; з тієї ж причини показник базового тесту без відповідних параметрів майже нічого не говорить.
- Зберігайте набір випадків розбіжностей. Ситуації, коли у вас та рецензенту були розбіжності, є найціннішими даними в усьому завданні. Перевиконування цих випадків після кожної зміни критеріїв — це найшвидший спосіб з’ясувати, чи допомогла ця зміна.
Ці фрагменти орієнтовані на чіткість, а не на максимальну надійність у реальних умовах. Використовуйте їх як шаблон для читання та адаптації, додаючи повторні спроби, обробку помилок та механізми зберігання даних залежно від потреб вашої системи.
Основні висновки
- Процес оцінювання — це набір даних, генератор та система оцінювання; все інше є додатковими інструментами.