Качество ответов RAG чаще ограничивает нарезка документации, а не сама модель. Чанк на 2000 токенов, в котором смешаны три разные функции, разрезанная пополам таблица параметров или код, потерявший сигнатуру, ломают поиск сильнее, чем слабая LLM. Chunk size и overlap - две ручки, которые настраивают первыми, и обе не имеют универсального правильного значения.
Прямой ответ: chunk size - размер фрагмента документа, который индексируется как отдельная единица и превращается в один вектор. Overlap - сколько текста из конца предыдущего чанка повторяется в начале следующего, чтобы смысл на стыке не терялся. Стартовые ориентиры для текстовой документации: 256-512 токенов и overlap 10-15% от размера чанка. Это отправная точка для проверки, а не финальные настройки.
Цепочка выглядит так: документ, правила разбиения, чанки, эмбеддинги, векторный индекс, top-k по запросу, контекст LLM, ответ. Результат на выходе зависит от связки chunk size + overlap + стратегия границ + модель эмбеддингов + reranking. Подбор одного параметра при слабом звене в другом месте ничего не даст. Если пайплайна еще нет, начните с общей схемы: как подключить документацию, wiki и FAQ к LLM через RAG.
Что такое chunk size и overlap и почему нет универсального значения
Универсального лучшего размера чанка не существует. Он зависит от типа документации, длины ответов, которые вы ждете, модели эмбеддингов, контекстного окна генеративной модели и того, как формулируют вопросы ваши пользователи. Совет из чужой статьи про 1000 токенов без проверки на своих документах дает случайный результат: у вас другой корпус, другой язык и другие вопросы.
Чем chunk size отличается от overlap и как они взаимодействуют
Chunk size задает длину фрагмента. Overlap задает, насколько соседние фрагменты перекрываются. Пример: чанк 500 токенов и overlap 50 токенов означают, что каждый следующий чанк начинается на 450 токенов позже предыдущего, а последние 50 токенов предыдущего фрагмента повторяются в начале нового.
Число чанков приблизительно оценивается так:
N ≈ T / (chunk_size - overlap)
T - общее число токенов в корпусе
chunk_size - размер чанка в токенах
overlap - перекрытие в токенах
Из формулы видно, откуда берется рост индекса: overlap 50% при чанке 500 токенов почти удваивает число векторов. Реальное значение чуть больше из-за последнего неполного чанка.
Overlap не решает проблему разрезанной таблицы или блока кода. Если граница проходит по середине сигнатуры функции, дублирование 50 токенов не вернет целостность: оба чанка получат обрывок, и запрос про эту функцию не совпадет ни с одним. Для таких блоков важны правила границ, а не размер перекрытия.
Взаимодействие параметров одностороннее: chunk size задает верхнюю границу того, что вообще может попасть в один вектор, overlap лишь сглаживает стыки. Наращивать overlap вместо исправления правил разбиения - тупик.
На что реально влияют параметры: retrieval, контекст LLM и стоимость
Retrieval. Крупный чанк смешивает несколько тем в одном векторе. Эмбеддинг получается усредненным, и близость к конкретному вопросу падает: фрагмент на 1500 токенов про установку, конфигурацию и миграцию одинаково далек от каждого из трех запросов. Мелкий чанк дает точный вектор, но теряет смысл: предложение без подлежащего или шаг инструкции без условия, при котором он выполняется, плохо отвечают на вопрос.
Контекст LLM. Top-k умножается на chunk size. k=10 при чанках 800 токенов дает до 8000 токенов только под найденный контекст, плюс системный промпт, история диалога и сам вопрос. Это упирается в окно модели и в стоимость генерации: длинный вход считает каждая модель, а в локальном варианте цена измеряется временем на prefill и памятью под KV-кэш.
Индекс и latency. Число чанков определяет число векторов. Больше векторов означает дольше индексацию, больше места в хранилище и выше нагрузка на поиск. Растет overlap - растет и число векторов, а платите вы за это запросами к модели эмбеддингов при каждой переиндексации корпуса.
Компромисс выглядит так: крупные чанки дешевле по числу векторов и дают больше контекста в одном попадании, но размывают релевантность и съедают окно. Мелкие точнее в поиске, но требуют большего k и часто режут смысл. Оптимума под все случаи нет, есть выбор под ваш корпус и ваш тип вопросов.
Стартовые ориентиры по chunk size и overlap для своей документации
Таблица ниже - практические точки старта, а не результаты тестов и не гарантия качества. Финальные значения определяются проверкой на вашем наборе вопросов.
| Тип документации | Стартовый chunk size | Overlap | Что важнее размера |
|---|---|---|---|
| Текстовая документация, статьи, руководства | 256-512 токенов | 10-15% | Целостность абзаца |
| Технические инструкции с плотной терминологией | 512-800 токенов | 10-20% | Целостность шага и его условия |
| FAQ, короткие справочные статьи | 128-256 токенов | 0-10% | Один вопрос, один чанк |
| Справочники API | по функции или эндпоинту | 0% | Сигнатура, параметры, пример вызова |
| Таблицы, прайсы, матрицы совместимости | строки плюс шапка | 0% | Сохраненная шапка таблицы |
| Changelog и релиз-ноты | по записи версии | 0% | Не смешивать версии |
Overlap 10-20% покрывает типичный случай, когда смысл фразы размазан по границе. Для кода, таблиц и FAQ перекрытие чаще вредит: оно порождает почти одинаковые векторы, и top-k забивается копиями одного фрагмента.
Как пересчитать ориентиры в токенах под свой корпус
Возьмите 10-20 типичных фрагментов своей документации, прогоните через токенизатор модели, которая задает ограничение, и посмотрите распределение. Короткий скрипт на Python:
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("ваша-эмбеддинг-модель")
paths = ["docs/deploy.md", "docs/api.md", "docs/faq.md"]
for p in paths:
text = open(p, encoding="utf-8").read()
ids = tok(text, add_special_tokens=False)["input_ids"]
print(f"{p}: {len(text)} символов, {len(ids)} токенов, "
f"{len(text) / len(ids):.2f} символа на токен")
Считайте по той модели, которая задает ограничение. Токенизатор эмбеддинг-модели и генеративной LLM почти всегда разные: чанк измеряется в токенах эмбеддера, а в окно генеративной модели он попадает уже в своих токенах. Ошибетесь с базой - реальный размер входа окажется в полтора-два раза больше ожидаемого.
Токены не равны символам. Для английского текста один токен часто покрывает около 4 символов, для русского при BPE-токенизации один токен укладывается примерно в 2-3 символа, местами меньше. Значит, 500 токенов русского текста - это заметно меньше знаков, чем 500 токенов английского. Ориентиры из англоязычных статей, перенесенные в русскую документацию без пересчета, дают чанки мельче, чем нужно.
Отдельный фактор - мультиязычные эмбеддинг-модели. Они обучались на разных языках, и качество вектора для русского текста может отличаться от английского при том же числе токенов. Ограничение на размер чанка остается токенным, но плотность смысла на один токен у русского текста своя.
Как тип документации меняет выбор
Справочник API: единица смысла - функция или эндпоинт целиком, вместе с сигнатурой, параметрами, кодами ошибок и примером вызова. Резать такой блок по 400 токенов бессмысленно, ответ на вопрос про параметры метода разбросан по трем чанкам.
Пошаговая инструкция: единица смысла - шаг вместе с условием и ожидаемым результатом. Если шаг требует выполнения предыдущего, чанк должен включать контекст, после чего это делается, иначе LLM выдаст инструкцию без предусловия.
FAQ: единица смысла - пара вопрос-ответ. Здесь работает chunk size, равный длине ответа, а overlap не нужен вовсе.
Длинные концептуальные статьи: единица смысла - абзац или группа абзацев вокруг одного тезиса. Крупные чанки тут допустимы, поскольку в тексте мало точных терминов-якорей, по которым ищет пользователь.
Changelog и релиз-ноты: единица смысла - запись версии. Смешивать версии в одном чанке опасно: модель процитирует устаревшее поведение как актуальное.
Документация с большим объемом кода: ориентиры в токенах работают плохо, работают структурные правила. О них ниже.
Как настроить границы разбиения: код, таблицы и структура документации
Правила границ дают больше, чем тюнинг размера. Чанк на 600 токенов, нарезанный по заголовкам, работает лучше чанка на 400 токенов, разрезанного посередине абзаца.
Базовый подход - рекурсивное разбиение по иерархии разделителей: сначала по заголовкам, затем по пустым строкам, потом по предложениям, и только в крайнем случае по символам. Такой recursive splitter пытается уложить фрагмент в лимит, не разрывая более крупную логическую единицу. Для Markdown добавьте в список разделителей уровни заголовков, элементы списков и блоки кода, а путь по заголовкам сохраняйте в метаданных.
Альтернативы, которые стоит рассмотреть, если базовый вариант не дает нужного качества: parent-child retrieval, где поиск идет по мелким чанкам, а в контекст LLM отдается родительский крупный блок, и semantic chunking, где границы ставятся там, где меняется смысл, по расстоянию между соседними предложениями в векторном пространстве. Обе техники не универсальны: parent-child добавляет слой логики и усложняет индексацию, semantic chunking зависит от качества эмбеддинг-модели и стоит дополнительных вычислений.
Почему код и таблицы нельзя резать по символам
Разрезанная сигнатура функции дает два чанка, каждый из которых не соответствует ни одному осмысленному запросу. Пользователь спрашивает, как вызвать метод, а в индексе лежат def method_x(self, и timeout=30) -> dict:. Поиск не находит ни то, ни другое, а LLM, получив обрывок, достраивает недостающие параметры по догадке.
Разрезанная таблица теряет шапку. Строка 12 | 8 | да без заголовков про VRAM, число слоев и поддержку бесполезна и для поиска, и для генерации. Модель либо проигнорирует фрагмент, либо припишет числа не тем колонкам.
Правила, которые снимают большую часть проблемы:
- Код: функция, класс или логический блок целиком как атомарный чанк. Если блок превышает лимит, режьте по границам функций и сохраняйте в каждом чанке объявление класса и импорты, нужные для понимания.
- Таблицы: одна строка или группа строк плюс заголовок таблицы в каждом чанке. Для широких таблиц дублируйте шапку текстом:
Параметр: значение. - Списки: элемент списка вместе с вводной строкой, иначе отдельный пункт теряет смысл.
- Формулы и схемы: целиком, с подписью и расшифровкой переменных.
- Примеры кода внутри инструкции: пример остается привязанным к шагу, который он иллюстрирует.
Overlap в таких блоках обычно вреден. Он не восстанавливает разорванную структуру, а создает вторую копию обрывка. Уменьшайте его до нуля, когда чанки атомарны.
Метаданные чанка: что сохранять вместе с текстом
К тексту чанка добавляйте служебные поля: путь по заголовкам, тип блока (код, таблица, текст, список), имя файла, версию или дату документа, язык, идентификатор раздела. Это дает фильтрацию и переранжирование без изменения chunk size.
Пример применения: пользователь спрашивает про параметр из версии 2.0, а в индексе лежат чанки 1.x и 2.x. Фильтр по полю версии убирает половину ложных попаданий, тогда как подбор chunk size эту проблему не решает вообще. Аналогично фильтр по типу блока помогает, когда вопрос про код, а в выдаче лежат прозаические описания.
Метаданные стоит вклеивать в текст чанка, если модель эмбеддингов не поддерживает отдельные поля. Строка вида Файл: deploy.md | Раздел: Настройка GPU | Тип: инструкция в начале чанка помогает поиску по формулировкам, которые встречаются в заголовках, а не в теле документа.
Как проверить chunk size и overlap на своём наборе вопросов
Без eval-набора любое изменение настроек превращается в гадание. Разница между чанками 400 и 600 токенов не видна ни в двух ответах, ни в десяти, но на полусотне вопросов она проявляется в метриках.
Минимальный eval-набор: сколько вопросов и какие
30-50 вопросов уже дают сигнал, если они покрывают разные типы запросов. Ниже 30 выводы случайны, выше 100 растет стоимость прогонов, а прирост информации падает.
- Фактологические: какое значение по умолчанию у параметра.
- Процедурные: как настроить компонент с нуля.
- Навигационные: где описана конкретная интеграция.
- По коду: какие аргументы принимает функция.
- По таблицам: какой объем памяти нужен для конфигурации из строки.
- Пограничные: вопросы, ответа на которые в документации нет вообще.
У каждого вопроса должен быть явный правильный источник в документации: файл и раздел, откуда берется ответ. Это ground truth, без него считать recall нельзя. Формулируйте вопросы так, как их задают пользователи: «почему падает с ошибкой 500 при загрузке» работает лучше, чем «Обработка ошибок загрузки» из заголовка раздела.
Метрики retrieval: что считать и как интерпретировать
| Метрика | Что показывает | Как читать |
|---|---|---|
| Recall@k | Доля вопросов, где правильный чанк попал в top-k | Главная метрика для RAG: пропущенное LLM не восстановит |
| Precision@k | Доля релевантных чанков среди top-k | Мусор в контексте повышает риск ошибки и расход токенов |
| MRR | Средняя обратная позиция первого правильного чанка | Показывает, насколько высоко в выдаче нужный фрагмент |
| Hit rate | Доля вопросов, где найден хотя бы один правильный чанк | Быстрый индикатор полного провала поиска |
Крупные чанки обычно поднимают recall и снижают precision: в один фрагмент попадает больше текста, шанс задеть нужный абзац растет, но вместе с ним в top-k приезжает лишнее. Мелкие чанки дают обратную картину. Для RAG приоритет у recall при умеренном k: если правильный фрагмент не найден, генерация не поможет, а лишние чанки LLM частично отфильтрует сама.
Ищите точку, где recall@k высокий, а precision не проваливается, вместо погони за максимумом одной метрики. Если recall@5 равен 0.9 при чанках 800 токенов и 0.6 при 300, это еще не приговор мелким чанкам: проверьте, как изменился размер входа и качество финальных ответов.
Как сравнивать конфигурации корректно
Меняйте один параметр за раз. Если одновременно меняете chunk size и overlap, результат нельзя приписать ни одному из них.
Зафиксируйте перед прогоном: модель эмбеддингов, версию и способ нормализации текста, метрику расстояния, значение k, наличие и версию reranker, промпт генерации, версию документации. Любое изменение из этого списка ломает сравнимость прогонов.
Порядок прогонов: базовая конфигурация, затем 2-3 варианта чанкинга. Пример набора: 400 токенов с overlap 10%, 600 токенов с overlap 10%, 400 токенов с overlap 20%. Такой набор разделяет эффект размера и эффект перекрытия.
Если бюджет позволяет, повторяйте прогон на перемешанном порядке вопросов: кэш эмбеддингов, порядок индексации и rate limit провайдера дают разброс. Выводы по пяти вопросам не значат ничего.
Финальные ответы оценивайте отдельно от метрик поиска: вручную по шкале или через LLM-as-judge. У судьи-модели есть свои ограничения, она склонна одобрять длинные и уверенные ответы и хуже ловит фактические ошибки в цифрах. Для критичных вопросов ручная проверка обязательна. Как устроить такой цикл оценки, разобрано в материале про метрики качества и борьбу с галлюцинациями в RAG-системах.
Типичные ошибки при подборе chunk size и overlap
Семь ошибок, которые встречаются чаще всего, и признак, по которому каждую видно:
- Копирование чужого chunk size без проверки. Признак: настройки взяты из статьи или чужого репозитория, своих замеров нет.
- Огромный overlap «на всякий случай». Признак: в top-k несколько почти одинаковых фрагментов, размер индекса вырос в разы, recall не изменился.
- Нулевой overlap там, где смысл теряется на границе. Признак: ответы обрываются на середине условия или шага.
- Разбиение по символам без учета структуры. Признак: в чанках обрывки таблиц, половины функций, пункты списка без вводной строки.
- Путаница токенов и символов. Признак: в конфиге стоит chunk_size=1000 с мыслью о символах, а splitter считает токены, и фактический размер отличается в разы.
- Отсутствие eval-набора. Признак: настройки меняются после двух неудачных ответов в чате.
- Попытка вылечить chunk size проблему, которая в другом месте. Признак: правильный чанк есть в индексе, но никогда не попадает в top-k, либо его нет вообще из-за ошибок парсинга.
Overlap: когда он помогает, а когда вредит
Overlap помогает, когда смысл фразы или шага инструкции распределен между соседними чанками: условие в конце одного, действие в начале следующего. Здесь повтор 50-100 токенов сохраняет связку.
Overlap вредит в трех случаях. Чанки атомарны (функция, строка таблицы, пара вопрос-ответ), и перекрытие создает дубликаты. Overlap сравним с chunk size, например 400 при чанке 500: почти весь текст дублируется, индекс растет вдвое, а поиск получает пары одинаковых векторов. Top-k заполняется копиями одного фрагмента, и модель видит один и тот же текст пять раз вместо пяти разных источников.
Практическое правило: начинайте с 10-15% от chunk size и уменьшайте, если в выдаче появились дубли. Ноль - нормальное значение для структурированных блоков.
Когда проблема не в chunk size, а в архитектуре retrieval
Сигналы, что тюнинг параметров не поможет:
- Правильный чанк есть в индексе, но не попадает в top-k ни при одном размере. Причина чаще в модели эмбеддингов или в несовпадении языка запроса и документа.
- Пользователи используют слова, которых нет в документации: синонимы, аббревиатуры, внутренний жаргон. Помогают hybrid search на базе BM25 и вектора, расширение запроса, а не размер чанка.
- В выдаче много похожих чанков, все релевантные, но ответа в них нет. Смотреть в сторону reranking и parent-child retrieval.
- Вопрос требует сведения фактов из разных разделов. Здесь работают многошаговый поиск и декомпозиция запроса: например, AgenticRetrieveStream разбивает сложные запросы на подзадачи и итеративно уточняет поиск.
- Часть документации не разобрана: PDF-таблицы превратились в шум на этапе парсинга. Чанкинг бессилен, если исходный текст испорчен.
Практический чек-лист настройки чанкинга для своей документации
- Определите тип документации и ожидаемые типы вопросов. От этого зависят стартовые ориентиры.
- Выберите стартовые chunk size и overlap: 256-512 токенов и 10-15% для текста, 512-800 токенов для плотных инструкций, 128-256 токенов для FAQ.
- Пересчитайте ориентиры в токенах вашей эмбеддинг-модели на 10-20 реальных фрагментах корпуса.
- Настройте правила границ: Markdown-aware splitting по заголовкам, код и таблицы как атомарные блоки, списки вместе с вводной строкой.
- Сохраните метаданные: путь по заголовкам, тип блока, имя файла, версию документа.
- Соберите eval-набор из 30-50 реальных вопросов с указанием правильного источника для каждого.
- Прогоните 2-3 конфигурации при зафиксированных модели эмбеддингов, k, промпте и версии документации.
- Сравните recall@k, precision@k, MRR и hit rate, затем качество финальных ответов.
- Зафиксируйте выбранную конфигурацию в конфиге репозитория вместе с датой и версией корпуса.
- Пересматривайте настройки при крупном обновлении документации, смене модели эмбеддингов или изменении типов вопросов пользователей.
Chunking - итеративный процесс. Конфигурация, которая работает на документации из 300 файлов, развалится после подключения PDF-архива с таблицами. Держите eval-набор и правила разбиения в одном репозитории с пайплайном, тогда следующая проверка займет час, а не неделю.