Одна и та же модель выдала 28% и 76% на одном бенчмарке. Разница — в способе подачи вопросов

от автора

Я собирал бенчмарк для русскоязычных LLM на корпоративных документах: политики, приказы, счета, согласования. Хотел проверить, умеет ли модель найти нужный документ среди похожих, сослаться на конкретную строку и не сломаться, когда в одном из документов меняешь одну дату.

В этом эксперименте результат оказался сильно зависим от того, как были организованы запросы к модели.

Одна и та же модель, одни и те же данные, разница 47.4 пункта

Одна и та же модель, одни и те же данные, разница 47.4 пункта

Этот эксперимент показывает, что итоговая точность LLM зависит от всего протокола взаимодействия: структуры промпта, доступного контекста, схемы ответа и правил обработки ошибок. Одна и та же модель на одних и тех же данных дала 28.4% и 75.9% при разной подаче вопросов. Для исследований это означает, что результаты бенчмарков и лидербордов нужно сравнивать только при точно описанном протоколе, а в прикладных системах качество можно заметно повысить за счёт организации запросов и structured output.

В эксперименте зафиксированы одна модель — qwen3.5:4b, один набор из 1 160 задач и одинаковые эталонные ответы. Менялся только способ подачи вопросов. Разрыв в 47.4 пункта сохранился во всех 10 000 бутстрэп-репликах и оказался положительным в каждом из 30 кейсов.

Практический смысл: число, которое попадает в README как «accuracy», заметно зависит от того, как упакован промпт. Ниже разберу, что именно ломается, и покажу баг, который объясняет значительную часть разрыва.

Устройство кейса

Покажу на конкретном примере, иначе дальше будет абстрактно.

Кейс с командировкой. Общая политика задаёт лимит на гостиницу 9 000 ₽ за ночь. Сверху лежит временный приказ, поднимающий лимит до 12 000 ₽. Есть счёт из гостиницы на четыре ночи. Чтобы посчитать сумму к возмещению, модель должна определить, какой документ главнее, проверить срок действия приказа и сложить.

В варианте B я меняю в приказе одну строку — дату окончания — так, что приказ перестаёт действовать к четвёртой ночи. Максимальная сумма должна измениться с 48 000 ₽ на 45 000 ₽. При этом ответ на вопрос про базовую политику («какой лимит без приказов?») обязан остаться 9 000 ₽ — этой правки он не касается.

Это и есть единица бенчмарка: вариант A (исходный), вариант B (изменено существенное, ответ должен поехать), вариант C (изменена второстепенная деталь, вывод должен устоять). Всего 30 кейсов, 80 вариантов, 1 160 вопросов. У каждого вопроса эталонный ответ строго заданного типа: где-то булево, где-то деньги, где-то дата.

Два протокола опроса

Я гонял бенчмарк двумя способами. Данные, модель и эталоны одинаковые, различается структура вызова.

Независимый, по одному вопросу. Один документный вариант, один вопрос, полная JSON-схема под задачу. 1 160 отдельных запросов. Соседние вопросы модель не видит.

Пакетный компактный. Один вариант сразу со всеми вопросами к нему, поля в ответе короткие и общие. 80 запросов. Соседние вопросы видны.

По одному вопросу

Пакетно

Что в одном вызове

один вопрос

все вопросы по варианту

Вызовов всего

1 160

80

Схема ответа

полная, под задачу

короткая, общая

Видит соседние вопросы

нет

да

Точность ответа

28.4%

75.9%

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

Отсюда вывод, который я применяю в работе: Каждая итоговая оценка привязана к конкретному протоколу. Без его описания цифру сложно сравнивать с другими результатами.

Баг, объясняющий значительную часть разрыва

Я разобрал, на каких вопросах модель сыпется в независимом режиме. Хуже всего — на вопросах да/нет:

Строгая точность по булевым вопросам в независимом режиме: 1 из 499, то есть 0.2%.

Я полез в сырые ответы. Из 291 булева ответа, прошедшего внешний парсер, 290 не содержат поля answer. Не неправильное значение в нём — поля нет.

