Почему ваша LLM тупеет на больших документах и как ее починить. ai-агенты.. ai-агенты. llm.. ai-агенты. llm. rag.. ai-агенты. llm. rag. Блог компании OTUS.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели. векторный поиск.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели. векторный поиск. искусственный интеллект.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели. векторный поиск. искусственный интеллект. Машинное обучение.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели. векторный поиск. искусственный интеллект. Машинное обучение. обработка документов.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели. векторный поиск. искусственный интеллект. Машинное обучение. обработка документов. Программирование.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели. векторный поиск. искусственный интеллект. Машинное обучение. обработка документов. Программирование. промпт-инжиниринг.. ai-агенты. llm. rag. Блог компании OTUS. большие языковые модели. векторный поиск. искусственный интеллект. Машинное обучение. обработка документов. Программирование. промпт-инжиниринг. эмбеддинги.

Меня зовут Андрей Бирюков. Я — независимый эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу статьи и книги. 

Когда Google анонсировал 2M контекста, а Anthropic подкинул еще 100K, наверняка многие подумали, что RAG теперь можно выкинуть на свалку истории.

Зачем париться с этими чанками, эмбеддингами и всякой прочей ерундой, если можно просто кинуть в модель весь дамп википедии и спросить ее что угодно?

Однако, в реальности возникает эффект, который тихо убивает вашу точность, когда модель читает ваш гигантский промпт как студент перед экзаменом. То есть, первые 5 страниц учит, последние 5 страниц учит, а середину… ну, там что‑то было про котиков, наверное.

И в итоге контекст 2M, а вопрос по документу на 100 страниц модель отвечает хуже, чем GPT-3.5 с контекстом 4K. И да, это не баг, это фича архитектуры внимания.

В этой статье мы поговорим о том, как перестать кормить LLM модели тоннами текста и начать использовать маленькую модель как умного секретаря, повысив точность с 34% до 68%, одновременно сократив расходы в 8 раз.

Пытаем LLM документацией

Давайте для чистоты эксперимента возьмем реальный датасет — 500 страниц технической документации по сетевому оборудованию (наши любимые логи, IP‑адреса, версии прошивок). Разобьем его на 5 сегментов по 100 страниц: A, B, C, D, E.

Почему ваша LLM тупеет на больших документах и как ее починить - 1

Далее нам нужно будет задать 10 вопросов, ответы на которые лежат строго в сегменте C (золотая середина) и посмотреть, что из этого выйдет.

Стратегия № 1: Full‑Context (кормим всё целиком)

Начнем с самого простого варианта: запихнуть все 500 страниц в контекст GPT-4o. Но результат оказался не слишком интересным, так как мы получили точность всего 34%, и при этом первый токен появился только через 14 секунд (согласитесь, многовато).

Ну и конечно не обошлось без галлюцинаций: модель начала комбинировать IP‑адреса из сегментов A и E, создавая несуществующие маршруты.

В реальности такой вариант практически бесполезен.

Стратегия № 2: Naive RAG (векторный поиск, топ-5 чанков)

Теперь давайте немного усложним реализацию, нарезав данные на чанки. Здесь мы получаем точность 42%, то есть лучше, но не намного. При этом, ответ пришел через 1.1 секунды, быстро, но зачем скорость, если ответ неправильный?

Здесь суть проблемы заключается в том, что мы теряем контекст между чанками, и из‑за этого модель не понимает общей картины.

В итоге мы приходим к неутешительному выводу о том, что оба подхода неприменимы, так как Full‑Context дорогой и тупой, а Naive RAG быстрый, но тоже недалекий.

RAPTOR, Self‑Refine и прочие модные штуки

Прежде чем перейти к реально работающему решению, давайте разберем, почему некоторые существующие паттерны не работают в 2026 году.

RAPTOR (Recursive Abstractive Processing)

Почему ваша LLM тупеет на больших документах и как ее починить - 2

Этот паттерн строит деревья кластеров, суммаризируя их на каждом уровне. Звучит вроде бы мощно, однако, на практике он отлично подходит для пересказа «в целом», но убивает точность.

Например, спросите у RAPTOR версию драйвера на странице 245 из нашей документации, и он выдаст вам среднюю температуру по больнице. Потому что суммаризация усредняет факты, что хорошо подходит для аналитики, но для техподдержки и других задач, требующих точности это смерть.

Self‑Refine / ReAct (итеративные уточнения)

Почему ваша LLM тупеет на больших документах и как ее починить - 3

