Перейти к содержанию
Новое AiManual теперь в MAX Подписаться
Публикация AiManual

Четыре кита Context Engineering: почему RAG галлюцинирует, даже если промпт идеален

Разбираем четыре этапа пайплайна RAG, где рождаются галлюцинации: парсинг, обработка запроса, поиск и генерация. Практические кейсы на документах Всемирного бан

Коротко

Что будет в материале

  1. 01

    Почему идеальный промпт не спасает от галлюцинаций RAG

  2. 02

    Четыре этапа пайплайна RAG, где рождаются галлюцинации

  3. 03

    Кейс 1: Парсинг - когда таблица превращается в шум

  4. 04

    Кейс 2: Обработка запроса - когда пользователь говорит не на языке документа

Вы потратили неделю на тюнинг системного промпта. Переписали инструкции, добавили примеры few-shot, трижды уточнили формулировки. Запускаете RAG-систему на новом корпоративном документе - и получаете уверенный ответ с несуществующей цифрой. Модель не «ошиблась» в привычном смысле. Она честно отработала по контексту, который вы ей передали. Проблема в том, что контекст уже был сломан.

Context Engineering - подход, который переносит фокус с промпта на четыре этапа пайплайна RAG: парсинг, обработку запроса, поиск и генерацию. Галлюцинация возникает не в момент генерации текста, а раньше - когда таблица превращается в шум, запрос пользователя не совпадает с терминологией документа, нужная страница отсекается лимитом чанков или модель вынуждена достраивать отсутствующее значение. Каждый из этих отказов изолирован, воспроизводим и закрывается конкретным инженерным контрактом.

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

Почему идеальный промпт не спасает от галлюцинаций RAG

Типичный цикл внедрения RAG в enterprise выглядит так: инженерная команда выбирает векторную базу, настраивает чанкинг, пишет промпт. Система работает на тестовых запросах. Затем приходит пользователь с реальным вопросом - и получает ответ, который выглядит правдоподобно, но содержит фактические ошибки. Первая реакция: «надо улучшить промпт». Вторая: «надо взять модель помощнее». Третья: «RAG не работает».

Практика показывает, что замена GPT-4 на GPT-5 или Claude на более свежую версию не устраняет галлюцинации, если корень проблемы - в качестве контекста. Модель не может «догадаться», что таблица была распарсена с перемешиванием столбцов. Она не знает, что пользователь имел в виду термин, которого нет в индексе. Она честно работает с тем, что получила.

Context Engineering исходит из простого принципа: галлюцинация - это отказ одного из четырёх этапов пайплайна. Диагностируйте этап, поставьте контракт на входе и выходе - и отказ исчезает. Без магии, без бесконечного тюнинга промптов.

Четыре этапа пайплайна RAG, где рождаются галлюцинации

Пайплайн RAG состоит из четырёх последовательных этапов. На каждом из них возможен свой тип отказа, который приводит к уверенному неверному ответу на выходе:

  • Парсинг. Документ извлекается из PDF, DOCX или HTML и разбивается на чанки. Если парсер не сохраняет структурные связи - строки таблицы, иерархию заголовков, подписи к рисункам - модель получает бессвязный набор токенов. Галлюцинация: числовой ответ, собранный из случайных ячеек таблицы.
  • Обработка запроса. Пользователь формулирует вопрос в своих терминах. Если эти термины отсутствуют в документе, векторный поиск не находит релевантные чанки. Галлюцинация: ответ на основе соседних, но нерелевантных фрагментов.
  • Поиск. Система ранжирует чанки по релевантности и отбирает top-K. Если нужный фрагмент оказывается на позиции K+1, он отсекается. Галлюцинация: модель «не видит» ключевой информации и либо отказывается отвечать, либо строит ответ на неполных данных.
  • Генерация. Модель получает контекст и промпт. Если точного ответа в контексте нет, но есть косвенные данные, модель склонна экстраполировать. Галлюцинация: правдоподобное, но вымышленное значение.

