Правильные документы, неправильный ответ: парадокс RAG
Вы настроили векторный поиск, отполировали эмбеддинги, нарезали документы по смыслу - и ваша RAG-система извлекает ровно те фрагменты, которые нужны для ответа. Но LLM, как назло, выдаёт полную чушь. Знакомо? Это конфликт контекста, и он ломает больше продакшен-систем, чем вы думаете.
Конфликт контекста - это когда ретривер вернул правильные документы, но генератор их интерпретировал неправильно, проигнорировал или смешал с собственными "знаниями". И нет, это не баг LLM - это дизайнерская ошибка вашего пайплайна.
В 2026 году большинство команд уже научились бороться с hallucination и improve retrieval, но конфликт контекста остаётся слепым пятном. Почему? Потому что метрики вроде precision@k показывают, что документы релевантны, а значит, проблема якобы не в поиске. Но ответ всё равно неверный.
Давайте разберёмся, почему так происходит и как это исправить - с кодом, экспериментами и конкретными приёмами, которые работают на CPU без GPU-оргии.
Что такое конфликт контекста и почему он вас уже достал
Представьте: вы спрашиваете у RAG-системы "Какая версия Python требуется для библиотеки TensorFlow 2.15?". Ретривер находит два документа: первый говорит, что нужен Python 3.9, второй - что Python 3.11. Оба документа релевантны, но противоречат друг другу. LLM получает этот "салат" и должна выбрать одну версию. Чаще всего она выбирает неправильную или даёт расплывчатый ответ.
Это прямой конфликт. Но есть и косвенный: документы не противоречат явно, но содержат информацию, которая в сумме с знаниями LLM даёт ошибку. Например, документ описывает устаревший API, а LLM знает про новый, и она решает, что документ ошибается, и говорит на основе своих знаний - которые могут быть устаревшими для вашего контекста.
Проблема в том, что стандартные пайплайны RAG не умеют разрешать такие конфликты. Они просто склеивают куски текста и подают в LLM, надеясь на чудо. Чуда не происходит.
Три причины, почему ваш RAG врёт даже с идеальными документами
- Приоритет первого контекста: LLM, особенно модели вроде GPT-4o 2026 edition, склонны придавать больше веса первому упомянутому факту в контексте. Если противоречивый документ попал первым - прощай, точность.
- Слепая уверенность в знаниях: LLM тренированы на огромных корпусах и часто предпочитают свои внутренние знания внешнему контексту. Даже если вы явно сказали "отвечай только по документам", модель может решить, что документ устарел или ошибается.
- Шум от слабо релевантных фрагментов: Даже с высоким similarity threshold в топ-5 всегда затесается какой-нибудь слабый документ, который добавляет помехи и сбивает LLM с толку.
Эти причины не гипотетические - они вылезают в 80% продакшен-систем. И стандартные подходы вроде увеличения top-k или настройки промптов не помогают. Нужны более хитрые методы.
Как заставить RAG выбирать правильный контекст: методы, которые работают
Я выделил три метода, которые реально работают на практике. Они требуют дополнительных вычислений, но окупаются резким ростом точности.
Метод 1: Косвенная симуляция (Indirect Simulation)
Вместо того чтобы подавать все документы сразу, мы симулируем "диалог" между LLM и документами. Сначала LLM генерирует возможные ответы на основе каждого документа по отдельности, а затем выбирает наиболее согласованный ответ.
# Упрощённый пример косвенной симуляции
from openai import OpenAI
client = OpenAI()
def indirect_simulation(query, documents):
answers = []
for doc in documents:
prompt = f"Документ: {doc}\n\nВопрос: {query}\nОтвет только на основе документа:"
response = client.chat.completions.create(
model="gpt-4o-2026", # Актуальная модель на 2026 год
messages=[{"role": "user", "content": prompt}],
temperature=0.0
)
answers.append(response.choices[0].message.content)
# Агрегируем ответы, например, голосованием
from collections import Counter
most_common_answer = Counter(answers).most_common(1)[0][0]
return most_common_answer
Этот метод уменьшает влияние конфликта, потому что каждый документ обрабатывается изолированно. Но он дорогой - N вызовов LLM для N документов. Для снижения затрат можно использовать меньшую модель для симуляции или применять метод только к топ-документам с высоким конфликтом.
Метод 2: Перекрестное кодирование (Cross-Encoding)
Используем модель перекрестного кодирования (например, на основе BERT) для оценки релевантности каждого документа к запросу в контексте других документов. Это помогает выявить, какие документы действительно поддерживают ответ, а какие вносят противоречие.
В 2026 году для этого отлично подходят модели вроде Cross-Encoder от SentenceTransformers, которые можно запускать на CPU с приемлемой скоростью.
from sentence_transformers import CrossEncoder
cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
def rank_with_cross_encoder(query, documents):
pairs = [[query, doc] for doc in documents]
scores = cross_encoder.predict(pairs)
ranked_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
return [documents[i] for i in ranked_indices]
Перекрестное кодирование даёт более точную оценку релевантности, чем векторный поиск, потому что учитывает взаимодействие запроса и документа целиком. Но оно медленнее для больших коллекций, так что лучше использовать его как второй этап после векторного поиска. Если хотите глубже разобраться в том, почему эмбеддинги иногда "слепнут", почитайте мою статью про слепое пятно RAG.
Метод 3: Динамическое взвешивание источников
Мы учим модель (или используем эвристики) присваивать вес каждому документу на основе его согласованности с другими документами. Документы, которые противоречат большинству, получают низкий вес или отфильтровываются.
Один из способов - вычислять embedding каждого документа и находить косинусное сходство между ними. Документы, которые сильно отличаются от кластера основных, считаются выбросами.
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
def filter_outliers(documents_embeddings, threshold=0.5):
# documents_embeddings - массив эмбеддингов документов
similarity_matrix = cosine_similarity(documents_embeddings)
avg_similarity = np.mean(similarity_matrix, axis=1)
mask = avg_similarity > threshold
return mask
Этот метод хорошо работает, когда у вас есть несколько явных выбросов. Но он не помогает, если все документы противоречат друг другу поровну - тут нужна семантическая анализ.
Пошаговый план: отлаживаем конфликт контекста за 4 часа
1 Диагностика: находим конфликт в вашей системе
Сначала нужно понять, есть ли у вас конфликт контекста. Возьмите 100 случайных запросов к вашей RAG-системе и для каждого запроса проверьте: если LLM дала неправильный ответ, но ретривер вернул правильные документы - это конфликт. Автоматизируйте это с помощью LLM-as-a-judge подхода.
2 Внедряем перекрестное кодирование как второй этап ретривера
Добавьте cross-encoder после векторного поиска. Оставьте только топ-3 документа после перекрёстного ранжирования. Это сразу снизит шум. Используйте легковесную модель, например, 'cross-encoder/ms-marco-MiniLM-L-6-v2', она работает на CPU за доли секунды.
3 Добавляем взвешенную агрегацию контекста
Вместо простой конкатенации документов, создайте промпт, который явно указывает на важность каждого документа. Например: "Документ 1 (высокая релевантность): ... Документ 2 (средняя релевантность): ...". Веса можно получить из scores cross-encoder. Готовые шаблоны промптов, которые снижают риск галлюцинаций, я собрал в отдельной статье про промпты для RAG.
4 Тестируем с косвенной симуляцией для критичных запросов
Для запросов, где точность критична (например, медицинские или юридические), внедрите косвенную симуляцию. Запускайте её асинхронно и кэшируйте результаты. Да, это дорого, но для 5% запросов это допустимо.
Весь код для этих шагов я выложил в GitHub-репозитории. Эксперименты проводятся на CPU, так что вы можете воспроизвести их даже на ноутбуке.
Ошибки, которые все допускают (и вы тоже)
- Игнорирование метрики согласованности документов. Precision@k не показывает, насколько документы противоречат друг другу. Добавьте метрику, которая измеряет семантическое сходство между топ-документами. Если оно низкое - у вас потенциальный конфликт.
- Слепая вера в один документ. Даже если первый документ имеет высший score, это не значит, что он правильный. Всегда проверяйте консенсус.
- Использование одинаковых промптов для всех типов запросов. Для фактологических запросов нужны строгие промпты с требованием ссылаться на документы, для аналитических - более гибкие. Возьмите готовые шаблоны из статьи "Промпты для RAG".
- Отсутствие мониторинга конфликта. Раз в неделю запускайте скрипт, который ищет случаи конфликта в логах. Как это делать, я писал в статье про мониторинг RAG.
Частые вопросы про конфликт контекста
| Вопрос | Ответ |
|---|---|
| Конфликт контекста - это то же самое, что hallucination? | Нет. Hallucination - когда LLM придумывает факты из головы. Конфликт контекста - когда LLM неправильно интерпретирует реальные документы. Но они часто идут вместе. |
| Какой метод самый эффективный? | Комбинация перекрестного кодирования и взвешенной агрегации даёт +30% точности почти бесплатно. Косвенная симуляция - для сценариев, где ошибка стоит дорого. |
| Можно ли полностью избежать конфликта? | Нет, если данные противоречивы. Но можно научить систему обнаруживать противоречия и либо запрашивать уточнение, либо указывать на неопределённость. |
Конфликт контекста - это не приговор для RAG, а инженерная задача. Решается она не магией, а системным подходом: улучшаем ретривер, добавляем ранжирование, меняем агрегацию. И да, иногда приходится платить за дополнительные вычисления. Но лучше заплатить за CPU, чем терять пользователей из-за неверных ответов.
Если хотите глубже погрузиться в оценку RAG-систем, посмотрите мою статью про LLM-as-a-judge. А для понимания, как другие подходы, например графы знаний, решают проблемы RAG в сложных доменах, есть материал про RAG в юриспруденции.
Эксперименты из этой статьи вы можете повторить самостоятельно - весь код на GitHub. И если вы столкнулись с конфликтом контекста, напишите мне, как вы его решили. Интересные кейсы я добавлю в статью.