Возьмём короткий абзац из внутренней инструкции службы поддержки:

Возврат оформляется в течение трёх рабочих дней. Если сумма превышает 50 000 рублей, заявку сначала согласует руководитель отдела. Без согласования оператор не имеет права передавать её в бухгалтерию.

И контрольный вопрос, который завтра задаст сотрудник: «Может ли оператор отправить в бухгалтерию возврат на 70 000 рублей без согласования?»

Теперь разрежем абзац двумя способами. В первом варианте граница падает посреди второго предложения:

  • Фрагмент А1: «Возврат оформляется в течение трёх рабочих дней. Если сумма превышает 50 000 рублей,»
  • Фрагмент А2: «заявку сначала согласует руководитель отдела. Без согласования оператор не имеет права передавать её в бухгалтерию.»

Во втором варианте граница проходит между предложениями:

  • Фрагмент Б1: «Возврат оформляется в течение трёх рабочих дней.»
  • Фрагмент Б2: «Если сумма превышает 50 000 рублей, заявку сначала согласует руководитель отдела. Без согласования оператор не имеет права передавать её в бухгалтерию.»

Что видит модель? В схеме А поиск по вопросу про 70 000 рублей может принести А1: там есть и сумма, и порог, но нет действия. Либо А2: там есть запрет, но нет условия, и непонятно, при какой сумме он действует. Модель либо честно скажет, что информации недостаточно, либо додумает. В схеме Б фрагмент Б2 содержит условие, действие и запрет целиком, и ответ собирается без угадывания. Фрагмент Б1, наоборот, почти бесполезен для этого вопроса, но и не мешает: он короткий.

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

Две цены одного куска: когда мало и когда много

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

Слишком длинный фрагмент ломает точность другим способом. Вектор длинного куска усредняет несколько тем. Если в одной главе на три страницы вперемешку описаны порядок возврата, правила обмена и условия гарантии, запрос про возврат совпадёт с этим фрагментом «примерно», но так же «примерно» совпадёт и запрос про гарантию. В промпт уйдёт вся глава, включая две трети лишнего. Лишнее стоит денег и задержки, а главное, отвлекает: модель может зацепиться за соседнее правило, которое звучит похоже.

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

Символы, токены и границы документа: три разные линейки

Когда говорят «размер чанка», обычно смешивают три единицы измерения.

  • Символы. Просто посчитать, но они слабо связаны с тем, что реально ограничивает систему. Одна и та же тысяча символов по-русски, по-английски и в тексте с номерами артикулов превращается в разное число токенов.
  • Токены. Именно в них считаются лимит входа эмбеддинг-модели и бюджет контекста генеративной модели. Но токенизатор у каждой модели свой, и русский текст у разных токенизаторов даёт разное количество токенов. Число «512» без указания токенизатора мало о чём говорит.
  • Структурные границы. Заголовок, абзац, пункт списка, строка таблицы. Это единицы смысла, заданные автором документа, и они не зависят ни от символов, ни от токенов.

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

Примерно такую логику закладывают в инструменты разбиения, которые работают с разобранной структурой документа. В документации Docling описан, например, HybridChunker: он уточняет иерархическое разбиение с учётом токенизатора (Docling: Chunking). Иначе говоря, сначала берётся деление, которое следует структуре документа, а затем с помощью токенизатора проверяется, как получившиеся куски соотносятся с ограничениями модели. Отсюда следует важная вещь: у такого чанкера нет «правильного размера из коробки». Максимальная длина в токенах привязана к выбранному токенизатору, а значит, к выбранной модели, и подбирается под неё. Если вы смените эмбеддинг-модель, смените и токенизатор в разбиении. И только потом сравнивайте результаты.

Что делать с заголовками и таблицами

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

Таблицы — отдельная история. Длинную таблицу нельзя резать по числу символов: строка с цифрами без шапки превращается в набор бессмысленных значений. «12 | 3 | 0,5» — это что? Документация Docling отмечает, что заголовки таблицы могут повторяться в её фрагментах (Docling: Chunking). Для читателя этого факта следует вынести практический смысл: если таблица большая и всё же разбивается, нужно убедиться, что каждый кусок несёт шапку, иначе значения оторваны от названий колонок. А ещё полезно проверить на нескольких своих таблицах, как именно это работает в вашей версии и конфигурации, а не полагаться на память о документации.