Ни один из этих отказов не исправляется улучшением промпта. Промпт работает с тем контекстом, который уже собран. Если контекст содержит шум или не содержит ответа - промпт бессилен. Это ключевой инсайт, который меняет подход к построению надёжных RAG-систем.

Кейс 1: Парсинг - когда таблица превращается в шум

Возьмём отчёт Всемирного банка с таблицей финансовых показателей по регионам. Стандартный пайплайн: PDF → PyPDF2 или аналогичный парсер → разбиение по двойным переносам строки → чанки. Парсер извлекает текст построчно, но теряет структуру столбцов. Строка «East Asia | 12.4 | 8.7 | 3.2» превращается в три отдельные строки, которые могут попасть в разные чанки. Связь «регион → показатель → значение» разрушена.

Модель получает чанк с числами 12.4, 8.7, 3.2 без привязки к регионам. Запрос: «Какой объём инвестиций в East Asia?». Модель видит числа, видит где-то рядом слова «East Asia» и «investment», уверенно выдаёт: «12.4 миллиарда долларов». Ответ неверен - 12.4 относится к другому региону и другому показателю.

Реляционный парсинг: контракт на первом кирпиче

Решение - парсить таблицы как реляционные структуры, а не как плоский текст. Для каждой таблицы извлекается явная схема: список столбцов, список строк, значения с координатами [строка, столбец]. На выходе парсера - JSON с ключами, сохраняющими семантику:

{
  "table_id": "investments_2025",
  "columns": ["region", "fdi_inflow", "portfolio_flow", "other_investment"],
  "rows": [
    {"region": "East Asia", "fdi_inflow": 12.4, "portfolio_flow": 8.7, "other_investment": 3.2},
    {"region": "Sub-Saharan Africa", "fdi_inflow": 4.1, "portfolio_flow": 1.3, "other_investment": 0.9}
  ]
}

Такой чанк попадает в контекст модели. Связи сохранены. Модель не гадает, какое число к чему относится - структура задана явно. В ноутбуке показано сравнение: наивный парсинг даёт точность ответов на табличные вопросы 34%, реляционный - 96% на том же документе и той же модели.

Для сложных PDF с многоколоночной вёрсткой и вложенными таблицами одного реляционного парсера может быть недостаточно. Полезен каскадный подход: сначала дешёвая эвристика определяет, есть ли на странице таблица, и если да - вызывает специализированный парсер. Это экономит бюджет на вызов платных OCR-моделей и не замедляет обработку простых страниц.

Кейс 2: Обработка запроса - когда пользователь говорит не на языке документа

Пользователь спрашивает: «Какой уровень защищённости требуется для обработки персональных данных?». В стандарте NIST SP 800-53 используется термин «security assurance level», а словосочетание «уровень защищённости» не встречается ни разу. Векторный поиск по эмбеддингам находит ближайшие чанки - но они про «security controls» и «privacy requirements», не содержащие конкретного ответа про уровень.

Модель получает нерелевантный контекст. Вместо того чтобы сказать «не знаю», она генерирует ответ на основе общих фраз из найденных чанков. Ответ звучит убедительно, но не соответствует документу. Это не галлюцинация модели - это ошибка retrieval, вызванная терминологическим разрывом.

Расширение запроса: от синонимов до HyDE

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

  • Доменный тезаурус. Для каждой предметной области строится маппинг: «уровень защищённости» → «assurance level», «security level», «protection level». Быстро, дёшево, не требует вызова LLM. Подходит для документов с устоявшейся терминологией - стандартов, нормативных актов, технической документации.
  • Query2query через LLM. Модель получает исходный запрос и инструкцию переформулировать его в терминах, характерных для документов определённого типа. Например: «Переформулируй запрос, используя терминологию NIST SP 800-53». Работает хорошо, но добавляет задержку и стоимость одного LLM-вызова.
  • HyDE (Hypothetical Document Embeddings). Модель генерирует гипотетический фрагмент документа, который мог бы содержать ответ на запрос. Затем по эмбеддингу этого фрагмента ищутся реальные чанки. Метод показывает высокую точность для сложных запросов, но требует двух LLM-вызовов на запрос - генерация гипотезы и финальный ответ.

