Chunking

Чанкинг — разбиение документов на фрагменты (чанки) перед индексацией в RAG. От стратегии нарезки напрямую зависит качество поиска: слишком крупные чанки → шум, слишком мелкие → потеря контекста. Это первая точка, где «ломается» RAG.

Суть

LLM нельзя скормить весь корпус целиком — его режут на куски, каждый кусок → вектор (Embeddings) → индекс. Вопрос «как резать» нетривиален: граница чанка определяет, окажется ли нужный факт целиком в одном фрагменте или расколется пополам.

Зачем это нужно

Минимальный baseline («recursive chunking по знакам препинания») работает, но теряет смысл на сложных документах: чанк «Его цена 500 руб» не знает, что «его» = товар X из соседнего чанка (проблема анафор). Продвинутые техники чинят именно это.

Как работает (стратегии от простого к сложному)

  • Fixed / Recursive — нарезка по фиксированному размеру или рекурсивно по разделителям (абзацы → предложения → знаки препинания). Baseline 2026.
  • Semantic Chunking — границы по смысловым переходам (улучшает MRR, см. RAG Metrics).
  • Adaptive Chunking — размер чанка подстраивается под структуру документа.
  • Parent Document Retrieval (small-to-large): ищем по мелким чанкам (точность retrieval), а в LLM отдаём родительский крупный документ (полнота контекста). Context Precision +15-25%.
  • Late Chunking (Jina AI, 2024): сначала прогоняем весь документ через embedding-модель (self-attention видит всё), потом режем на чанки со span pooling — каждый токен уже «знает» глобальный контекст. Без доп. LLM-вызовов, +10-12% retrieval accuracy.
  • Contextual Retrieval (Anthropic, 2024): LLM дописывает к каждому чанку краткий контекст документа перед эмбеддингом. Решает анафоры, лучшая когерентность, но дорого (LLM-вызов на каждый чанк) — «для VIP-документов».
  • Production-нюансы (общие, не RU-специфичные): «наивный» fixed 512/128 — не универсальный дефолт (рвёт предложения, отделяет вопрос от ответа). Practical: recursive (абзацы→предложения), semantic (граница там, где косинус между соседними предложениями падает ниже порога), structure-aware (AST для кода, пункты для юр.), parent-heading в каждый child-чанк. Оптимальный размер/overlap под русские документы — отдельный targeted research (см. fertility-прокси в Tokenization).

При сохранении чанков держим метаданные: chunk_id, doc_id, title, url/путь (см. конвейер в RAG).

Пример

Parent Document Retrieval — два уровня чанкинга: поиск по мелким чанкам (точность), а в LLM отдаём крупный родительский документ (полный контекст). Метаданные (doc_id, title) держим на каждом child-чанке.

child_splitter  = RecursiveCharacterTextSplitter(chunk_size=150, chunk_overlap=20)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=50)

parent_store = {}                       # doc_id -> полный текст (родитель)
for doc in DOCUMENTS:
    parent_store[doc["id"]] = doc["content"]
    for chunk in child_splitter.split_text(doc["content"]):
        child_chunks.append(chunk)                                  # child: точный поиск
        child_metadatas.append({"doc_id": doc["id"], "title": doc["title"]})

chunk_size здесь считается в символах, а не в токенах — RecursiveCharacterTextSplitter по умолчанию меряет длину через len(). Поэтому 150 и 600 из примера нельзя сопоставлять с ориентирами таблицы ниже, которые даны в токенах: на русском 150 символов — это порядка сорока токенов, то есть в разы меньше, чем рекомендованные для child-чанка 200–400. Чтобы числа означали токены, сплиттер создают классметодом с явным энкодером — RecursiveCharacterTextSplitter.from_tiktoken_encoder(model_name="gpt-4", chunk_size=..., chunk_overlap=...); он рекурсивно дробит куски, пока каждый не уложится в лимит. Оговорка для русского корпуса: tiktoken считает по токенизатору OpenAI, а размер надо мерить по токенизатору своей embedding-модели — для неё берут from_huggingface_tokenizer. Расхождение молчаливое: код отработает и на символах, просто чанки окажутся мельче задуманного, а качество поиска просядет без единого сообщения об ошибке.

Ориентиры под русскоязычные документы

Отдельного «русского» правила нарезки нет — язык влияет на нарезку косвенно, через токенизацию: на русском один и тот же смысл занимает заметно больше токенов, чем на английском (Tokenization), поэтому чанк, отмеренный в символах, на русском окажется в токенах больше, чем ожидалось. Мерить размер надо в токенах целевой embedding-модели, а не в символах.

Практические ориентиры, встречающиеся в русскоязычных разборах (2026):

Схема Размер Перекрытие
Плоская нарезка 500-1500 токенов около четверти от лимита при фиксированной нарезке
Семантическая по границам смысла 1-2 предложения в следующий чанк
Иерархическая (small-to-big) child 200-400 токенов для поиска, parent 1500-2000 для передачи в модель

Иерархическая схема здесь интереснее прочих именно на русском: искать удобно по коротким фрагментам, а в модель отдавать крупный кусок, потому что обрезанный по границе абзаца русский текст чаще теряет связность (согласование, отсылки местоимениями) сильнее, чем английский.

⚠️ Числа выше — ориентиры из практических разборов, а не измеренные оптимумы. Оптимальный размер зависит от embedding-модели, плотности знания в документе, его структуры и типа запросов, поэтому проверяется на своём корпусе тем же eval-набором, что и всё остальное (RAG Metrics).

Late Chunking или Contextual Retrieval

Оба приёма лечат одну болезнь — чанк, вырванный из контекста и потерявший смысл, — но платят за это по-разному.

Late Chunking дешёв: документ прогоняется через модель целиком, эмбеддинги чанков берутся из общего прохода, поэтому каждый несёт след соседей. Никакой дополнительной генерации не происходит. Ограничение прямое: документ должен помещаться в контекст embedding-модели, и приём не добавляет информации, которой в документе нет.

Contextual Retrieval дороже на порядок, потому что для каждого чанка модель пишет короткое пояснение о его месте в документе, — то есть на индексацию тратится генерация, умноженная на число чанков. Зато он добавляет то, чего в тексте чанка нет вовсе: к какому разделу относится, о каком объекте речь, когда местоимения и сокращения раскрываются только по документу.

Отсюда критерий: если потерянный контекст восстанавливается из соседних предложений — хватит Late Chunking; если он восстанавливается только из структуры документа — нужен Contextual Retrieval. Практический признак второго случая: таблицы, нумерованные разделы регламентов, документы, где половина чанков начинается с «в этом случае» и «он».

Связано с

  • RAG — чанкинг = первый этап конвейера ingestion
  • Embeddings — каждый чанк превращается в вектор
  • Reranking — что делать с найденными чанками дальше
  • RAG Metrics — как измерить, что чанкинг улучшил поиск