Чанкинг: как правильно разбить документы для RAG базы знаний
- Чанкинг
- RAG
- Документы
Чанкинг (chunking) - это процесс разбиения документов на фрагменты оптимального размера для загрузки в RAG-систему. Качество чанкинга напрямую определяет качество ответов ИИ: слишком мелкие фрагменты теряют контекст, слишком крупные размывают релевантность, а неправильные границы разрезают мысль пополам. В этой статье мы разберём все основные стратегии чанкинга - от простого разбиения по символам до семантического анализа границ - с практическими рекомендациями по выбору подхода для разных типов документов и реальными примерами из проектов Промолитики.
Когда вы загружаете документы в RAG-систему, они не используются целиком. Языковая модель имеет ограниченное контекстное окно - даже самые современные модели обрабатывают от 8 000 до 200 000 токенов за раз. Отправить модели все 5 000 документов вашей компании невозможно. Поэтому из всех документов нужно выбрать самые релевантные фрагменты - и качество этого выбора определяется тем, как документы были разбиты.
Представьте базу знаний компании с 500 статьями. Клиент спрашивает: «Как настроить интеграцию с amoCRM?» RAG-система должна найти именно тот фрагмент, где описана настройка amoCRM, а не всю статью на 20 страниц об интеграциях вообще. Если фрагмент слишком большой - модель получит много нерелевантной информации и может «утонуть» в ней. Если слишком маленький - фрагмент может содержать обрывок инструкции без начала и конца.
Чанкинг - это искусство нарезать документы так, чтобы каждый фрагмент был самодостаточным, содержал законченную мысль и имел достаточно контекста для понимания. Это звучит просто, но на практике является одной из самых сложных задач при построении RAG-систем.
Самый простой подход - разрезать текст на фрагменты фиксированной длины. Например, каждые 500 токенов (примерно 375 слов) начинается новый фрагмент. Это можно реализовать за 10 строк кода, и для простых случаев подход работает удовлетворительно.
Текст документа разбивается на части по заданному количеству символов или токенов. Обычно используется размер от 256 до 1024 токенов. Каждый фрагмент получает свой порядковый номер и метаданные (название документа, автор, дата).
Фиксированное разбиение подходит как начальная точка для прототипирования или для однородных текстов (художественная литература, стенограммы), где нет чёткой структуры. Для бизнес-документов с разделами и заголовками - это слабый подход.
Следующий уровень - использовать естественные границы текста. Абзац - это минимальная единица, которая обычно содержит одну законченную мысль. Разбиение по абзацам сохраняет эту целостность.
Текст разделяется по двойным переносам строк (пустым строкам). Каждый абзац становится отдельным фрагментом. Если абзац слишком короткий (менее 50 символов), он объединяется со следующим. Если слишком длинный (более 1500 символов), он разбивается по предложениям.
Разбиение по абзацам хорошо работает для блогов, статей, FAQ и других текстов с логичной абзацной структурой. Для технической документации с глубокой иерархией заголовков нужен более продвинутый подход.
Самый эффективный подход для структурированных документов - использовать заголовки как естественные точки разбиения. Каждый раздел (H2) или подраздел (H3) становится отдельным фрагментом вместе с текстом под ним.
Парсер анализирует структуру документа: находит заголовки разных уровней (H1, H2, H3) и использует их как точки разбиения. Текст между двумя заголовками одного уровня становится одним фрагментом. Заголовок включается в начало фрагмента, обеспечивая контекст. Иерархия заголовков сохраняется: если H3-подраздел находится внутри H2-раздела, заголовок H2 добавляется как метаданные к фрагменту H3.
Документ «Руководство по интеграции» с разделом «Настройка amoCRM» (H2) и подразделами «Авторизация» (H3) и «Маппинг полей» (H3) даст три фрагмента. Каждый фрагмент начинается с заголовка и содержит только текст своего раздела. При этом фрагмент «Авторизация» будет иметь метаданные: «Родительский раздел: Настройка amoCRM, Документ: Руководство по интеграции».
Это наш основной подход в Промолитике для базы знаний, документации и технических руководств. Для документов без заголовков мы используем fallback на абзацное разбиение.
Самый продвинутый подход - определять границы фрагментов автоматически, на основе анализа смысла текста. Семантическое разбиение не зависит от форматирования документа и работает с любым типом текста.
Алгоритм анализирует последовательные предложения и оценивает «смысловую дистанцию» между ними. Для этого используются эмбеддинги: каждое предложение превращается в вектор, и вычисляется косинусное сходство между соседними предложениями. Когда сходство резко падает - значит, тема сменилась, и здесь должна проходить граница фрагмента.
Представьте текст, в котором 10 предложений посвящены настройке email-уведомлений, а затем автор переходит к описанию push-уведомлений. Косинусное сходство между последним предложением про email и первым про push будет значительно ниже, чем между соседними предложениями внутри каждой темы. Эта «яма» в графике сходства - сигнал для разделения.
Семантическое разбиение оправдано для неструктурированных текстов, где другие методы работают плохо: транскрипты звонков, email-переписка, логи чатов. Для документов с хорошей структурой (markdown, HTML) heading-based подход проще и не уступает по качеству.
Один из ключевых параметров чанкинга - размер фрагмента. Слишком маленький - и контекст теряется. Слишком большой - и релевантность размывается. Рассмотрим, какие компромиссы возникают при выборе размера.
По нашему опыту в Промолитике, оптимальный размер фрагмента для большинства бизнес-задач - от 256 до 512 токенов (примерно 200-400 слов). Это достаточно для сохранения контекста и достаточно компактно для точного поиска.
Но универсального ответа нет. Для товарных каталогов фрагмент = одна карточка товара (может быть 50 токенов). Для юридических документов, где смысл предложения зависит от контекста всего раздела, лучше работают фрагменты по 800-1000 токенов. Для FAQ каждая пара «вопрос-ответ» - это естественный фрагмент, независимо от длины.
Главное правило: размер фрагмента должен определяться типом контента, а не техническими ограничениями. Начните с рекомендованного диапазона и проверьте на реальных запросах - это единственный надёжный способ подобрать оптимум.
Перекрытие - приём, при котором конец одного фрагмента повторяется в начале следующего. Если фрагмент = 500 токенов с перекрытием 100 токенов, то последние 100 токенов фрагмента N становятся первыми 100 токенами фрагмента N+1.
Перекрытие решает проблему «разорванного контекста». Если мысль начинается в конце фрагмента N и продолжается в начале фрагмента N+1, перекрытие гарантирует, что хотя бы в одном из них мысль будет представлена полностью (или почти полностью).
Практический пример. В документе написано: «Для корпоративных клиентов действует специальный тариф. Стоимость подписки - 50 000 руб./мес при оплате за год.» Если граница фрагмента проходит между этими предложениями, без перекрытия первый фрагмент скажет про «специальный тариф» без цены, а второй - про цену без пояснения, что она для корпоративных клиентов. С перекрытием в 100 токенов оба предложения попадут хотя бы в один фрагмент.
Типичные значения: от 10% до 25% от размера фрагмента. При фрагменте 512 токенов перекрытие составит 50-128 токенов. Мы чаще используем 15-20% - это хороший баланс между покрытием контекста и объёмом хранилища.
Больший overlap означает больше фрагментов (при том же объёме текста) и, соответственно, больший расход на хранение эмбеддингов и вычисления при поиске. Для базы из 10 000 фрагментов это несущественно. Для базы из 10 000 000 - уже заметно.
Важно: при использовании heading-based разбиения перекрытие обычно не нужно, потому что заголовок уже обеспечивает контекст, и каждый раздел - самодостаточная единица. Перекрытие наиболее полезно при фиксированном и абзацном разбиении.
Фрагмент без метаданных - как страница книги без номера и названия главы. Технически текст есть, но использовать его эффективно невозможно. Метаданные обогащают фрагмент контекстом и позволяют фильтровать, ранжировать и цитировать результаты поиска.
В Промолитике мы храним метаданные в виде JSON-объекта, привязанного к каждому вектору в Turbopuffer или pgvector. При поиске метаданные используются для фильтрации (показать только документы за последний год) и для обогащения ответа (указать источник и дату).
Универсального подхода к чанкингу не существует. Разные типы документов требуют разных стратегий. Вот наши рекомендации, основанные на десятках проектов.
Идеальные форматы для чанкинга, потому что структура размечена явно. Используйте heading-based разбиение: каждый заголовок ## (H2) начинает новый фрагмент. Заголовки ### (H3) могут либо начинать отдельные фрагменты (если разделы длинные), либо оставаться частью родительского H2-фрагмента (если разделы короткие). Заголовок верхнего уровня # (H1) добавляется как метаданные ко всем фрагментам.
Fallback: если раздел под H2 слишком длинный (более 1500 токенов) и не содержит подзаголовков, разбейте его по абзацам, сохранив H2 в начале каждого фрагмента.
PDF-документы - самый сложный случай. Если PDF текстовый и имеет закладки (bookmarks), используйте их как аналог заголовков. Если закладок нет, определяйте заголовки по форматированию: больший шрифт, жирный текст, особый стиль. Для сканированных PDF после OCR применяйте абзацное разбиение с перекрытием.
Отдельная проблема - разбиение по страницам. Некоторые системы по умолчанию создают один фрагмент на страницу. Это плохой подход: мысль автора не заканчивается на границе страницы. Мы рекомендуем игнорировать постраничное деление и работать с текстом как с непрерывным потоком.
Для email-архивов единица чанкинга - одно письмо (после очистки от подписей и цитирования). Если письмо длинное, его можно дополнительно разбить по абзацам. Метаданные обязательно включают: тему письма, отправителя, дату, ID цепочки (для связывания писем одного треда).
Важный нюанс: цепочку писем (thread) можно обрабатывать двумя способами. Первый - каждое письмо отдельно, с ID цепочки в метаданных. Второй - вся цепочка как один документ. Мы используем первый подход для больших архивов (когда цепочки могут содержать 50+ писем) и второй - для коротких переписок (3-5 писем).
Каждый тикет - отдельный фрагмент. Если тикет содержит много сообщений (10+), мы используем реструктуризацию: языковая модель создаёт из всей переписки один структурированный фрагмент с полями «Проблема», «Решение», «Результат». Этот подход значительно повышает качество поиска по сравнению с загрузкой сырых логов.
Каталог товаров или объектов недвижимости - самый простой случай. Каждая карточка товара = один фрагмент. Все характеристики преобразуются в текстовое описание. Метаданные включают числовые параметры (цена, площадь, вес) для точной фильтрации, которую семантический поиск не может обеспечить.
Каждая пара «вопрос-ответ» - один фрагмент. Вопрос включается в текст фрагмента, потому что он содержит ценную семантическую информацию о теме. Если ответ очень длинный (более 500 токенов), его можно разбить, но вопрос должен повторяться в начале каждого фрагмента.
В проектах Промолитики мы используем комбинированный подход, который мы называем «heading-first». Он работает в три этапа.
Этап 1: анализ структуры. Парсер определяет тип документа и наличие заголовков. Для markdown и HTML это тривиально. Для PDF используется эвристика на основе размера шрифта и форматирования. Для текстовых файлов - регулярные выражения для обнаружения паттернов заголовков.
Этап 2: первичное разбиение. Если документ имеет заголовки - разбиение по H2/H3. Если нет - по абзацам. В обоих случаях фрагмент обогащается метаданными: название документа, иерархия заголовков, дата, автор.
Этап 3: нормализация размеров. Фрагменты проверяются на размер. Слишком короткие (менее 100 токенов) объединяются со следующими. Слишком длинные (более 1000 токенов) дополнительно разбиваются по абзацам с сохранением заголовка в начале. Перекрытие добавляется только для абзацного разбиения (50-100 токенов).
Этот подход мы применяем в боте для застройщика (база знаний из статей о жилых комплексах), в системе поиска по email-архиву (после реструктуризации писем) и во внутренних проектах (документация, регламенты). Он даёт стабильно хорошие результаты для разных типов контента и не требует сложной настройки.
За время работы с десятками проектов мы собрали список самых частых ошибок, которые снижают качество RAG-системы на этапе чанкинга.
Если вы только начинаете работу с RAG, вот пошаговый план выбора стратегии чанкинга:
Чанкинг - невидимый, но критически важный этап построения RAG-системы. Правильное разбиение документов определяет качество поиска и, как следствие, качество ответов ИИ. Не существует единой «лучшей» стратегии - выбор зависит от типа документов, задачи и объёма данных. Heading-based разбиение лучше всего работает для структурированных документов, абзацное - для текстов без заголовков, семантическое - для неструктурированных потоков. Перекрытие между фрагментами и богатые метаданные повышают качество любой стратегии.
В Промолитике мы используем комбинированный подход «heading-first» с fallback на абзацы, перекрытием 50-100 токенов и обязательными метаданными для каждого фрагмента. Этот подход работает в продакшене для базы знаний застройщика, email-архивов, тикетов поддержки и внутренней документации. Если вам нужна помощь с настройкой чанкинга или всей RAG-системы - свяжитесь с нами для бесплатной консультации.