Эта модель что называется, ходит кругами, то есть перечитывает контекст и постоянно уточняет.

Она как‑бы работает, но требует 5–7 запросов к дорогой LLM и стоимость одной сессии может достигать $0.5. Теперь умножьте на 1000 пользователей в день и получите достаточно серьезные затраты.

Первым решением, которое может прийти на ум в такой ситуации, это увеличение чанков.

Если модель теряется в середине 500-страничного документа, давайте сделаем чанки по 10K токенов. Но, после такого изменения модель теряется в середине каждого чанка и становится только хуже.

Таким образом, мы приходим к неутешительному выводу о необходимости нового подхода, обеспечивающего точность, скорость и по приемлемой цене.

Семантический привратник

Представьте, что у вас в компании есть дорогой и талантливый юрист (наша большая LLM). Вы не будете скидывать ему 500 страниц договора и говорить «разберись». Вместо этого, вы сначала дадите договор младшему ассистенту, который вытащит оттуда ключевые факты (суммы, даты, стороны), оценит, какая часть договора вообще относится к вашему вопросу. В итоге ассистент подаст юристу только самое важное на одной странице. Именно так и будет работать наш «Семантический привратник».

Роль ассистента будет выполнять Mistral-7B‑Instruct в 4-битной квантизации. Он стоит копейки (в смысле денег и ресурсов), но умеет читать и анализировать.

Давайте посмотрим, как все это работает. В начале, мы делаем грубый векторный поиск по базе знаний и достаем не 5 чанков, а 50, чтобы наверняка не потерять релевантный кусок. Дальше каждый из этих 50 чанков прогоняем через Mistral-7B с простой задачей: «Оцени релевантность чанка вопросу от 0 до 1. Вытащи оттуда 3 ключевых факта. Ответь в формате JSON.»

При этом мы запрещаем модели фантазировать, то есть, она не генерирует связный текст, а только извлекает факты из исходного чанка. Это снижает вероятность галлюцинаций до минимума.

Вот пример ответа Mistral:

Почему ваша LLM тупеет на больших документах и как ее починить - 4

Далее мы используем буфер динамического приоритета, то есть сортируем 50 чанков по значению score и всё, что ниже 0.6, выкидываем. Из оставшихся берем ТОП-10 по релевантности.

Здесь важным моментом является то, что мы не кладем эти чанки в том порядке, в котором они были в документе. Вместо этого, мы сортируем их по убыванию важности и самый релевантный становится первым.

Ранее мы говорили о том, что модель забывает середину. Давайте попробуем ее обмануть, и для этого в начало промпта мы положим сжатые синопсисы всех 10 чанков (это примерно 2000 токенов), а в самом конце промпта продублируем наиболее релевантный чанк целиком, без сжатия.

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

Как это выглядит на практике

Для инференса Mistral-7B мы используем vLLM — он расходует меньше памяти и работает быстрее трансформеров из коробки. Вот рабочий код нашего пайплайна на Python:

from vllm import LLM, SamplingParams
import json

# Загружаем Mistral-7B в 4-битной квантизации
# На A100 40GB влезает без проблем
llm = LLM(
    model="mistralai/Mistral-7B-Instruct-v0.3",
    quantization="AWQ",  # 4-bit квантизация
    dtype="half",
    max_model_len=4096
)

