/
Вопросы и ответы
/
RAG
/

Почему RAG врёт и как это фиксить

Почему RAG врёт и как это фиксить

11 часов назад

Никита Вихров

Ответы

0

Почему RAG врёт и как это фиксить

RAG уменьшает галлюцинации — но не убирает их полностью. Система может найти не тот документ, модель может неправильно его интерпретировать, а пользователь получит уверенный неправильный ответ. Разберём конкретные причины и способы их починить.


Причина 1: Поиск нашёл не тот документ

Самая частая проблема. Вопрос пользователя и нужный документ сформулированы по-разному — embedding-поиск нашёл что-то похожее, но не то.

# Вопрос: "Как отменить подписку?"
# Нашёл: документ про "изменение тарифного плана" — близко, но не то

# Диагностика: логируй что нашёл поиск
def rag_with_debug(question: str) -> str:
    relevant_docs = vector_search(question, top_k=3)

    print("=== НАЙДЕННЫЕ ДОКУМЕНТЫ ===")
    for i, doc in enumerate(relevant_docs):
        print(f"{i+1}. [score: {doc['score']:.3f}] {doc['content'][:100]}...")

    context = "\n".join([d["content"] for d in relevant_docs])
    # ... генерация ответа

Как фиксить:

Гибридный поиск — комбинируй semantic search с keyword search:

from rank_bm25 import BM25Okapi

def hybrid_search(question: str, documents: list, top_k: int = 3) -> list:
    # Semantic search
    query_emb = model.encode([question])[0]
    semantic_scores = [cosine_similarity(query_emb, doc["embedding"]) for doc in documents]

    # BM25 keyword search
    tokenized_docs = [doc["content"].split() for doc in documents]
    bm25 = BM25Okapi(tokenized_docs)
    bm25_scores = bm25.get_scores(question.split())

    # Нормализуем и объединяем
    semantic_norm = (semantic_scores - min(semantic_scores)) / (max(semantic_scores) - min(semantic_scores) + 1e-9)
    bm25_norm = (bm25_scores - min(bm25_scores)) / (max(bm25_scores) - min(bm25_scores) + 1e-9)

    combined = 0.7 * semantic_norm + 0.3 * bm25_norm
    top_indices = combined.argsort()[::-1][:top_k]
    return [documents[i] for i in top_indices]

Причина 2: Чанки слишком большие или слишком маленькие

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

# Плохо: разбиваем механически по N символов
def bad_chunking(text: str, chunk_size: int = 500) -> list:
    return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
# Предложение может разрезаться посередине

# Лучше: разбиваем по смысловым границам с перекрытием
def smart_chunking(text: str, chunk_size: int = 500, overlap: int = 100) -> list:
    sentences = text.split(". ")
    chunks = []
    current_chunk = []
    current_size = 0

    for sentence in sentences:
        sentence_size = len(sentence)

        if current_size + sentence_size > chunk_size and current_chunk:
            chunks.append(". ".join(current_chunk))
            # Перекрытие: оставляем последние предложения
            overlap_sentences = []
            overlap_size = 0
            for s in reversed(current_chunk):
                if overlap_size + len(s) < overlap:
                    overlap_sentences.insert(0, s)
                    overlap_size += len(s)
            current_chunk = overlap_sentences
            current_size = overlap_size

        current_chunk.append(sentence)
        current_size += sentence_size

    if current_chunk:
        chunks.append(". ".join(current_chunk))

    return chunks

Причина 3: Модель игнорирует контекст и выдумывает

Даже с правильными документами модель может «съехать» на свои знания.

# Слабый system prompt — модель может уйти в сторону
system_weak = "Используй документацию для ответа."

# Строгий system prompt — явные ограничения
system_strict = """Отвечай ТОЛЬКО на основе предоставленной документации.
Если информации в документации нет — отвечай: "В документации нет ответа на этот вопрос."
Не используй знания, которых нет в документации.
В конце ответа укажи: "Источник: [название документа]"."""

Причина 4: Нет порогового значения релевантности

Поиск всегда находит что-то — даже если это «что-то» совсем не по теме.

def search_with_threshold(question: str, min_score: float = 0.5) -> list:
    results = vector_search(question, top_k=5)

    # Фильтруем нерелевантные результаты
    relevant = [r for r in results if r["score"] >= min_score]

    if not relevant:
        return []  # Лучше сказать "не знаю", чем отвечать по нерелевантному контексту

    return relevant


def answer_or_decline(question: str) -> str:
    relevant_docs = search_with_threshold(question)

    if not relevant_docs:
        return "В документации нет информации по этому вопросу."

    # ... генерация ответа

RAG кажется простым — нашли, вставили, спросили. На практике каждый этап можно сделать плохо, и ошибки складываются. Хорошая RAG-система — это итеративная работа: логируй что ищется, проверяй качество чанков, тестируй пороговые значения.

11 часов назад

Никита Вихров

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845