Минимальный набор решений по структуре, которые стоит принять до сравнения размеров:

  1. Заголовки разделов сохраняются в метаданных или в самом тексте фрагмента.
  2. Таблица либо остаётся одним фрагментом, если помещается, либо делится по строкам с повтором шапки.
  3. Список, у которого есть вводная фраза («Для возврата требуется:»), не отрывается от неё.
  4. Пример кода, команда или формула не режутся посередине.

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

Схемы для сравнения: пусть их будет мало

Соблазн перебрать десять значений от 128 до 2048 токенов с шагом в 128 понятен, но малополезен. Большинство соседних значений дают близкие результаты, а вывод вида «на 384 лучше, чем на 352» тонет в шуме выборки. Гораздо информативнее сравнить несколько принципиально разных схем:

  • Схема 1. Фиксированная нарезка. Окно в заданное число токенов с небольшим перекрытием, без учёта структуры. Это базовая линия: она показывает, сколько даёт структура сама по себе.
  • Схема 2. По абзацам и заголовкам. Каждый раздел или абзац становится фрагментом, длинные делятся по границам предложений, короткие соседние объединяются до потолка.
  • Схема 3. Структура плюс токенизатор. Деление по иерархии документа, затем проверка и уточнение с учётом токенизатора вашей модели; например, через HybridChunker, если документы разбираются Docling.
  • Схема 4. То же с контекстом заголовков. Схема 3, где к каждому фрагменту добавлена цепочка заголовков.

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

Что должно оставаться неизменным

Эксперимент по чанкам легко испортить тем, что меняется что-то ещё. Поэтому зафиксируйте:

  • эмбеддинг-модель и её токенизатор;
  • способ поиска (векторный, гибридный, с реранкером или без) и все его параметры;
  • число извлекаемых фрагментов или бюджет контекста в токенах. Выбирайте одно: если фиксируете число фрагментов, схемы с длинными кусками получат больший контекст и выиграют нечестно; если фиксируете бюджет в токенах, короткие куски получат больше мест. Лучше прогнать оба режима отдельно и честно отметить, какой из них показан;
  • генеративную модель, промпт и температуру;
  • исходные документы и их версии.

Особенно важно не менять одновременно модель и разбиение. Если вы заменили эмбеддинг-модель и одновременно переделали чанки, любая разница в качестве будет неотделима от эффекта замены. Сначала разбиение при старой модели, потом, если нужно, модель при выбранном разбиении.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Набор вопросов, который придумывают раньше схем

Контрольные вопросы пишутся до того, как вы увидите результаты чанкинга, иначе легко подогнать вопросы под удачные куски. Для каждого вопроса фиксируются:

  1. текст вопроса в том виде, в котором его задал бы сотрудник;
  2. документ и место, где лежит ответ (раздел, номер пункта, таблица);
  3. минимальный набор фактов, без которых ответ неполон: например, для нашего вопроса о 70 000 рублях это «порог 50 000», «нужно согласование руководителя отдела», «без него передавать в бухгалтерию нельзя»;
  4. тип вопроса: фактоид, условие и действие, значение из таблицы, процедура из нескольких шагов, вопрос, ответа на который в документах нет.

Последний тип часто забывают, а он важен. Если система на вопрос без ответа уверенно отвечает по случайному фрагменту, это проблема, которая усиливается при длинных кусках с множеством тем. Добавьте несколько таких вопросов и пропишите ожидаемое поведение: «сообщить, что в документах это не найдено».

Сколько вопросов нужно? Для первого отбора вполне хватает нескольких десятков, если они покрывают разные документы и разные типы. Если у вас собрано по 5 вопросов на 20 типов документов, это лучше, чем 100 однотипных вопросов к одному регламенту. И сразу прикиньте, что каждая схема потребует переиндексации корпуса: по числу схем и размеру корпуса можно понять, сколько вычислительного времени нужно запасти.

