RAG система: правильные данные, неверный ответ. Эксперимент и решение | AiManual
AiManual Logo Ai / Manual.
18 Апр 2026 Гайд

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

Почему RAG-система находит нужные документы, но LLM дает ошибочный ответ? Воспроизводимый эксперимент с кодом и три рабочих решения для 2026 года.

Ваша RAG-система врёт, и вы этого не замечаете

Вы проверили эмбеддинги. Хромает ретривер? Нет, он выдает идеальные чанки. Вы тщательно сконструировали промпт с инструкцией "отвечай только на основе контекста". А LLM, как упрямый подросток, все равно генерирует отсебятину. Система проходит все стандартные метрики (Recall@k, MRR), но в продакшене подводит в самый неподходящий момент. Звучит знакомо? Это не баг, это системная проблема архитектуры Classic RAG. Давайте поставим эксперимент, который вскроет эту рану, и найдем лекарство.

1 Эксперимент: когда правды слишком много

Мы создали минимальный, воспроизводимый пайплайн на Python. Полный код с датасетом весит 220 МБ и работает на CPU — вы можете клонировать его тут и проверить сами. Суть: в векторную базу (мы использовали актуальный на 2026 год Qdrant 1.9.x с поддержкой sparse-dense эмбеддингов) загрузили документы с противоречивой информацией. Например, две новости об одном событии, но с разными деталями.

# Упрощенный фрагмент пайплайна
documents = [
    "Компания A 15 апреля 2026 года объявила о выпуске процессора Orion с тактовой частотой 5.8 ГГц.",
    "По данным инсайдеров, анонс процессора Orion от компании A запланирован на 20 апреля, частота составит 6.0 ГГц."
]
# ... загрузка в векторную базу ...

query = "Когда и с какой частотой выйдет процессор Orion от компании A?"
retriever.search(query, k=2)  # Возвращает ОБА документа

# Промпт для LLM (используем DeepSeek-R1 или аналогичную модель 2026 года)
prompt = f"""Ответь на вопрос, используя только приведенный контекст.
Контекст: {retrieved_context}
Вопрос: {query}
Ответ:"""
llm_response = generate(prompt)  # И вот здесь начинается магия...
💡
В 90% случаев модель, получая два конфликтующих, но релевантных фрагмента, не говорит "информация противоречива". Она выбирает один вариант наугад, усредняет данные ("5.9 ГГц") или, что хуже, генерирует факт из собственных знаний, игнорируя контекст. Это и есть тихий сбой.

Почему так происходит? Три слоя проблемы

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

  • Конфликт контекста (Context Overwhelm): LLM, особенно большие модели, обучены "додумывать" и создавать связный нарратив. Когда в контексте есть A и не-А, модель не оценивает достоверность, а следует паттернам из своего претренинга.
  • Слепой ретривель: Косинусная близость эмбеддингов не измеряет фактологическую согласованность чанков. Ретривер считает оба документа релевантными — и он прав с точки зрения поиска! Но для генератора это мусор на входе.
  • Промпт-инжиниринг как карго-культ.

Фраза "отвечай только на основе контекста" в промпте — это молитва, а не гарантия. Модели 2025-2026 годов стали лучше следовать инструкциям, но при конфликте данных внутренние механизмы "правдоподобия" все равно перевешивают.

2 Решение: от пассивного пайплайна к контрольному циклу

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

Шаг 1: Гибридный поиск с прицелом на согласованность

Перестаньте полагаться только на векторный поиск. RAG в 2026 году требует гибридного подхода. Комбинируйте dense-эмбеддинги (например, из модели BGE-M3) с ключевыми словами. Но главное — добавьте этап реранкинга на основе перекрестной проверки.

