Разбиение текста для эмбеддингов и RAG — деление по токенам

Разбейте длинный текст на фрагменты в токенах с перекрытием для эмбеддингов и RAG. Режет по границам предложений и абзацев, никогда посреди слова, и показывает каждый разрез.

Разбиение текста на чанки для эмбеддингов: где ляжет разрез, то и найдётся

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

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

Рекурсивное разбиение по границам

Наивный подход — резать каждые N токенов. Быстро и неверно: граница токена никак не связана со смысловой — вы разрежете посреди предложения, иногда посреди слова, и полученный вектор описывает фрагмент, которого никто не писал.

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

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

Зачем на самом деле нужно перекрытие

Перекрытие повторяет хвост одного чанка в начале следующего. Оно существует потому, что факт и то, к чему он относится, часто оказываются по разные стороны границы. «Миграция шла всю ночь. Она упала на третьей таблице.» Разрежьте между этими предложениями — и во втором чанке будет падение без подлежащего, а в первом миграция без исхода. Ни то ни другое толком не находится. С перекрытием второй чанк уносит с собой антецедент.

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

Одна защита, о которой стоит знать: перекрытие, равное размеру чанка или большее, означало бы, что каждый чанк начинается со всего, чем кончился предыдущий, и процесс не сдвигается с места. Такое значение обрезается, а не подвешивает вкладку.

Как выбрать размер

Размер чанка — компромисс между точностью и контекстом, и универсально правильного ответа нет:

  • Мелкие чанки, около 256 токенов, дают резкие совпадения. Вектор по двум-трём предложениям определяется тем, что в этих предложениях сказано. Хорошо для вопросов и ответов по плотному справочному материалу. Риск — чанки, которые хорошо совпали, но не содержат окружающего контекста, нужного, чтобы действительно ответить.
  • Средние чанки, около 512, — общепринятое значение по умолчанию и то, на чём большинству моделей эмбеддингов лучше всего. Примерно один увесистый абзац.
  • Крупные чанки, от 1024 и выше, несут больше контекста на попадание, но размывают вектор. Чанк, покрывающий четыре темы, оказывается в среднем между всеми четырьмя и ни с одной не совпадает сильно.
  • Очень крупные чанки, в несколько тысяч токенов, перестают быть единицей поиска и становятся единицей постраничного прохода — чтобы прогнать длинный документ через модель последовательными заходами, а не искать по нему.

Четыре стратегии разбиения

Умное разбиение — та самая рекурсивная лестница, описанная выше; для большинства текстов подходит именно оно. Абзацы набивает чанк целыми абзацами и никогда не делит абзац: подходит тексту, где абзац и так является единицей мысли и где неровные размеры чанков лучше разорванного. Заголовки Markdown дают по чанку на раздел — естественный выбор для документации, где заголовок ровно называет то, что под ним. Строки набивает чанк целыми строками: для логов, выгрузок CSV и списков, где каждая строка самостоятельна.

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

Подсчёт и что означает это число

Токены считаются тем же токенизатором, которым сайт меряет репозитории, — из семейства, которое используют модели OpenAI. У Claude и Gemini цифры немного другие: разные словари режут один и тот же текст по-разному, — но они идут достаточно близко, чтобы примерить чанк к лимиту. Если бюджет впритык, оставьте запас, а не считайте число точным.

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

Вывод, который можно чем-то скормить

Три варианта скачивания. Простой текст с необязательными заголовками --- Chunk n/N --- — читать и проверять. JSON массивом объектов с индексом, числом токенов и текстом — для скрипта, который проглотит всё разом. JSONL по объекту на строку — это то, чего ждут потоковая загрузка и большинство батчевых эндпоинтов эмбеддингов, и то, что нужно, когда корпус слишком велик, чтобы держать его в памяти одним разобранным массивом.

Это естественно стыкуется с остальным сайтом: сначала превратите репозиторий, PDF, веб-страницу или расшифровку в текст, затем нарежьте результат здесь. Ни на одном из шагов ничего не загружается на сервер.