def gatekeeper_filter(query, chunks, top_k=10):
    """
    query: вопрос пользователя
    chunks: список из 50 чанков (текст)
    top_k: сколько лучших оставить
    """
    prompt_template = """
    [INST]
    Твоя задача — проанализировать фрагмент документа.
    Вопрос пользователя: {query}
    
    Текст фрагмента:
    {chunk}
    
    Ответь строго в формате JSON:
    - "score": число от 0 до 1 (насколько чанк релевантен вопросу)
    - "facts": массив из максимум 3 ключевых фактов (короткие предложения из текста)
    
    Пример: {{"score": 0.85, "facts": ["Сервер перезагружен в 14:32", "Ошибка E531"]}}
    [/INST]
    """
    
    # Готовим батч промптов для 50 чанков
    batch_prompts = [
        prompt_template.format(query=query, chunk=chunk) 
        for chunk in chunks
    ]
    
    # Запускаем инференс с низкой температурой (детерминизм)
    sampling_params = SamplingParams(
        temperature=0.0,      # Никакой креативности, только факты
        max_tokens=200,
        stop=["nn"]         # Чтобы не генерировала лишнего
    )
    
    outputs = llm.generate(batch_prompts, sampling_params)
    
    # Парсим JSON из ответов
    parsed_results = []
    for i, output in enumerate(outputs):
        raw_text = output.outputs[0].text
        try:
            # Ищем { ... } в ответе
            start = raw_text.find('{')
            end = raw_text.rfind('}') + 1
            json_str = raw_text[start:end]
            data = json.loads(json_str)
            parsed_results.append({
                "score": float(data.get("score", 0.0)),
                "facts": data.get("facts", [])[:3]
            })
        except (json.JSONDecodeError, ValueError):
            # Если модель накосячила — ставим низкий скор
            parsed_results.append({"score": 0.0, "facts": []})
    
    # Сортируем по убыванию скора
    scored_chunks = list(zip(chunks, parsed_results))
    scored_chunks.sort(key=lambda x: x[1]['score'], reverse=True)
    
    # Берем топ-K
    top_chunks = scored_chunks[:top_k]
    
    # Компонент В: формируем сжатый контекст из фактов
    compressed_facts = []
    for chunk_data, meta in top_chunks:
        compressed_facts.extend(meta['facts'])
    
    # Ограничиваем длину, чтобы не переполнить контекст
    compressed_context = " ".join(compressed_facts[:40])
    
    # Дублируем лучший чанк целиком в конец
    best_chunk_raw = top_chunks[0][0] if top_chunks else ""
    
    return {
        "compressed_context": compressed_context,
        "anchor_chunk": best_chunk_raw
    }

# Пример использования
chunks = [...]  # ваши 50 чанков из векторной БД
query = "Какая версия прошивки была на сервере 10.0.4.22 во время сбоя?"

result = gatekeeper_filter(query, chunks)

# Формируем финальный промпт для GPT-4
final_prompt = f"""
Контекст (ключевые факты):
{result['compressed_context']}

Подробный фрагмент документа (самый важный):
{result['anchor_chunk']}

Вопрос пользователя: {query}

Ответь на вопрос, используя только предоставленную информацию.
"""

В итоге, результат будет иметь следующий вид:

Почему ваша LLM тупеет на больших документах и как ее починить - 5

Что мы получили в итоге

В заключении давайте посмотрим, какие результаты мы получили. Мы прогнали наш пайплайн на 1000 запросов из реальной техподдержки и сравнили с тремя другими подходами.

Метод

Токенов в промпте

Точность (точное совпадение)

Задержка

Стоимость за 1000 запросов

Full‑Context

~180 000

34.2%

14.5 сек

$180

Naive RAG (Top-5)

~8 000

41.1%

1.4 сек

$15

RAPTOR

~12 000

49.7%

5.2 сек

$40

Semantic Gatekeeper (наш)

~9 000

68.4%

3.8 сек

$22

Если говорить о точности, то мы обогнали Full‑Context почти вдвое (68% против 34%), благодаря тому, что LLM перестала отвлекаться на шум. Мы дали ей только релевантные факты, а не 500 страниц воды.

Также, мы потратили 2.4 секунды на прогон через Mistral-7B, но сэкономили 10 секунд на GPT-4 (потому что промпт стал в 20 раз короче). И наконец, по деньгам, мы потратили $22 против $180, то есть экономия в 8 раз. На загрузке в десятки тысяч запросов вы можете сэкономить значительные суммы.

Таким образом, мы смогли починить наш RAG и получить хороший результат при обработке большого объема запросов.

ЧТО ЕЩЕ ПОЧИТАТЬ ПО ТЕМЕ:

Почему ваша LLM тупеет на больших документах и как ее починить - 6

Если вы уже работаете с LLM или только начинаете строить свои AI‑системы, наверняка сталкивались с проблемой: модель знает много, но при работе с большими объёмами данных начинает терять важные детали и выдавать неточные ответы. Разобраться, как управлять качеством работы LLM и строить надёжные сценарии взаимодействия с моделями, — следующий шаг после знакомства с базовыми возможностями ИИ.

На бесплатных уроках разберём, как устроены современные подходы к работе с LLM:

  • 22 сентября в 18:00. «Ландшафт современного NLP: от эмбеддингов и классических ML‑методов до современных LLM». Записаться.

  • 23 сентября в 18:00. «Structured Outputs: как заставить LLM всегда возвращать то, что вам нужно». Записаться.

  • 1 октября в 20:00. «Проверка ответов LLM и борьба с галлюцинациями через промпт‑инжиниринг». Записаться.

Больше бесплатных уроков сентября смотрите в дайджесте.

Автор: Andrey_Biryukov

Источник