Чанкинг: режем текст на фрагменты с перекрытием

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

Чанкинг (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 сшивает соседние чанки, чтобы смысл на границе не терялся.
Проверьте себя
1. Зачем в RAG режут документы на чанки, а не эмбеддят целиком?
AЧтобы сэкономить место на диске
BЧтобы вектор был «про одно», а в промпт шёл короткий релевантный кусок
CЧтобы ускорить парсинг
DЧтобы зашифровать текст
2. Что такое overlap (перекрытие) при чанкинге?
AСжатие чанков
BОбщие слова/предложения у соседних чанков, чтобы не терять смысл на границе
CУдаление дубликатов
DШифрование границ
3. Чем плох слишком крупный размер чанка?
AОн всегда точнее
BВектор размывается по нескольким темам, а в промпт идёт много лишнего
CОн не помещается в БД
DОн ускоряет галлюцинации
Поддержать проект