Чанкинг: режем текст на фрагменты с перекрытием
Самый влиятельный шаг индексации: как нарезать текст, чтобы поиск находил нужное.
Чанкинг (chunking) — разбиение документа на небольшие фрагменты (chunks), каждый из которых отдельно эмбеддится и индексируется.
Зачем вообще резать
Эмбеддить документ целиком — плохо: вектор «усреднит» все темы сразу, и поиск станет размытым. К тому же в промпт надо подавать короткий релевантный кусок, а не всю статью. Поэтому текст режут на чанки — каждый про что-то одно.
Стратегии нарезки
- Фиксированный размер — по N слов/символов/токенов. Просто, но рвёт фразы посередине.
- По предложениям/абзацам — режем по границам смысла. Чанки естественнее.
- Рекурсивно — пробуем по абзацам, потом по предложениям, потом по словам, пока не уложимся в размер.
Перекрытие (overlap) — против разрыва смысла
Если резать встык, важная мысль может оказаться разорванной на границе двух чанков, и ни один не будет полным. Решение — перекрытие: соседние чанки делят несколько общих слов/предложений. Так контекст на стыке не теряется.
Чанкинг по словам с overlap (запускаемый)
def chunk_by_words(text, size=10, overlap=3):
words = text.split()
chunks = []
step = size - overlap
start = 0
while start < len(words):
piece = words[start:start + size]
chunks.append(" ".join(piece))
if start + size >= len(words):
break
start += step
return chunks
text = ("RAG соединяет поиск и генерацию. Сначала система находит "
"релевантные фрагменты в базе знаний. Затем эти фрагменты "
"подставляются в промпт. И только потом языковая модель "
"формирует ответ с опорой на найденный контекст.")
chunks = chunk_by_words(text, size=10, overlap=3)
print("Всего чанков:", len(chunks))
for i, c in enumerate(chunks):
print(f"[{i}] {c}")Вывод:
Всего чанков: 4 [0] RAG соединяет поиск и генерацию. Сначала система находит релевантные фрагменты [1] находит релевантные фрагменты в базе знаний. Затем эти фрагменты подставляются [2] эти фрагменты подставляются в промпт. И только потом языковая модель [3] потом языковая модель формирует ответ с опорой на найденный контекст.
Обратите внимание: конец чанка [0] («находит релевантные фрагменты») повторяется в начале [1]. Это и есть overlap в три слова — он сшивает соседние чанки, чтобы мысль на границе не потерялась.
Чанкинг по предложениям (запускаемый)
Часто нарезка по границам предложений даёт более осмысленные чанки. Добавим перекрытие в одно предложение.
import re
def split_sentences(text):
parts = re.split(r"(?<=[.!?])\s+", text.strip())
return [p for p in parts if p]
def chunk_by_sentences(text, max_sentences=2, overlap=1):
sents = split_sentences(text)
chunks = []
step = max_sentences - overlap
i = 0
while i < len(sents):
chunks.append(" ".join(sents[i:i + max_sentences]))
if i + max_sentences >= len(sents):
break
i += step
return chunks
text = ("Векторная база хранит эмбеддинги. Она умеет быстро искать ближайшие. "
"Поиск идёт по близости векторов. Точный перебор медленный на больших данных.")
for i, c in enumerate(chunk_by_sentences(text, 2, 1)):
print(f"[{i}] {c}")Вывод:
[0] Векторная база хранит эмбеддинги. Она умеет быстро искать ближайшие. [1] Она умеет быстро искать ближайшие. Поиск идёт по близости векторов. [2] Поиск идёт по близости векторов. Точный перебор медленный на больших данных.
Как выбрать размер
Слишком крупные чанки → размытый вектор и лишний контекст в промпте. Слишком мелкие → теряется связность, факт распадается. Типичный старт: 200–500 токенов на чанк, overlap 10–20%. Дальше — подбор под ваши данные.
Итог
- Чанкинг режет документ на фрагменты под отдельный эмбеддинг.
- Стратегии: фиксированный размер, по предложениям/абзацам, рекурсивно.
- Overlap сшивает соседние чанки, чтобы смысл на границе не терялся.