Ваш RAG врет. И виноваты не эмбеддинги, а ваши чанки
Вы настраиваете RAG. Векторная база — вроде бы pgvector. Модель эмбеддингов — возможно, последний text-embedding-3-large. Ретривер — гибридный поиск. А ответы все равно получаются оторванными от контекста или просто бредовыми. Знакомая история?
В 95% случаев проблема не в сложности архитектуры, а в фундаменте — в том, как вы режете документы. Стандартный подход "512 токенов с перекрытием" умер примерно в 2024 году. Сейчас это гарантированный способ получить хрупкую, нестабильную систему, которая будет падать на сложных запросах.
Простой тест: возьмите техническую документацию, нарежьте ее чанками по 500 символов. Задайте вопрос о взаимосвязи двух концепций из разных разделов. Получите ответ? Скорее всего, нет. Контекст разорван.
Три смертных греха чанкинга (и почему они убивают retrieval)
Проблема начинается с трех базовых заблуждений, которые кочуют из туториала в туториал.
- Грех 1: Игнорирование иерархии. Документ — это не плоский текст. Это структура: разделы, подразделы, абзацы, списки. Разрезая все под одну гребенку, вы теряете логические связи. Запрос "как настроить параметр X в разделе 3.2?" не найдет ответ, если чанк захватил половину 3.1 и половину 3.2.
- Грех 2: Слепая вера в семантику. Современные эмбеддинги, особенно семейства embedding-3, действительно мощные. Но они не всесильны. Если в одном чанке оказались "инструкция по установке" и "лицензионное соглашение", семантический поиск может отдать лицензию на запрос про установку. Потому что статистически слова "программа", "использование", "файлы" встречаются в обоих.
- Грех 3: Отсутствие обратной связи. Вы нарезали документы, загрузили в векторную базу и... забыли. Но как оценить, хороши ли ваши чанки? Если 30% запросов возвращают нерелевантные фрагменты, проблема не в ранжировании, а в изначальном разбиении.
Именно поэтому в полном руководстве по RAG мы говорим: чанкинг — это не препроцессинг, это полноценный этап дизайна системы.
От плоского разреза к объемной структуре: иерархический подход
Решение первого греха — признать, что документ имеет уровни. Ваша задача — сохранить их при индексации.
1 Извлекаем структуру документа
Не начинайте с regex по переносам строк. Используйте парсеры, которые понимают семантику разметки. Для HTML/XML — BeautifulSoup или lxml с учетом тегов заголовков (<h1>-<h6>). Для PDF — утилиты вроде pdfplumber или коммерческие решения, которые сохраняют координаты текста и стили. Для Markdown — ищите последовательности #.
import pdfplumber
def extract_hierarchy_from_pdf(pdf_path):
hierarchy = []
with pdfplumber.open(pdf_path) as pdf:
for page in pdf.pages:
# Извлечение текста с попыткой сохранить структуру
words = page.extract_words(keep_blank_chars=False, use_text_flow=True)
# Анализ размера шрифта и координат для определения заголовков
# (упрощенный пример)
for word in words:
if word['size'] > 12: # Условный порог для заголовка
hierarchy.append({'type': 'heading', 'text': word['text']})
else:
hierarchy.append({'type': 'content', 'text': word['text']})
return hierarchy
Этот код — только иллюстрация. В реальности используйте готовые библиотеки вроде unstructured.io, которые на 2026 год стали стандартом для извлечения структурированных данных из документов.
2 Строим дерево чанков
После извлечения структуры создайте дерево, где каждый узел — потенциальный чанк. Корневой узел — весь документ. Дети — основные разделы. Внуки — подразделы. Листья — абзацы или группы предложений.
Этот подход — основа иерархического RAG. При запросе система сначала ищет в мелких чанках. Если релевантность низкая или контекста мало, она "расширяет" поиск, поднимаясь к родительскому чанку (большему фрагменту). Так вы балансируете между точностью и полнотой. Подробнее о масштабировании такой архитектуры читайте в гайде по масштабированию RAG.
Режем по живому: семантическое chunking против статического
Иерархия решает проблему структуры. Но что делать с содержанием? Два предложения могут быть грамматически связаны, но семантически разными. Вот где в игру вступает семантическое chunking.
Идея проста: резать документ не по фиксированному количеству токенов, а по границам смысловых единиц. Для этого нужна модель, понимающая связность текста.
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# Используем современную модель для эмбеддингов предложений
# Актуально на 2026: модель 'sentence-transformers/all-mpnet-base-v3' все еще сильна,
# но стоит проверить обновления в репозитории
model = SentenceTransformer('sentence-transformers/all-mpnet-base-v3')
def semantic_chunking(text, threshold=0.75, window_size=3):
sentences = text.split('. ') # Упрощенное разделение, лучше использовать nltk
if len(sentences) <= window_size:
return [text]
chunks = []
current_chunk = []
# Сравниваем соседние предложения в скользящем окне
for i in range(len(sentences) - window_size + 1):
window = sentences[i:i+window_size]
embeds = model.encode(window)
# Считаем косинусное сходство между первым и последним предложением в окне
similarity = cosine_similarity([embeds[0]], [embeds[-1]])[0][0]
if similarity < threshold:
# Если связность низкая, это граница чанка
if current_chunk:
chunks.append('. '.join(current_chunk))
current_chunk = []
if not current_chunk:
current_chunk = window.tolist() if hasattr(window, 'tolist') else list(window)
else:
current_chunk.append(window[-1])
if current_chunk:
chunks.append('. '.join(current_chunk))
return chunks
Алгоритм скользящего окна проверяет, насколько предложения связаны по смыслу. Если эмбеддинг первого и последнего предложения в окне сильно отличаются (ниже порога) — здесь граница чанка.
Важно! Не используйте одну и ту же модель для chunking и для основного поиска. Для chunking нужна быстрая, легкая модель, возможно, специально обученная на определении границ тем (topic segmentation). Для поиска — самая мощная из доступных. Разделяйте обязанности.
Комбинируя иерархический и семантический подход, вы получаете адаптивные чанки, которые соответствуют логике документа. Но как понять, что вы все сделали правильно? Нужна оценка.
Оценка чанков: метрики, которые покажут правду
Если вы не измеряете, вы гадаете. Стандартные метрики RAG (Hit Rate, MRR) оценивают систему в целом. Нам же нужны метрики конкретно для чанков.
| Метрика | Что измеряет | Идеальное значение |
|---|---|---|
| Chunk Utilization Rate | Процент чанков, которые хотя бы раз попали в топ-N ответов ретривера за определенный период. | Высокое (80%+), но не 100%. 100% значит, что чанки слишком мелкие или общие. |
| Query Coverage per Chunk | Среднее количество уникальных запросов, на которые отвечает один чанк. | Низкое (1-3). Если один чанк отвечает на 20 разных запросов, он, вероятно, слишком общий и содержит шум. |
| Context Cohesion Score | Семантическая связность внутри чанка (среднее косинусное сходство между предложениями). | Высокое (>0.7 для embedding-3 моделей). |
| Boundary Precision | Насколько границы чанков совпадают с границами смысловых блоков, размеченных человеком. | Зависит от домена, стремитесь к >0.8. |
Как собирать эти данные? Внедрите логирование. Каждый раз, когда ретривер возвращает чанк, записывайте: id чанка, запрос, позицию в ранжировании, оценку релевантности. Раз в неделю анализируйте логи.
# Пример структуры логирования
log_entry = {
'query': 'Как настроить кэширование в API?',
'chunk_id': 'doc_123_chunk_5',
'chunk_text_snippet': '...',
'retriever_score': 0.876,
'retrieved_at_position': 2,
'timestamp': '2026-04-16T10:30:00Z'
}
На основе этих логов вы можете вычислить метрики и выявить проблемные чанки — те, что никогда не находятся или, наоборот, находятся на все запросы (мусорные чанки). О том, как автоматизировать оценку и улучшение, читайте в статье про автоматическое чанкование.
Практический план: внедряем продвинутый chunking за 5 шагов
Теория — это хорошо, но без плана действия вы не сдвинетесь с места. Вот что нужно сделать на следующей неделе.
- Аудит текущих чанков. Возьмите 100 случайных запросов из логов. Посмотрите, какие чанки возвращает ретривер. Выпишите те, где ответ неполный или содержит лишнее. Это ваш болевой points.
- Внедрите парсер структуры. Выберите инструмент для вашего основного формата документов (PDF, HTML, DOCX). Начните с библиотеки unstructured — она поддерживает большинство форматов и возвращает элементы с типом (Title, NarrativeText, ListItem). Это готовые кандидаты в чанки.
- Протестируйте семантическое разделение. Возьмите 10 длинных документов. Примените к ним семантический алгоритм chunking. Сравните результат с фиксированным чанкингом. Визуально оцените, насколько границы соответствуют смене тем.
- Настройте логирование чанков. Модифицируйте код ретривера, чтобы он записывал данные, необходимые для вычисления метрик из таблицы выше. Не храните полные тексты в логах, только id и сниппеты.
- Запустите A/B тест. Разделите трафик запросов. 50% — на старую систему индексации, 50% — на новую, с иерархическими и семантическими чанками. Сравните конечную метрику — удовлетворенность ответами (можно через feedback пользователей или оценку модели).
Этот процесс не одноразовый. Чанкинг — это параметр, который нужно периодически пересматривать, особенно когда меняется тип документов или характер запросов. Как часть общей стратегии, рекомендую ознакомиться с roadmap по RAG на 2026 год, где chunking занимает центральное место на этапе оптимизации индекса.
Ошибки, которые все равно совершат (и как их избежать)
- Ошибка: Смешивать чанки разного уровня в один индекс. Если у вас есть мелкие чанки (абзацы) и крупные (разделы), их эмбеддинги будут в разном масштабе. Поиск по такому индексу даст хаос. Решение: Индексируйте только листья (мелкие чанки). Крупные храните как метаданные или в отдельном индексе для "расширения" контекста.
- Ошибка: Оптимизировать chunking под один тип запросов. Вы настраиваете чанки так, чтобы идеально отвечать на фактологические вопросы. А пользователи начинают задавать сравнительные вопросы, требующие информации из двух разных чанков. Решение: Используйте технику sentence window retrieval или auto-merging retrieval. При поиске находите мелкий чанк, но в контекст для LLM добавляете его соседей или родительский чанк.
- Ошибка: Забывать про таблицы и код. Текст — это просто текст. Но что делать с таблицей, которую вы разрезали пополам? Или с блоком кода, разорванным на середине функции? Решение: Обрабатывайте таблицы и блоки кода как atomic units. Не разрезайте их. Используйте специальные парсеры (например, tabula для таблиц) и сохраняйте их как отдельные чанки с типом "table" или "code". При запросе, связанном с данными, ищите чанки этого типа.
Эти ошибки не гипотетические. Они всплывают в каждом втором production проекте. И часто их корень — в желании сделать "проще и быстрее", игнорируя природу данных. Помните, что RAG — это не про векторный поиск, это про информационный дизайн. Чанкинг — его основа.
Что в итоге? Chunking как непрерывный процесс
Не ждите волшебной кнопки "оптимальный размер чанка". Ее нет. Есть процесс: извлеки структуру, разрежь по смыслу, измерь качество, настрой, повтори. Инструменты будут меняться (в 2026 уже появились модели, которые сами предлагают границы чанков, например, специализированные версии Llama 3.2 для text segmentation). Но принцип останется: чанк должен быть семантически цельным и структурно осмысленным.
Следующий шаг — связать качество чанков с агентными архитектурами, где RAG становится частью контрольного цикла. Но это уже тема для отдельного разговора.