Как прогонять и что считать

Для каждой схемы и каждого вопроса записывайте три уровня результата.

Уровень поиска. Попал ли в топ-k фрагмент, содержащий все нужные факты целиком? Не «хоть какой-то кусок из правильного документа», а именно полный набор. Отдельно отмечайте случай, когда нужные факты разошлись по двум фрагментам: оба нашлись (спасение за счёт k), нашёлся только один (потерян контекст) или не нашёлся ни один.

Уровень ответа. Верен ли ответ модели и не добавила ли она того, чего нет в источнике. Проверки такого рода удобно описывать декларативно. Инструменты вроде Promptfoo предлагают для этого assertions, причём условия можно объединять в группы (Promptfoo: Assertions and Metrics). Для нашего вопроса о возврате это, например, группа: ответ упоминает необходимость согласования руководителем и не утверждает, что оператор может передать заявку самостоятельно. Для вопросов про отсутствующую информацию проверка обратная: ответ содержит отказ.

Уровень цены. Сколько токенов контекста ушло на вопрос и сколько времени заняла обработка. В Promptfoo предусмотрены проверки стоимости и задержки, а пороги задаются конфигурацией теста (Promptfoo: Assertions and Metrics). Если вы пользуетесь другим инструментом, замените его, суть та же: цена и скорость измеряются рядом с качеством, а не потом.

Теперь посмотрим, почему цену нельзя оставлять на потом. Возьмём условный расчёт, цифры в нём выбраны только для иллюстрации. Пусть в контекст попадает пять фрагментов. При фрагментах около 200 токенов это 1000 токенов контекста на вопрос, при фрагментах около 800 токенов это 4000. Если ассистент отвечает на 10 000 вопросов в день, разница между схемами составляет 10 и 40 миллионов входных токенов в день. Вы платите (деньгами или временем обработки) четырёхкратную разницу, и качество должно её оправдывать. Если длинные куски дают прирост на пару процентов, а короткие с заголовками дают почти то же при четверти цены, выбор очевиден. Если длинные куски единственные, кто не теряет условия в сложных регламентах, то, возможно, они стоят своих денег, но только для этого типа документов.

Спорные результаты: как их читать

Самое интересное в таблице результатов не победитель, а случаи, где схемы расходятся. Они делятся на несколько понятных типов.

Ответ найден, но стоит дорого

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

Потерян контекст

Нашёлся фрагмент с правильной фразой, но без условия, при котором она верна, ровно как А2 в нашем примере. Симптомы: ответ звучит убедительно, но неполон, либо верен «в общем случае» и ошибочен в вашем конкретном. Проверьте, где проходила граница. Если она разрезала предложение или оторвала пункт от вводной фразы, дело в нарезке, а не в размере: увеличение потолка может помочь случайно, но надёжнее запретить разрез в этом месте. Если же условие лежит в другом разделе (например, в общих положениях, на которые документ лишь ссылается), никакой размер не поможет, и придётся менять способ хранения связей между разделами или добавлять заголовочный контекст.

Вырос шум

При переходе к более длинным кускам точность поиска упала: в топ-k чаще попадали фрагменты, которые только похожи на вопрос. Это особенно заметно на коротких запросах из одного-двух слов и на корпусах, где много однотипных документов с похожими формулировками, например десяток почти одинаковых регламентов для разных подразделений. В такой ситуации помогает не только уменьшение куска, но и метаданные (подразделение, версия, тип документа) как фильтр. Но это уже изменение поиска, а не чанкинга, и его нужно проверять отдельным прогоном, а не вместе с размером.

Таблицы

Отдельно выпишите вопросы, ответ на которые лежит в таблицах. Если при какой-то схеме числа находятся, но модель путает колонки, значит, фрагмент пришёл без шапки или шапка оторвана от значений. Сверьтесь с тем, как ваш конвейер обращается с заголовками таблицы при разбиении; в документации Docling заявлено повторение заголовков в фрагментах таблицы, и стоит убедиться на ваших файлах, что в итоговых кусках шапка действительно присутствует. Если схема не воспроизводит это, стоит подумать о другом представлении таблицы: например, превращать строку в короткое предложение вида «Тариф X: срок 3 дня, лимит 50 000».

