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

Этот эксперимент показывает, что итоговая точность 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, но сам ответ не кладёт. Я проверил две гипотезы: инверсию (true↔false) и нормализацию (строка "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 считается сбоем и не исключается из подсчёта.
TL;DR
-
Один и тот же
qwen3.5:4bна одном датасете: 28.4% против 75.9% строгой точности, разница только в способе подачи вопросов (по одному за запрос против всех вопросов кейса сразу). Протокол стоит фиксировать рядом с цифрой. -
290 из 291 булева ответа в независимом режиме приходили без поля
answer. Ошибка в значении и незаполненное поле — разные сбои. -
Метрики «по валидным парам» считаются на урезанном подмножестве (18 пар из 99 для A-B флипа). Знаменатель стоит проверять.
-
Всё гоняется на MacBook Air M4 / 16 ГБ и воспроизводится целиком.
Материалы
Код, данные, эталонные ответы, предсказания и скрипты воспроизведения опубликованы в репозитории RuDocGround-CF. Там же доступна полная академическая версия статьи в PDF.
Автор: StefanBidzhamov


