Verifiable Eval

Свободный текст оценивают судьёй не потому, что иначе нельзя, а потому, что не пробовали иначе. Третий путь: модель работает извлекателем, а сравнивает код. Проза превращается в структурированные утверждения по схеме, утверждения сверяются с эталоном программно, а у извлекателя появляется проверка, которой у судьи быть не может, — обязанность процитировать фрагмент, на котором утверждение стоит.

Почему это не «судья, только аккуратнее»

Граница, записанная в LLM as Judge, делит мир надвое: что выразимо кодом — проверяется кодом, остальное уходит судье. Формулировка верная и неполная, потому что молчаливо считает выразимость свойством задачи.

Она свойство формы ответа. «Назови три жилых комплекса с наибольшей доходностью и укажи её» — вопрос с единственным верным ответом, и проверяется он тривиально, как только из абзаца прозы извлечены три названия и три числа. Непроверяемой задачу делает не её содержание, а то, что ответ пришёл текстом.

Отсюда разделение труда, в котором вероятностной части достаётся механическая работа:

Что делает Кто Чем проверяется
превратить прозу в структуру по схеме модель привязка к фрагменту, сравнение двух независимых извлечений
сравнить структуру с эталоном код ничем: сравнение детерминированное

Модель при этом не выносит суждения о правильности — она только переносит написанное в поля. Ошибиться она может, и ошибку ловят ниже; но ошибка извлечения — это расхождение с текстом, а не расхождение во мнении, и потому проверяемо.

Привязка к дословному фрагменту

Извлекатель обязан вернуть вместе со значением опорный фрагмент — кусок исходного ответа, на котором значение стоит. Дальше чистая работа со строками, без единого вызова модели: фрагмент ищется в ответе после приведения регистра и пробелов, затем без учёта пунктуации, затем нечётко. Не нашёлся — утверждение помечается выдуманным.

Это и есть механизм, ради которого стоит менять судью на извлекателя. Судью проверить нечем: его вердикт — мнение, у мнения нет места в тексте, куда можно ткнуть. У извлечения место есть всегда, и проверка стоит ноль.

Пороги нечёткого совпадения — вопрос калибровки, а не значения: у авторов это 0,6 по отношению всей строки и 0,8 по лучшему окну, и подобраны они под свой корпус. Универсального числа здесь нет, метод же простой — прогнать на размеченных случаях и смотреть, где начинаются ложные пометки.

Коллегия собирается по разногласию, а не по умолчанию

Дешёвый путь — две модели разных вендоров извлекают одно и то же независимо, плюс проверка на дословность. Сошлись значениями и обе привязаны — подтверждено, конец. Дорогой путь открывается только при расхождении: доизвлекают ещё три модели, и анализатор решает по пяти извлечениям и сырому ответу.

Ценно здесь то, что триггер объективный. «Коллегия при сомнении» обычно упирается в вопрос, откуда берётся сомнение: самооценка модели для этого не годится по той же причине, по которой не годится самопроверка вместо валидатора. Здесь сомнение — это факт: два извлечения разошлись, или фрагмент не нашёлся в тексте.

Требование к составу коллегии то же, что к подтверждениям вообще (Source Independence): модели берутся от разных поставщиков. Пять извлечений одной моделью — это одно извлечение с четырьмя копиями.

Неуверенность разложена на причины, а не свалена в ведро

Исход у каждого утверждения один из двух — подтверждено или требует разбора, — но во втором случае обязательно назван признак, по которому оно туда попало:

Причина Что произошло Куда смотреть
disagreement оба извлечения привязаны, значения разные в текст: скорее всего он допускает два чтения
possibly_hallucinated одно извлечение нашло значение, второе — что факта нет в извлекатель, нашедший лишнее
possibly_missed зеркальный случай в извлекатель, не нашедший имеющееся
primary_ungrounded / secondary_ungrounded фрагмент не найден в ответе в извлекатель: он сочинил опору
both_ungrounded не найдены оба в схему извлечения: скорее всего она не о том
analyzer_uncertain арбитр не уверен сам к человеку

