Анализ длинных документов в LLM: методы chunking, RAG и сохранения контекста | AiManual
AiManual Logo Ai / Manual.
10 Фев 2026 Гайд

Когда 128К токенов не хватает: как заставить LLM анализировать документы длиннее её памяти

Практическое руководство по обработке длинных документов в LLM. Методы chunking, advanced RAG, иерархическая агрегация и сохранение контекста с примерами кода н

Контекст - это новая валюта. И мы все банкроты

Вы загружаете 300-страничный технический отчет в GPT-4o. Модель с контекстом в 128К токенов должна справиться, верно? На практике она теряет связь между главой 2 и приложением G. Или генерирует ответ, основанный на первых 50 страницах, игнорируя ключевые детали в конце.

Предел контекста у LLM - это горизонт. Кажется, что вот-вот расширят, а ты все равно упираешься. 128К, 200К, 1M токенов - неважно. Всегда найдется документ, который нужно "скормить" целиком. И тогда рождается вопрос: как анализировать то, что не помещается в память модели?

Забудьте про SemanticZip. В нашей предыдущей статье мы разобрали, почему семантическое сжатие документов для LLM обречено на провал. Восстановленный текст - это всегда НОВЫЙ текст, а не точная копия оригинала.

Почему просто нарезать текст на куски - недостаточно

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

Представьте юридический договор. Пункт 3.2 ссылается на определение из пункта 1.1. Если эти пункты попадут в разные чанки, модель не увидит связи. Она будет интерпретировать "Сторона А" из пункта 3.2 как абстрактное понятие, а не конкретного участника, определенного в начале документа.

💡
Ключевая проблема: контекстная слепота. Когда LLM обрабатывает чанк изолированно, она теряет глобальное понимание документа. Это как читать книгу, вырывая случайные страницы.

1 Умный chunking: нарезка с сохранением смысловых границ

Глупый чанкер режет по символам или словам. Умный - по смысловым границам. Разница в точности ответов достигает 40%.

Вот как НЕ надо делать:

# ПЛОХО: наивный чанкинг по символам
def naive_chunk(text, chunk_size=1000):
    return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]

А вот рабочий подход с использованием актуальных на 2026 год библиотек:

# ХОРОШО: семантический чанкинг с сохранением структуры
from langchain_text_splitters import (
    RecursiveCharacterTextSplitter,
    MarkdownHeaderTextSplitter
)

# Для технической документации с заголовками
headers = [("#", "Заголовок 1"), ("##", "Заголовок 2"), ("###", "Заголовок 3")]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers)

# Для обычного текста с рекурсивным разделением по символам
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,  # Критически важно для контекста
    separators=["\n\n", "\n", ". ", " ", ""],  # Иерархия разделителей
    length_function=len,
    is_separator_regex=False
)

Ключевые параметры, которые большинство упускает:

  • chunk_overlap=200: Не жадничайте. 10-20% перекрытия между чанками спасают от разрыва контекста. Предложение, которое начинается в одном чанке и заканчивается в другом, - гарантия ошибки.
  • Иерархия разделителей: Сначала пробуем разбить по двойным переносам строк (абзацы), потом по одинарным, потом по точкам. Это сохраняет логические блоки.
  • Учет структуры документа: Для PDF с четкой структурой используйте специализированные сплиттеры. В нашем гайде по Docling мы разбирали стратегии для 130+ страничных документов.

2 RAG 2.0: не просто поиск, а реконтекстуализация

Базовый RAG, который все знают: векторный поиск + LLM. Проблема в том, что найденные чанки все равно теряют связь с исходным документом.

Advanced RAG 2026 года - это трехэтапный процесс:

  1. Гибридный поиск: Векторная семантика + лексическое совпадение (BM25). Почему это must-have, мы подробно разбирали в полном руководстве по RAG.
  2. Рерайтинг (reranking): Модель-ранкер (например, BGE-reranker-v2.0) переоценивает релевантность найденных чанков конкретно для вашего вопроса.
  3. Контекстуализация: Самый важный и самый часто пропускаемый шаг.