Для enterprise-сценариев с чувствительными данными рекомендуется начинать с доменного тезауруса. Он не отправляет пользовательские запросы во внешние API и не добавляет задержку. Когда тезаурус исчерпан - подключать query2query с локальной моделью.

Кейс 3: Поиск - когда нужная страница оказывается ниже cutoff

Отчёт Всемирного банка содержит 120 страниц. Ответ на запрос «прогноз инфляции для Аргентины на 2026 год» находится в разделе «Latin America Outlook» на странице 47. Система настроена на top-20 чанков. Векторный поиск возвращает 20 наиболее релевантных фрагментов - и все они с первых 15 страниц, где обсуждаются общие макроэкономические тренды.

Почему так происходит? Эмбеддинг запроса ближе к общим рассуждениям об инфляции, чем к конкретной таблице с цифрами по Аргентине. Плюс первые страницы документа обычно содержат executive summary с высокой плотностью ключевых терминов. Нужный чанк оказывается на 23-й позиции - ниже cutoff. Модель получает контекст без целевых данных и либо галлюцинирует прогноз, либо выдаёт общую информацию, не отвечающую на вопрос.

Маршрутизация по оглавлению: как не потерять иголку в стоге сена

Решение - двухэтапный поиск с предварительной маршрутизацией. Первый этап: классификация запроса в один или несколько разделов документа на основе оглавления. Второй этап: поиск чанков только внутри выбранных разделов.

Реализация: оглавление документа парсится в структуру с заголовками разделов и номерами страниц. Для каждого заголовка вычисляется эмбеддинг. При поступлении запроса вычисляется его эмбеддинг и находится top-3 ближайших заголовка разделов. Поиск чанков ограничивается страницами этих разделов. В примере с Аргентиной запрос будет направлен в раздел «Latin America Outlook», и нужный чанк окажется на первой позиции.

Этот подход даёт два преимущества. Во-первых, снижается шум - модель не отвлекается на нерелевантные разделы. Во-вторых, гарантируется, что релевантный контекст не будет отсечён лимитом top-K, потому что поиск ведётся в существенно меньшем пространстве. Цена - один дополнительный вызов эмбеддинга для классификации запроса по оглавлению. На практике задержка составляет 50-100 мс и полностью окупается повышением точности.

Кейс 4: Генерация - когда модель достраивает отсутствующее значение

Самый коварный тип галлюцинаций. Документ NIST SP 800-57 содержит таблицу с требованиями к длине ключа для разных классов криптографической защиты. Для классов 1, 2, 3 и 5 значения указаны: 128, 192, 256 и 256 бит соответственно. Для класса 4 значение отсутствует - в документе стоит прочерк.

Пайплайн отработал корректно: парсинг сохранил структуру таблицы, запрос «длина ключа для класса 4» расширен и нашёл нужный чанк. Модель получает контекст с таблицей, видит последовательность 128-192-256-?-256 и уверенно выдаёт: «Для класса 4 требуется длина ключа 256 бит». Ответ логичен, но неверен - в документе значение не определено.

Это не ошибка retrieval и не дефект парсинга. Модель получила правильный контекст, но экстраполировала закономерность там, где её нет. Стандартный промпт «ответь на вопрос на основе контекста» не предотвращает такое поведение.

Типизированная генерация: контракт на последнем кирпиче

Решение - заставить модель вернуть не свободный текст, а типизированную структуру с обязательным полем-флагом. Модель получает инструкцию: «Если точный ответ содержится в контексте, укажи complete_answer_found: true и приведи значение. Если точного ответа нет, укажи complete_answer_found: false и не додумывай».

Промпт выглядит так:

Ты - система извлечения фактов из документов. 
Контекст: {context}
Вопрос: {query}

Верни JSON строго по схеме:
{
  "complete_answer_found": boolean,
  "answer": string | null,
  "evidence": string | null
}

