Как вы уменьшаете галлюцинации в RAG?

Краткий тезис

Галлюцинации в RAG — это ситуация, когда LLM генерирует информацию, которой нет в предоставленном контексте (retrieved documents). Это главная проблема RAG в production, потому что пользователь теряет доверие к системе. Уменьшение галлюцинаций требует многоуровневого подхода: улучшение retrieval (чтобы контекст был релевантным), улучшение промпта (чтобы модель не выдумывала), постобработка (проверка фактов) и настройка генерации (низкая температура). Комбинация этих методов может снизить галлюцинации с 20-30% до 2-5%.

Ключевая идея Галлюцинации — это не бинарная проблема (есть/нет), а спектр. Нужно измерять faithfulness и систематически уменьшать её.


1. Термин: Галлюцинация (Hallucination) в RAG

Что это LLM генерирует утверждение, которое не следует из контекста (retrieved documents), даже если это утверждение само по себе правдиво.

Пример галлюцинации

Контекст (retrieved): "Компания была основана в 2010 году. Штаб-квартира находится в Москве."

Вопрос: "Где находится штаб-квартира компании?"

Ответ LLM: "Штаб-квартира компании находится в Москве, где работает 5000 сотрудников."
                                                              ^^^^^^^^^^^^^^^^^^^^^^^^
                                                              ГАЛЛЮЦИНАЦИЯ (нет в контексте)

Термин «Faithfulness» Метрика, измеряющая долю утверждений в ответе, которые можно подтвердить из контекста. (см. вопрос 16)

Почему LLM галлюцинируют в RAG

ПричинаОбъяснение
Обучение на интернетеLLM обучена отвечать всегда (даже если не знает)
Противоречивый контекстКонтекст содержит и релевантную, и нерелевантную информацию
Плохой промптИнструкция не запрещает галлюцинации явно
Высокая температураБольше случайности → больше галлюцинаций
Слишком сложный вопросLLM пытается "додумать" ответ

2. Уровень 1: Retrieval-level (улучшение качества контекста)

2.1 Увеличить top-k и улучшить retrieval

Идея Чем больше релевантного контекста видит LLM, тем меньше нужно додумывать.