// что требует схема:{"answer": false, "decision": "not_a_reimbursement_cap", "evidence": [...]}// что отдаёт модель:{"decision": "not_a_reimbursement_cap", "evidence": [...], "reasoning": "..."}//  ↑ поля answer нет

Модель пишет решение, прикладывает доказательства, расписывает логику в reasoning, но сам ответ не кладёт. Я проверил две гипотезы: инверсию (truefalse) и нормализацию (строка "false" вместо булева, которую режет валидатор). Ни одна не подтвердилась — поля физически нет в объекте.

Для пайплайна это сбой, даже когда в reasoning лежит верный вывод: парсеру нечего извлечь. В пакетном режиме та же модель на тех же вопросах даёт по булевым 80.4%. Поле появляется или исчезает в зависимости от упаковки запроса.

Отсюда замечание для тех, кто работает со structured output: ошибка в значении и незаполненное поле — это разные сбои, и в одной метрике их лучше не смешивать. Иначе можно дорабатывать промпт для рассуждений там, где сломан формат ответа.

Подсчёт только по валидным ответам завышает результат

Ещё одно наблюдение, которое, насколько я вижу, влияет на многие опубликованные цифры.

Контрфактические пары устроены так: вариант B меняет существенное (ответ должен измениться), вариант C меняет второстепенное (вывод должен устоять). Меряем, ведёт ли себя модель ожидаемо.

Распространённый способ подсчёта — брать только пары, где оба ответа валидны. Логика понятная: отфильтровать мусор и смотреть на чистые случаи. На таком знаменателе все четыре типа отношений дают выше 0.94.

Зелёные столбцы — это подсчёт по валидным парам. Но для A-B флипа из 99 пар до подсчёта доживают 18; остальные отваливаются на этапе формата (в том числе из-за пропавшего поля answer). Модель, которая выдаёт корректный JSON в основном на простых парах, на таком знаменателе выглядит почти идеально стабильной, потому что трудные случаи выпадают ещё до подсчёта.

Поэтому я держу три слоя метрик рядом:

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

  • строгий + оба ответа верны — добавлен, чтобы два одинаково неправильных ответа не получали зачёт за «инвариантность» (формально не изменилось, но изменилось не туда).

  • по валидным парам — тот самый высокий показатель, но с явной пометкой про урезанный знаменатель.

Практический вывод: встречая relation accuracy около 0.95, стоит сразу проверить, по какому знаменателю она посчитана.

Что за бенчмарк

RuDocGround-CF — 30 русскоязычных кейсов корпоративного стиля. Состав:

  • комплекты из нескольких документов (политики, приказы, счета, переписка, системные выгрузки);

  • три варианта на кейс — A (база), B (существенная правка), C (второстепенная правка);

  • 80 вариантов, 1 160 вопросов, 556 размеченных связей между вариантами;

  • 11 типов ответов со строгой проверкой — булево, дата, деньги, идентификатор, порог и другие;

  • доказательства привязаны к ID конкретных документов.

Здесь проверяется не только правильность ответа, но и пригодность результата для автоматического пайплайна: некорректный JSON считается сбоем и не исключается из подсчёта.

Разрыв виден во всех 30 кейсах

Разрыв виден во всех 30 кейсах

TL;DR

  • Один и тот же qwen3.5:4b на одном датасете: 28.4% против 75.9% строгой точности, разница только в способе подачи вопросов (по одному за запрос против всех вопросов кейса сразу). Протокол стоит фиксировать рядом с цифрой.

  • 290 из 291 булева ответа в независимом режиме приходили без поля answer. Ошибка в значении и незаполненное поле — разные сбои.

  • Метрики «по валидным парам» считаются на урезанном подмножестве (18 пар из 99 для A-B флипа). Знаменатель стоит проверять.

  • Всё гоняется на MacBook Air M4 / 16 ГБ и воспроизводится целиком.

Материалы

Код, данные, эталонные ответы, предсказания и скрипты воспроизведения опубликованы в репозитории RuDocGround-CF. Там же доступна полная академическая версия статьи в PDF.

ссылка на оригинал статьи https://habr.com/ru/articles/1054240/