Вот как выглядит контекстуализация в коде:

# Продвинутый RAG с контекстуализацией
from llama_index.core import (
    VectorStoreIndex, 
    ServiceContext,
    StorageContext
)
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.core.postprocessor import SentenceTransformerRerank

# Современная модель эмбеддингов (актуально на 2026)
embed_model = OpenAIEmbedding(
    model="text-embedding-3-large-3072d",  # 3072 размерность вместо старых 1536
    dimensions=1536  # Можно уменьшить для экономии без большой потери качества
)

# Семантический сплиттер вместо простого
node_parser = SemanticSplitterNodeParser(
    buffer_size=1,
    breakpoint_percentile_threshold=95,
    embed_model=embed_model
)

# Рерайтер для улучшения релевантности
rerank = SentenceTransformerRerank(
    model="BAAI/bge-reranker-v2.0",
    top_n=3  # Берем только 3 самых релевантных чанка после переранжирования
)

# Критический момент: добавляем мета-контекст к каждому чанку
def enrich_chunk_with_context(chunk, document_metadata, surrounding_chunks):
    """Добавляем к чанку информацию о его месте в документе"""
    context_info = f"""
Документ: {document_metadata.get('title', 'Без названия')}
Раздел: {document_metadata.get('section', 'Основной текст')}
Предыдущий контекст: {surrounding_chunks['previous'][:200] if surrounding_chunks['previous'] else 'Начало документа'}
Следующий контекст: {surrounding_chunks['next'][:200] if surrounding_chunks['next'] else 'Конец документа'}
---
{chunk}
---
Этот фрагмент находится в середине документа о {document_metadata.get('topic', 'технической документации')}.
"""
    return context_info

Используйте модели эмбеддингов с увеличенной размерностью. text-embedding-3-large-3072d от OpenAI (2024) и BGE-M3 от BAAI (2025) дают на 15-25% лучшее качество поиска на сложных документах по сравнению со старыми моделями в 768-1536 измерений.

3 Иерархическая агрегация: от деталей к общей картине

Map-Reduce - классика, но в 2026 она эволюционировала. Теперь это многоуровневая агрегация с промежуточными суммаризациями.

Как это работает на практике:

# Многоуровневая агрегация для анализа длинных документов
from typing import List, Dict
import asyncio

class HierarchicalDocumentAnalyzer:
    def __init__(self, llm, chunk_size=2000):
        self.llm = llm
        self.chunk_size = chunk_size
    
    async def analyze_large_document(self, document_text: str, question: str) -> str:
        # Уровень 1: Разбиваем на крупные секции (главы)
        sections = self._split_into_sections(document_text)
        
        # Уровень 2: Параллельный анализ каждой секции
        section_tasks = []
        for section in sections:
            task = self._analyze_section(section, question)
            section_tasks.append(task)
        
        section_summaries = await asyncio.gather(*section_tasks)
        
        # Уровень 3: Агрегация результатов секций
        if len(section_summaries) > 1:
            # Если секций много, делаем промежуточную агрегацию
            intermediate_groups = self._group_summaries(section_summaries, group_size=3)
            intermediate_results = []
            
            for group in intermediate_groups:
                combined = "\n".join(group)
                intermediate_analysis = await self._aggregate_analysis(combined, question)
                intermediate_results.append(intermediate_analysis)
            
            # Финальная агрегация
            final_input = "\n---\n".join(intermediate_results)
        else:
            final_input = section_summaries[0]
        
        # Финальный ответ с учетом всей иерархии
        final_prompt = f"""
На основе анализа всего документа, ответьте на вопрос.

Контекст анализа:
{final_input}

Вопрос: {question}

Ответ должен учитывать информацию из всех разделов документа.
"""
        
        return await self.llm.ainvoke(final_prompt)
    
    async def _analyze_section(self, section_text: str, question: str) -> str:
        """Анализ отдельной секции с учетом вопроса"""
        # Здесь может быть RAG внутри секции
        prompt = f"""
Проанализируйте следующий раздел документа в контексте вопроса:

Вопрос: {question}

Раздел документа:
{section_text[:3000]}

Ключевые выводы из этого раздела, релевантные вопросу:
"""
        return await self.llm.ainvoke(prompt)

