Почему размер чанка определяет качество retrieval
RAG-система ищет не по исходному документу, а по его фрагментам — чанкам. Каждый чанк превращается в вектор через embedding-модель Как выбрать embedding-модель для RAG: размер вектора, язык, качество и стоимость поиска, и именно этот вектор участвует в поиске. Если чанк слишком большой, embedding усредняет смысл и теряет специфичность: запрос «как настроить TLS в nginx» может не найти чанк на 3000 токенов, где нужная инструкция занимает три строки. Если чанк слишком маленький, в него не попадает достаточно контекста для осмысленного ответа, и модель получает фрагмент без опоры.
Задача chunking — найти баланс между гранулярностью поиска и полнотой контекста, который получит LLM после retrieval.
Фиксированный размер с перекрытием
Самый простой подход: разбить текст на блоки по N символов или токенов с перекрытием (overlap) в M единиц.
Код:
Текст: [AAAAAAAAAA|BBBBBBBBBB|CCCCCCCCCC]
Чанк 1: [AAAAAAAAAA]
Чанк 2: [AAAA|BBBBBBBBBB]
Чанк 3: [BBBB|CCCCCCCCCC]
Перекрытие нужно, чтобы информация на границе разреза не терялась. Без него предложение, попавшее ровно на стык двух чанков, не будет полностью представлено ни в одном из них.
Типичные параметры:
| Параметр | Диапазон | Когда использовать |
|---|---|---|
| Размер чанка | 256–1024 токенов | Универсальный старт |
| Overlap | 10–20% от размера | Всегда, кроме поабзацного разбиения |
Размер в токенах предпочтительнее размера в символах, потому что embedding-модели и LLM работают с токенами. 512 токенов для английского текста — примерно 380–400 слов, для русского — 300–350 слов из-за более длинных словоформ.
Ограничение метода. Фиксированный размер игнорирует структуру документа. Разрез может пройти посреди таблицы, между заголовком и его содержимым, внутри определения термина. Это снижает качество retrieval для структурированных документов.
Рекурсивное разбиение по структуре
Более точный подход — учитывать естественные границы текста. Рекурсивный сплиттер работает по приоритетному списку разделителей:
- Заголовки (
#,##,###в Markdown; теги<h1>–<h6>в HTML)
- Двойной перенос строки (граница абзаца)
- Одинарный перенос строки (граница строки)
- Точка, вопросительный/восклицательный знак (граница предложения)
- Пробел (граница слова)
Алгоритм пытается разбить текст по первому разделителю. Если полученный фрагмент всё ещё превышает целевой размер, спускается к следующему уровню. Это сохраняет логическую целостность: абзац не разрывается, если помещается в лимит.
Код:
Разделители по приоритету:
["\n## ", "\n\n", "\n", ". ", " "]
Целевой размер: 512 токенов
Документ → разбить по "\n## " → секции
Секция > 512 токенов? → разбить по "\n\n" → абзацы
Абзац > 512 токенов? → разбить по "\n" → строки
Строка > 512 токенов? → разбить по ". " → предложения
Этот метод хорошо работает для документации, статей, юридических текстов — всего, где есть явная структура.
Семантический chunking
Идея: разбивать текст не по формальным границам, а по смене темы. Алгоритм:
- Текст разбивается на предложения.
- Каждое предложение эмбеддится.
- Вычисляется косинусное расстояние между соседними embedding-векторами.
- В точках, где расстояние превышает порог, ставится граница чанка.
Код:
Предложения: S1 S2 S3 S4 S5 S6 S7
Расстояние: 0.1 0.1 0.4 0.1 0.1 0.5
Границы: ^ ^
Чанки: [S1-S3] [S4-S6] [S7...]
Плюсы: чанки соответствуют смысловым блокам, даже если в тексте нет заголовков.
Минусы: требует дополнительного прогона embedding-модели по всем предложениям (вычислительно дороже), порог нужно подбирать под конкретный корпус, результат нестабилен на коротких текстах.
Семантический chunking оправдан, когда документы неструктурированы (транскрипты разговоров, потоковые заметки) и фиксированный или рекурсивный подход даёт плохой retrieval.
Специализированные стратегии для разных типов контента
Код
Разбивать по функциям, классам или модулям. Разрез посреди тела функции делает чанк бесполезным. Если функция длиннее целевого размера, лучше разбить по логическим блокам внутри неё с добавлением сигнатуры в каждый чанк как контекста.
Таблицы
Таблицу стоит сохранять целиком как один чанк, если она помещается в лимит. Если нет — разбивать по строкам, но в каждый чанк включать заголовок таблицы и строку с названиями колонок. Без этого LLM не сможет интерпретировать числа.
PDF и сканы
PDF-парсеры часто теряют структуру: колонки смешиваются, заголовки отрываются от текста, таблицы превращаются в набор ячеек без связей. Перед chunking стоит нормализовать извлечённый текст: восстановить порядок чтения, объединить разорванные абзацы, пометить таблицы.
Диалоги и транскрипты
Естественная граница — реплика или обмен репликами. Фиксированный размер здесь работает плохо, потому что разрезает контекст разговора. Лучше группировать по темам или по спикеру с перекрытием.
Метаданные чанка
Каждый чанк должен нести метаданные, которые помогают при retrieval и при генерации ответа:
- Источник: имя файла, URL, идентификатор документа
- Позиция: номер чанка в документе, смещение
- Контекст: заголовок секции, к которой относится чанк
- Тип контента: текст, таблица, код, список
Заголовок секции особенно важен. Если чанк содержит текст «Шаг 3: нажмите кнопку подтверждения», без заголовка «Установка сертификата» он бесполезен. Добавление родительского заголовка в начало чанка (или в отдельное поле метаданных) заметно улучшает точность поиска.
Как размер чанка влияет на контекстное окно
После retrieval топ-K чанков подставляются в промпт LLM. Если K = 5 и каждый чанк по 1024 токена, это 5120 токенов только на контекст. При лимите модели в 8K токенов остаётся мало места для системного промпта, инструкции и ответа.
Практическое правило: суммарный объём retrieved-чанков не должен превышать 50–70% контекстного окна модели. Это значит, что при K = 5 и окне 8K оптимальный размер чанка — около 500–800 токенов.
Если модель поддерживает 128K контекст, ограничение смягчается, но растёт стоимость инференса и latency. Большой контекст не компенсирует плохой retrieval: если нужный чанк не найден, объём окна не поможет.
Типичные ошибки
Разбиение без overlap. Граничная информация теряется, и запрос, который должен был найти чанк, возвращает соседний с частичным совпадением.
Слишком крупные чанки «на всякий случай». Embedding усредняет смысл, специфичные детали тонут, precision падает. Модель получает много нерелевантного текста.
Игнорирование структуры. Таблица разрезана пополам, заголовок отделён от содержимого, код разбит посреди функции. Retrieval находит фрагмент, но LLM не может его использовать.
Один размер для всех типов документов. Техническая документация, юридические договоры, чаты поддержки и код требуют разных стратегий. Универсальный размер 512 токенов — точка старта, не финальное решение.
Отсутствие метаданных. Чанк без указания источника и секции не позволяет LLM сослаться на документ и не даёт возможности фильтровать поиск по типу контента.
Проверка качества chunking
После настройки стратегии стоит оценить результат:
- Визуальная инспекция. Выбрать 20–30 случайных чанков и проверить: каждый ли самодостаточен, понятен ли без соседних, не разрезана ли логическая единица.
- Retrieval-тест. Составить 10–20 вопросов, на которые есть ответы в корпусе. Для каждого вопроса проверить, попадает ли нужный чанк в топ-5 результатов.
- Граничные случаи. Проверить запросы, ответ на которые находится на стыке двух чанков. Если overlap недостаточен, такие запросы будут проваливаться.
- Метрики. Если есть размеченный датасет, считать Recall@K и MRR (Mean Reciprocal Rank) для разных размеров чанка и стратегий.
Если Recall@5 ниже 0.8 на тестовых вопросах, проблема скорее всего в chunking или в embedding-модели Как выбрать embedding-модель для RAG: размер вектора, язык, качество и стоимость поиска, а не в промпте LLM.
Практический чек-лист
- Определить тип контента (документация, код, диалоги, таблицы) и выбрать стратегию под него
- Начать с рекурсивного разбиения, целевой размер 512 токенов, overlap 50–100 токенов
- Добавить метаданные: источник, заголовок секции, тип контента
- Для таблиц и кода — сохранять логическую целостность, не резать посреди структуры
- Прогнать retrieval-тест на 15–20 вопросах, измерить Recall@5
- Если precision низкий — уменьшить чанк или добавить overlap
- Если recall низкий — увеличить чанк или перейти к семантическому разбиению
- Проверить, что суммарный объём топ-K чанков укладывается в 50–70% контекстного окна модели