До (плохо

retrieved_chunks = vector_search(query, top_k=3)  # слишком мало

После (лучше

retrieved_chunks = vector_search(query, top_k=10)  # больше контекста
# + reranking (cross-encoder) для отбора лучших

Цифра Увеличение top-k с 3 до 10 может снизить галлюцинации на 30-50%, но увеличивает латенси и стоимость.

Ограничение Слишком большой top-k (>20) может добавить шума, что увеличивает галлюцинации (модель путается в противоречиях).


2.2 Обрезать нерелевантные чанки через threshold

Идея Не передавать в LLM чанки с низкой релевантностью (score ниже порога).

results = vector_search(query, top_k=20)
threshold = 0.7  # cosine similarity

# Оставляем только релевантные чанки
filtered_chunks = [r for r in results if r.score > threshold]

if not filtered_chunks:
    # Если нет релевантных чанков — отказ от ответа
    return "Информация не найдена в документах."

Термин «Dynamic threshold» Порог может меняться в зависимости от запроса (например, для коротких запросов порог ниже).

Цифра Threshold 0.7 отсекает 30-50% чанков с низкой релевантностью, снижая галлюцинации на 20-30%.


2.3 Гибридный поиск (vector + BM25)

Идея Некоторые галлюцинации возникают из-за того, что retrieval не нашёл точные термины. Гибридный поиск улучшает recall.

  1. Что такое гибридный поиск и когда он нужен|6. Что такое гибридный поиск и когда он нужен|6. Что такое гибридный поиск и когда он нужен|6|См. вопрос 6 для деталей.

3. Уровень 2: Prompt-level (инструкции модели)

3.1 Явный запрет галлюцинаций

Плохой промпт (провоцирует галлюцинации

Ответь на вопрос на основе контекста.

Хороший промпт (явный запрет

Ты — ассистент, который отвечает ТОЛЬКО на основе предоставленного контекста.
ПРАВИЛА:
1. Если информация есть в контексте — ответь, используя её.
2. Если информации НЕТ в контексте — НЕ ВЫДУМЫВАЙ. Скажи: "Информация не найдена".
3. Не добавляй свои знания из обучения.
4. Если контекст противоречив — укажи на это.

Контекст: {context}
Вопрос: {question}
Ответ:

Термин «System prompt» Начальная инструкция, которая задаёт поведение модели на всю сессию.


3.2 Запрашивать цитаты (citation)

Идея Принуждать LLM указывать источник каждого факта. Если модель не может найти цитату — она не должна писать этот факт.

Промпт с цитатами

Для каждого утверждения в ответе укажи источник в формате [doc:N], где N — номер документа из контекста.

Пример:
Контекст:
[doc:1] iPhone 15 стоит от 799 долларов.
[doc:2] iPhone 15 имеет 48-МП камеру.

Вопрос: "Сколько стоит iPhone 15 и какая у него камера?"

Ответ: iPhone 15 стоит от 799 долларов [doc:1] и имеет 48-МП камеру [doc:2].

Постобработка Проверяем, что каждая цитата действительно существует в контексте. Если citation указывает на несуществующий документ — это галлюцинация.

Цифра Цитаты снижают галлюцинации на 40-60% (модель реже выдумывает, если нужно предоставить источник).


3.3 Few-shot примеры (примеры правильных ответов)

Идея Показать модели примеры ответов, где она НЕ галлюцинировала.

Промпт с few-shot

Пример 1:
Контекст: "Столица Франции — Париж."
Вопрос: "Какая столица у Франции?"
Ответ: "Столица Франции — Париж." (правильно, только из контекста)

Пример 2:
Контекст: "Собаки — домашние животные."
Вопрос: "Какой любимый цвет у собак?"
Ответ: "Информация не найдена в контексте." (отказ от ответа)

Теперь твоя очередь...

Почему работает Модель учится на примерах — видит, что в случае отсутствия информации нужно отказываться, а не выдумывать.


4. Уровень 3: Generation-level (настройки генерации)

4.1 Self-reflection (модель проверяет себя)

Идея После генерации ответа, модель проверяет, можно ли подтвердить каждое утверждение из контекста.

Pipeline

# Шаг 1: обычная генерация
response = llm.generate(query, context)

# Шаг 2: self-reflection — модель проверяет свой ответ
reflection_prompt = f"""
Проверь, можно ли подтвердить каждое утверждение из ответа, используя контекст.
Контекст: {context}
Ответ: {response}

Выведи для каждого утверждения:
- "VERIFIED" если подтверждается
- "NOT_VERIFIED" если нет

Если есть NOT_VERIFIED, исправь ответ, убрав эти утверждения.
"""

refined_response = llm.generate(reflection_prompt)

# Шаг 3: возвращаем исправленный ответ
return refined_response

Термин «Self-reflection» (саморефлексия Модель анализирует свой собственный вывод и исправляет ошибки.

Цифра Self-reflection снижает галлюцинации на 30-50%, но удваивает количество LLM-вызовов (дороже, медленнее).


4.2 Контрастные промпты (contrastive prompting)

Идея Попросить модель сгенерировать два ответа: один с контекстом, другой без, и сравнить.

# Ответ с контекстом
with_context = llm.generate(query, context=real_context)

# Ответ без контекста (только знания модели)
without_context = llm.generate(query, context="")

# Если ответы сильно различаются — возможно, модель галлюцинирует
# Или: оставляем только те факты, которые есть в with_context, но НЕТ в without_context

Термин «Contrastive decoding» Сравнение распределений вероятностей с контекстом и без для выявления галлюцинаций.


4.3 Низкая температура (temperature)

Идея Галлюцинации возникают чаще при высокой температуре (больше случайности). Для фактологических RAG-задач лучше низкая температура.

TemperatureЭффектРиск галлюцинаций
0.0 (greedy)Детерминированный ответ, один и тот же на один запросНизкий
0.1-0.3Мало случайности, подходит для фактовНизкий-средний
0.5-0.7Баланс, для креативных задачСредний
0.8-1.0Высокая случайность, для креативаВысокий

Рекомендация для RAG temperature = 0.1 или 0.2.

Код

response = llm.generate(query, context, temperature=0.1)

5. Уровень 4: Post-processing (проверка после генерации)

5.1 Проверка именованных сущностей (NER)

Идея Извлечь из ответа все именованные сущности (имена, даты, числа, места) и проверить, есть ли они в контексте.

Термин «NER» (Named Entity Recognition Модель, выделяющая сущности: PERSON (Иван), DATE (2025), LOCATION (Москва), NUMBER (5000).

import spacy

nlp = spacy.load("ru_core_news_sm")

def check_entities(response, context):
    response_entities = [ent.text for ent in nlp(response).ents]
    context_entities = [ent.text for ent in nlp(context).ents]
    
    hallucinated_entities = []
    for entity in response_entities:
        if entity not in context_entities:
            hallucinated_entities.append(entity)
    
    if hallucinated_entities:
        # Удаляем или помечаем галлюцинации
        return remove_entities(response, hallucinated_entities)
    return response

Пример:

Контекст: "Компания основана в 2010 году."
Ответ: "Компания основана в 2010 году Иваном Ивановым."

NER из ответа: ["2010", "Иван Иванов"]
NER из контекста: ["2010"]
→ "Иван Иванов" — галлюцинация, удаляем

5.2 Проверка чисел и дат

Идея Проверить, что числа и даты в ответе присутствуют в контексте.

import re

def check_numbers(response, context):
    # Извлекаем все числа из ответа
    response_numbers = re.findall(r'\d+', response)
    context_numbers = re.findall(r'\d+', context)
    
    for num in response_numbers:
        if num not in context_numbers:
            # Число-галлюцинация
            return f"Контекст не содержит число {num}. Ответ не подтверждён."
    return response

6. Полный пайплайн (рекомендация для production)

def rag_with_hallucination_prevention(query):
    # ===== Уровень 1: Retrieval =====
    chunks = vector_search(query, top_k=15)
    # Фильтрация по threshold
    relevant_chunks = [c for c in chunks if c.score > 0.65]
    
    if not relevant_chunks:
        return "Информация не найдена в документах."  # отказ
    
    # ===== Уровень 2: Prompt =====
    prompt = f"""
    Ты — ассистент. Отвечай ТОЛЬКО на основе контекста.
    Если информации нет — скажи "Информация не найдена".
    Для каждого факта указывай источник [doc:N].
    
    Контекст: {relevant_chunks}
    Вопрос: {query}
    Ответ:
    """
    
    # ===== Уровень 3: Generation =====
    response = llm.generate(prompt, temperature=0.1)
    
    # ===== Уровень 4: Post-processing =====
    # Проверка цитат
    if not check_citations(response, relevant_chunks):
        return "Ответ содержит неподтверждённые утверждения. Пожалуйста, проверьте источники."
    
    # Проверка NER
    response = check_entities(response, relevant_chunks)
    
    return response

7. Сравнение методов (эффективность)

МетодСнижение галлюцинацийДополнительная латенсиДополнительная стоимостьСложность
Увеличение top-k20-30%+20%+20%Низкая
Threshold фильтрация20-30%0%0% (меньше токенов)Низкая
Хороший промпт30-50%0%0%Низкая
Цитаты40-60%0%+10-20% (больше токенов)Низкая
Few-shot20-40%0%+20-50% (токены)Низкая
Self-reflection30-50%+100%+100%Средняя
Низкая temperature10-20%0%0%Низкая
NER проверка10-30%+5%0%Средняя
Комбинация всех70-90%+50-100%+50-100%Высокая

Рекомендация Начать с простых методов (промпт, threshold, низкая temperature) — они дают большой эффект за малые деньги. Self-reflection и NER подключать, если галлюцинации остаются критичной проблемой.


8. Пет-проект для закрепления

Задача Измерить частоту галлюцинаций в вашем RAG и уменьшить их с помощью 3 методов.

Инструменты Python, RAGAS (faithfulness), любой LLM

Шаги

  1. Замерить baseline faithfulness на 50-100 примерах (через RAGAS)
  2. Внедрить 3 метода (в порядке увеличения сложности):
      1. Как бы вы спроектировали RAG-систему для 10 000 документов с разной структурой|1. Как бы вы спроектировали RAG-систему для 10 000 документов с разной структурой|1. Как бы вы спроектировали RAG-систему для 10 000 документов с разной структурой|1|Метод 1 Улучшить промпт (явный запрет + цитаты)
    • 2 Как вы решаете проблему lost in the middle при работе с длинными контекстами|2 Как вы решаете проблему lost in the middle при работе с длинными контекстами|2 Как вы решаете проблему lost in the middle при работе с длинными контекстами|2|Метод 2 Добавить threshold фильтрацию (score > 0.65)
    • 3 Какие стратегии chunking'а вы знаете и когда какую применяете|3 Какие стратегии chunking'а вы знаете и когда какую применяете|3 Какие стратегии chunking'а вы знаете и когда какую применяете|3|Метод 3 Добавить self-reflection (один дополнительный LLM-вызов)
  3. После каждого метода замерить faithfulness снова
  4. Посчитать, насколько улучшилась метрика
  5. Замерить дополнительную латенси и стоимость

Ожидаемый результат Вы увидите, как faithfulness растёт от 0.6-0.7 до 0.85-0.95, и поймёте, какой метод даёт лучший trade-off для вашего случая.


Связь с другими вопросами

ВопросТема
5Оценка retrieval (влияет на контекст)
6Гибридный поиск (улучшает retrieval)
8Обработка запросов без ответа (отказ вместо галлюцинации)
16Faithfulness — метрика галлюцинаций
67Prompt injection (галлюцинации от вредоносных запросов)
96Предотвращение галлюцинаций в production

17. Как вы уменьшаете галлюцинации в RAG|17. Как вы уменьшаете галлюцинации в RAG|17. Как вы уменьшаете галлюцинации в RAG|17 полностью разобран. Переходим к вопросу 18, когда будете готовы|Вопрос 17 полностью разобран. Переходим к вопросу 18, когда будете готовы]]


Навигация