Преимущество иерархического подхода: модель сначала понимает каждую часть документа в контексте вопроса, затем синтезирует общее понимание. Это дороже (больше вызовов LLM), но точнее на 30-50% для сложных аналитических задач.

Типичные ошибки, которые сведут на нет все ваши усилия

Я видел эти ошибки в десятках проектов. Они выглядят незначительными, но каждая убивает точность.

Ошибка Последствие Как исправить
Чанкинг без перекрытия Разрыв контекста на границах предложений chunk_overlap=10-20% от размера чанка
Игнорирование структуры документа Заголовки и подзаголовки теряются, иерархия нарушается Использовать MarkdownHeaderTextSplitter или аналоги
Pure векторный поиск Пропускает точные лексические совпадения Гибридный поиск: векторы + BM25
Отсутствие рерайтинга В топ попадают семантически близкие, но нерелевантные чанки Добавить этап reranking с BGE-reranker-v2.0
Изоляция чанков LLM не видит связи между частями документа Добавлять мета-контекст (раздел, соседние чанки)

Практический кейс: анализ юридического договора на 150 страниц

Давайте пройдем весь путь на реальном примере. У нас есть договор аренды коммерческой недвижимости. Нужно ответить на вопрос: "Какие штрафные санкции предусмотрены за досрочное расторжение договора арендатором?"

Шаг 1: Загружаем и структурируем PDF. Используем специализированные инструменты вроде Unstructured.io или Docling (про который мы писали здесь).

Шаг 2: Семантический чанкинг с учетом юридической структуры:

# Юридический документ имеет четкую структуру
legal_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1200,
    chunk_overlap=240,  # 20% перекрытие критично для ссылок между пунктами
    separators=[
        "\nСтатья ",  # Юридические статьи
        "\nПункт ",   # Пункты статей
        "\n\n",       # Абзацы
        "\n",         # Строки
        ". ",          # Предложения
        " "            # Слова
    ],
    keep_separator=True  # Сохраняем разделители как часть текста
)

Шаг 3: Векторизация с современной моделью. Для юридических текстов лучше использовать специализированные эмбеддинги или хотя бы text-embedding-3-large.

Шаг 4: Гибридный поиск. Запрос "штрафные санкции досрочное расторжение арендатор" должен найти не только семантически близкие чанки, но и точные упоминания "штраф", "неустойка", "расторжение".

Шаг 5: Контекстуализация найденных чанков. Если найден пункт 8.3 о штрафах, добавляем информацию: "Это пункт 8.3 Раздела VIII 'Ответственность сторон'. Предыдущий пункт 8.2 описывает форс-мажор, следующий 8.4 - возмещение убытков."

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

💡
Для особо чувствительных документов (юридических, медицинских, финансовых) рассмотрите полностью локальное решение. В статье "Консалтинг без утечек" мы разбирали архитектуру локальной фабрики анализа документов.

Что будет дальше? Прогноз на 2027-2028

Текущие методы - это костыли. Элегантное решение появится, когда LLM научатся:

  • Селективному чтению: Модель будет сама решать, какие части документа нужно прочитать внимательно, а какие можно пролистать. Как человек с книгой.
  • Динамическому контексту: Вместо фиксированного окна - адаптивное. Сложные части документа получают больше "внимания", простые - меньше.
  • Кросс-документному пониманию: Анализ не одного PDF, а сети связанных документов с сохранением ссылок между ними.

Пока этого нет, используйте гибридный подход: умный chunking + advanced RAG с контекстуализацией + иерархическая агрегация для сложных вопросов. Это не идеально, но это работает. Сегодня.

И последний совет: никогда не доверяйте LLM анализ критически важных документов без человеческой проверки. Особенно после того, как вы узнаете, как легко RAG-системы могут сливать персональные данные. Технологии - инструмент, а не замена эксперту.