# Псевдокод логики реранкинга
def rerank_for_consistency(query, retrieved_chunks):
    # Используем легкую модель-судью (например, Qwen2.5-Coder-7B) для оценки
    scores = []
    for chunk in retrieved_chunks:
        prompt = f"""Оцени, насколько фрагмент согласуется с другими по фактам.
        Запрос: {query}
        Фрагмент: {chunk}
        Другие фрагменты: {other_chunks}
        Верни оценку от 0 до 1."""
        score = judge_llm(prompt)
        scores.append(score)
    # Пересортируем чанки по убыванию согласованности
    return sorted_chunks

Шаг 2: Динамический промптинг с акцентом на конфликты

Замените статический промпт на адаптивный. Если система детектирует противоречия в извлеченных данных, она должна изменить инструкцию для LLM.

def build_adaptive_prompt(chunks, query):
    if detect_conflict(chunks):
        base_prompt = """В предоставленном контексте есть противоречивая информация.
        Перечисли все варианты, которые найдёшь, и укажи источник (документ 1, документ 2).
        Не пытайся угадать правильный ответ."""
    else:
        base_prompt = "Ответь на вопрос, используя контекст."
    return f"{base_prompt}\nКонтекст: {chunks}\nВопрос: {query}"

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

Шаг 3: Пост-обработка и валидация ответа

Не доверяйте слепо выходу генератора. Добавьте шаг, где другой экземпляр LLM (или тот же, но с другим промптом) проверяет, действительно ли ответ основан на предоставленном контексте. Метод LLM-as-a-judge здесь незаменим.

Интеграция в production: что сломается первым

Добавление этих шагов усложняет пайплайн и увеличивает latency. Вот где вас подстерегут практические проблемы:

Проблема Решение для 2026 года
Рост времени ответа (латенси) Используйте кэширование эмбеддингов и результатов реранкинга для частых запросов. Запускайте легкие модели-судьи на GPU инстансах с инференсом в 5-10 мс (например, используя специализированные инференс-сервисы).
Стоимость вызовов LLM Применяйте каскад моделей: tiny-моделя для детекции конфликтов, большая — только для финальной генерации сложных ответов.
Сложность мониторинга Внедрите трекинг метрик "конфликтности" чанков и "верности ответа контексту" как ключевых KPI, а не только recall. Инструменты вроде WhyLabs или собственные дашборды.

Чего точно нельзя делать

  • Увеличивать количество извлекаемых чанков (k) в надежде, что правильный "перевесит". Это только сильнее запутает модель.
  • Слепо верить в чудодейственность нового эмбеддинг-моделя. Да, модели 2026 года лучше, но они не решают проблему семантического конфликта. Эмбеддинги — слепое пятно RAG.
  • Игнорировать проблему, потому что "на тестовых данных все работает". В реальных данных всегда будут противоречия, устаревшие документы и дубликаты с ошибками.

Вместо выводов: куда смотреть дальше

Классический RAG-пайплайн устарел. Будущее за архитектурами с обратной связью и многоэтапным рассуждением. Обратите внимание на Agentic RAG, где LLM сама управляет процессом поиска и проверки, или на BayesRAG, который оценивает неуверенность модели. Ваша система должна не просто искать и генерировать, а сомневаться. Начните с добавления одного шага реранкинга на согласованность — это даст немедленное улучшение качества ответов на противоречивых данных. А потом двигайтесь к полному roadmap для production RAG в 2026 году.

FAQ по эксперименту

  • Какие модели использовались в эксперименте? Для ретривера — BGE-M3 (актуальная версия на 2026 год), для генератора — DeepSeek-R1 (аналог GPT-4 по возможностям, но с открытыми весами). Для судей — Qwen2.5-Coder-7B.
  • Почему 220 МБ? В репозиторий включены предрасчитанные эмбеддинги для датасета, чтобы эксперимент запускался быстро даже на ноутбуке.
  • Можно ли применить это для мультимодального RAG? Да, принцип тот же: детектировать конфликт между текстовыми описаниями изображений, а затем адаптировать промпт.
  • Что, если конфликтующих документов больше двух? Алгоритм реранкинга должен учитывать кластеризацию чанков по смысловым группам. Это следующая ступень сложности.

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