Смысл дробления тот же, что у перечня допустимых исправлений в сообщении об ошибке (Validation Loops): за названной причиной стоит своё следующее действие, а за общим «не уверен» — никакого.

Пустой ответ арбитра не считается согласием

Отказ, ради которого стоит читать код, а не описание. Арбитр иногда возвращает пустой или неразбираемый JSON — типично, когда мнётся на схеме-списке. Наивная обработка прочитала бы это как «итогового значения нет», то есть как подтверждённое отсутствие факта: два извлекателя молчат, арбитр молчит, значит все согласны.

Это ровно тот же дефект, что вызов инструмента, не вернувший ничего (Tool Calling): пустота заполняется утверждением, которого никто не делал. Защита в коде поставлена явно — если большинство из пяти попыток вернули непустое значение, пустота арбитра не принимается, и итог берётся из попыток.

Правило берётся руками: молчание участника — это отсутствие голоса, а не голос «против». Различать их обязан код, потому что на выходе они выглядят одинаково.

Вторая деталь оттуда же стоит внимания отдельно. Выбирать среди попыток «самую длинную» оказалось мало: несколько извлечений давали список той же длины, но с пустыми полями внутри — обрыв вывода по лимиту токенов. Ранжирование переделали на пару «длина плюс число заполненных полей». Тот самый молчаливый обрыв, ради которого заведены правила output-truncation-unchecked, только проявившийся не в проде, а в оценочном стенде.

Две дисциплины самого набора

Эталон живёт не в банке вопросов, а в коде проверяющих. Вопросы лежат отдельным файлом и их можно отдать наружу целиком; правильные ответы — в скриптах, по одному на вопрос. Разделение не про секретность как таковую, а про то, что оцениваемая система видит ровно вопросы и ничего сверх них.

Текст вопроса сверяется дословно, и расхождение валит прогон. Файл с ответами модели несёт не только идентификатор, но и сам вопрос; не совпал с банком — прогон прерывается, а не продолжается на том, что нашлось. Без этого устаревший шаблон молча оценивал бы ответы на другие вопросы, и числа получились бы правдоподобные. Тот же приём, что --check у генераторов: расхождение обязано падать, а не выправляться на ходу.

Оговорка: детерминированное сопоставление бывает хрупким

Общее правило «где выразимо кодом — проверяй кодом» здесь получает границу, и авторы формулируют её как признание: точное сопоставление по ключевым словам внутри проверяющих оказалось ломким, и его заменили голосованием нескольких моделей с программной перекрёстной сверкой полей.

Причина в том, что сущность, названная в свободном тексте, совпадает с эталоном хуже, чем кажется: синонимы, транслитерация, неполное имя, другой порядок слов. Сравнение чисел и множеств остаётся детерминированным; сопоставление названий — нет, и попытка сделать его точным даёт не строгость, а ложные несовпадения.

Практический вывод: правило остаётся в силе, но выразимость проверяется на своих данных, а не предполагается. И у голосования здесь есть страховка, которой не было у точного совпадения, — программная сверка полей рядом с голосом.

Полный след решения хранится вместе с оценкой

У каждого сопоставленного утверждения остаются голоса моделей с обоснованиями, результат программной сверки полей, совпадение по ключевым словам, признак вызова арбитра, его вердикт и исходное извлечение. Оценка не число, а реконструируемое решение — то же требование, что к цепочке доказательств вообще (Evidence Spine).

Практическая ценность появляется при расхождении с человеческой разметкой: без следа спор упирается в «модель посчитала иначе», со следом видно, на каком шаге — извлечении, привязке или сопоставлении — разошлись.

Связано с

  • LLM as Judge — судья и его границы; здесь конструкция, в которой судья не нужен
  • Agent Evals — эталонный набор целиком; здесь про устройство проверяющей части
  • Source Independence — почему коллегия собирается из моделей разных поставщиков
  • Evidence Spine — след, по которому оценку можно пересобрать
  • Validation Loops — названная причина вместо общего «не уверен», там же про перечень исправлений
  • Tool Calling — пустой ответ, прочитанный как содержательный
  • Paraphrase Drift — сила утверждения не выше силы совершённого действия