Вопросы без ответа

Если на вопросы без ответа система отвечает уверенно, смотрите, какие фрагменты она получила. Часто это длинные куски, близкие по теме, но не содержащие нужного факта. Модель берёт «ближайшее» и достраивает. Короткие куски с заголовками обычно делают отсутствие ответа заметнее: контекст явно не про это. Но проверьте на своих данных, универсальным правилом это считать нельзя.

Записывайте не победителя, а причину

Когда таблица результатов готова, не ограничивайтесь строкой «схема 4, потолок 400». Запишите по каждому типу документов, почему эта схема выиграла: «условия и действия в регламентах лежат в пределах одного пункта, а пункт укладывается в потолок», «таблицы тарифов нужно держать целиком с шапкой». Тогда, когда в корпусе появятся новые документы или вы сменили модель, вы будете знать, какие предпосылки нужно проверить заново, и не станете запускать полную перенастройку из-за того, что что-то «сломалось».

Если вас интересует более общая картина того, как размер куска влияет на RAG, у нас есть отдельный материал: Чанкинг документов: как размер куска ломает или спасает RAG. Здесь же мы говорили о том, как проверить это на ваших собственных данных.

Где всё это считать

Эксперимент по чанкам не требует большой инфраструктуры, но требует места, где его можно спокойно повторять: индексатор, хранилище векторов, скрипты прогона вопросов и журнал результатов. Для небольшого корпуса и нескольких схем обычно достаточно скромного VPS: переиндексация идёт в фоне, результаты складываются в файлы или базу. На VPS MAATRIX доступны NVMe-диски, процессоры AMD EPYC и root-доступ, так что можно поставить нужные версии библиотек, держать несколько независимых индексов рядом и не ломать рабочее окружение. Это удобно именно для сравнения схем: каждый индекс лежит отдельно, а прогон на старой схеме можно повторить через месяц на тех же данных.

Какой тариф нужен, зависит от размера корпуса, выбранной эмбеддинг-модели и того, где вы её запускаете. Для небольшой выборки документов можно начать с младших конфигураций линейки: Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) или Uab (2 vCPU, 4 ГБ, 80 ГБ), а для крупного корпуса и параллельных индексов смотрят в сторону Heka (4 vCPU, 8 ГБ, 160 ГБ), Maat (8 vCPU, 16 ГБ, 240 ГБ) или Djed (16 vCPU, 32 ГБ, 320 ГБ). Это ориентиры по ресурсам, а не обещание ёмкости: реальную нагрузку покажет ваш замер. Если эмбеддинги считает внешний сервис, потребность в оперативной памяти снижается, но тогда стоит отдельно проверить, доступен ли нужный вам сервис из выбранной локации.

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

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

Возвращаясь к исходному абзацу о возврате: для документов такого типа (регламенты, инструкции, положения, где важна связка «условие — действие — исключение») рабочее правило звучит так. Режем по структуре документа: пункт или абзац как минимальная единица, раздел как естественный потолок. Условие не отрываем от действия, вводную фразу не отрываем от списка. К каждому фрагменту добавляем цепочку заголовков. Потолок в токенах задаём по токенизатору вашей эмбеддинг-модели так, чтобы типичный пункт с условием и действием помещался целиком, а точное значение определяем прогоном на контрольных вопросах, в которых условие и действие лежат рядом. Любую схему, где граница может пройти внутри предложения с условием, из рассмотрения исключаем сразу.

Это правило относится к документам именно такого устройства. Для таблиц тарифов и спецификаций, для длинных связных текстов вроде аналитических отчётов, для переписки и тикетов, для кода и API-документации единицы смысла устроены иначе, и выбор схемы для них заслуживает отдельного эксперимента с собственными вопросами. Взять результат, полученный на регламентах, и применить его к договорам или к выгрузке чатов — это то же самое, что взять число токенов из чужой статьи: красиво, быстро и ничем не подкреплено.