Правила:
- complete_answer_found: true ТОЛЬКО если точное значение явно указано в контексте
- Если значение отсутствует или стоит прочерк - complete_answer_found: false, answer: null
- Не экстраполируй, не додумывай, не используй косвенные данные

На выходе система получает структуру, которую можно проверить в коде. Если complete_answer_found: false - пользователю возвращается честный ответ: «В документе значение для класса 4 не определено». Это не отказ системы, а корректный результат. Пользователь получает точную информацию вместо правдоподобной лжи.

Такой подход перекликается с идеей representation steering в модели Gemma-4-31B-AntiHal, где скептическая позиция встраивается на уровне скрытых представлений. Разница в том, что типизированная генерация работает с любой моделью, поддерживающей structured output, без модификации весов.

Собираем всё вместе: архитектура Context Engineering для enterprise

Четыре контракта интегрируются в единый пайплайн. Порядок прохождения запроса:

  1. Входной запрос → расширение через доменный тезаурус или query2query.
  2. Расширенный запрос → маршрутизация по оглавлению, определение целевых разделов.
  3. Поиск внутри выбранных разделов с реляционно распарсенными чанками.
  4. Генерация типизированного ответа с флагом complete_answer_found.
  5. Постобработка: если флаг false - возврат пользователю сообщения об отсутствии данных.

Компромиссы, которые нужно принять:

  • Задержка. Расширение запроса добавляет 100-300 мс (тезаурус - 0 мс, query2query с локальной моделью - 200-300 мс). Маршрутизация по оглавлению - 50-100 мс. Суммарно пайплайн тяжелее наивного RAG на 150-400 мс. Для большинства enterprise-сценариев это приемлемо.
  • Стоимость. Реляционный парсинг требует предобработки документов, но делается один раз при индексации. Расширение запроса через LLM добавляет стоимость одного вызова на запрос. Маршрутизация - стоимость одного дополнительного эмбеддинга. Типизированная генерация не добавляет стоимости относительно обычной.
  • Сложность. Каждый контракт - это дополнительный модуль, который нужно разработать, протестировать и поддерживать. Начинать стоит с одного этапа, который даёт максимальный прирост надёжности в вашем конкретном сценарии.

С чего начать внедрение? Проведите аудит текущих отказов. Разметьте 100 случайных запросов, на которых система ошиблась, и классифицируйте причину: парсинг, запрос, поиск или генерация. Этап с наибольшей долей отказов - первый кандидат на установку контракта. В нашем опыте для документов с таблицами наибольший прирост даёт реляционный парсинг. Для больших многотомных документов - маршрутизация по оглавлению. Для систем, где пользователи формулируют запросы в свободной форме - расширение запроса.

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

Когда улучшение промпта или смена модели не помогут: границы ответственности

Существует соблазн подождать следующего поколения моделей. GPT-6, Claude 5, Gemini 3 - наверняка они будут меньше галлюцинировать? Будут. Но фундаментальная проблема не в интеллекте модели, а в качестве и полноте контекста, который она получает.

Даже самая мощная модель не восстановит структуру таблицы, разрушенную парсером. Она не угадает, что пользователь под «защищённостью» имел в виду «assurance level». Она не получит доступ к чанку, который не попал в top-K. Она не отличит отсутствие значения от закономерности, если контекст содержит последовательность чисел с одним пропуском.

Context Engineering не заменяет сильные модели. Он дополняет их, обеспечивая надёжность на системном уровне. Модель отвечает за генерацию связного текста. Пайплайн отвечает за то, чтобы модель получила полный, структурно корректный и релевантный контекст. Разделение ответственности между моделью и инженерной обвязкой - ключевой архитектурный принцип, который мы детально разбирали в контексте LLM-агентов с верификацией. Тот же принцип работает и для RAG.

Практический вывод: когда вы видите галлюцинацию, не начинайте с промпта. Начните с диагностики этапа. В 80% случаев проблема решается не сменой модели и не переписыванием инструкций, а установкой контракта на одном из четырёх кирпичей пайплайна.

Подписаться на канал