LLM Engineering: RAG + реранкеры + оценка — гайд с кодом | AiManual
AiManual Logo Ai / Manual.
04 Июн 2026 Гайд

Практическое руководство по LLM Engineering: RAG, реранкеры и оценка — полный гайд с примерами кода

Пошаговое руководство по сборке продакшн-пайплайна RAG: гибридный поиск, реранкеры, LLM-as-Judge. Примеры кода, грабли и советы инженера.

Реклама
hor_partv1

RAG не панацея

Ты собрал RAG-систему. Эмбеддинги загнал, ChromaDB поднял, GPT-4o сверху прикрутил. Подаёшь вопрос — а модель возвращает не то. Нет, она не галлюцинирует — она просто склеила релевантный кусок с мусором, и контекст пошёл вразнос. Знакомо?

В эксперименте мы выяснили: даже при идеальной индексации ответ может быть неверным из-за того, как LLM интерпретирует несколько документов. Стандартный RAG — это только 60% успеха. Остальное решают реранкеры и грамотная оценка.

В этом гайде я покажу, как собрать цепочку: гибридный поиск → реранкер → LLM-as-Judge. Без заумных слов, с реальным кодом на Python, который можно сразу запустить.

Архитектура: от простого к сложному

Начнём с базы, прокачаем до продакшна. Если у тебя уже есть простая RAG-система — пропускай шаг 1.

1 «Наивный» RAG — планка, ниже которой не опускаться

Загружаем документы, режем на чанки, считаем эмбеддинги, складываем в векторную БД. При запросе — ищем по cosine similarity топ-k, отдаём LLM.

from sentence_transformers import SentenceTransformer
import chromadb

model = SentenceTransformer('all-MiniLM-L6-v2')  # 384-мерный эмбеддинг
client = chromadb.PersistentClient(path='./db')
collection = client.get_or_create_collection('docs')

def index_docs(texts, ids):
    embeddings = model.encode(texts).tolist()
    collection.add(ids=ids, embeddings=embeddings, documents=texts)

def search(query, k=5):
    q_emb = model.encode([query]).tolist()
    results = collection.query(query_embeddings=q_emb, n_results=k)
    return results['documents'][0]

Ошибка: если чанки большие (больше 512 токенов) — релевантность падает. Если мелкие — теряется контекст. Рекомендую размер 256-512 токенов с перекрытием 20-50 токенов. И всегда храни метаданные (источник, заголовок).

2 Гибридный поиск — когда эмбеддинги бессильны

Эмбеддинги отлично ловят семантику, но плохо работают с точными терминами (названиями продуктов, номерами заказов). BM25 — наоборот. Вместе они непобедимы.

from bm25s import BM25
import jieba  # для токенизации на русском (опционально)

# Допустим, у нас чанки в списке
corpus = [...]  # list of strings
tokenized_corpus = [jieba.lcut(doc) for doc in corpus]
bm25 = BM25()
bm25.index(tokenized_corpus)

def hybrid_search(query, k=5, alpha=0.5):
    # Семантический поиск (из шага 1)
    sem_docs = search(query, k=k*2)  # берём больше для слияния
    # BM25 поиск
    query_tokens = jieba.lcut(query)
    scores_bm25, indices = bm25.retrieve(query_tokens, k=k*2)
    bm25_docs = [corpus[i] for i in indices[0]]
    
    # Объединяем и ранжируем по взвешенной сумме
    # (упрощённый вариант — в реальности используй ReRanker)
    combined = list(set(sem_docs + bm25_docs))
    return combined[:k]

Совет: не мучайся с реализацией взвешивания — поставь реранкер. Он сделает единую оценку релевантности и решит, какой документ важнее. Полное руководство по RAG отлично объясняет архитектуру.

Реранкер — убийца шума

После гибридного поиска у тебя может быть 10-20 документов. Скармливать их все LLM — путь к перегрузке контекста и ошибкам. Реранкер (cross-encoder) за доли секунды переранжирует их, оставив 2-3 действительно релевантных.

Почему cosine similarity не годится для финального ранжирования?

Bi-encoder (как all-MiniLM-L6) генерирует эмбеддинг независимо для запроса и документа. Cosine similarity между ними — грубая оценка. Cross-encoder склеивает запрос и документ и пропускает через трансформер — получает точную оценку релевантности. Разница — как смотреть на фото товара против чтения отзыва с вопросом-ответом.

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch

tokenizer = AutoTokenizer.from_pretrained('BAAI/bge-reranker-v2-m3')
model = AutoModelForSequenceClassification.from_pretrained('BAAI/bge-reranker-v2-m3',
                                                           torch_dtype=torch.float16)
model.eval()

def rerank(query, docs, top_k=3):
    pairs = [[query, doc] for doc in docs]
    inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512)
    with torch.no_grad():
        scores = model(**inputs).logits.squeeze(-1)
    ranked = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, _ in ranked[:top_k]]
💡
Реранкер сильно тормозит, если прогонять все чанки. Запускай его после гибридного поиска на топ-20, а не на всей базе. Задержка 100-200 мс — приемлемая цена за качество.

На практике BGE-reranker-v2-m3 (китайский, но мультиязычный) или Cohere Rerank 3 (облачный) дают прирост ~15-20% к точности. Бери open-source, если важна приватность, иначе — Cohere (сслыка Cohere Rerank — пробовал, вменяемый API).

LLM-as-Judge: твой беспристрастный аудитор

Как понять, что пайплайн работает? Метрики вроде Recall@k — слишком грубые. Нужна оценка качества финального ответа. Идея: пусть сама LLM проверит ответ по трём критериям: верность (faithfulness), релевантность (relevance), полезность (helpfulness).

Подробно с подходом можно познакомиться в статье LLM-as-a-judge: как оценивать RAG-системы и находить слабые места.

Промпт для судьи

judge_prompt = """Ты — эксперт по оценке RAG-систем. Пользователь задал вопрос, а система дала ответ на основе контекста.

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

Оцени ответ по шкале 1-5:
1. Верность (Faithfulness): нет ли вымысла? Не противоречит ли контексту?
2. Релевантность (Relevance): отвечает ли на вопрос?
3. Полезность (Helpfulness): даёт ли полную информацию?

Выдай JSON: {{"faithfulness": число, "relevance": число, "helpfulness": число}}.
"""

def judge(question, context, answer):
    response = openai.chat.completions.create(
        model='gpt-4o',
        messages=[{"role": "user", "content": judge_prompt.format(context=context, question=question, answer=answer)}],
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

Предупреждение: LLM-судья может быть предвзят к модели-кандидату. Если используешь GPT-4o как судью для ответов от GPT-4o — получишь завышенные баллы (self-bias). Лучше брать другую модель (Claude 3.5 Sonnet или открытую llama-3.1-70b).

Собираем всё вместе: финальный пайплайн

Теперь соберём цепочку в программу «векторный поиск» → «гибридный поиск» → «реранкер» → «LLM-as-Judge». Пример ниже — упрощён, но отражает логику.

from openai import OpenAI

client_openai = OpenAI()

def rag_pipeline(question):
    # Шаг 1: гибридный поиск (эмбеддинги + BM25)
    candidate_docs = hybrid_search(question, k=15)
    
    # Шаг 2: реранкер
    top_docs = rerank(question, candidate_docs, top_k=3)
    context = "\n---\n".join(top_docs)
    
    # Шаг 3: генерация ответа
    sys_prompt = "Ответь на вопрос, используя только контекст. Если ответа нет — скажи, что не знаешь."
    completion = client_openai.chat.completions.create(
        model='gpt-4o',
        messages=[
            {"role": "system", "content": sys_prompt},
            {"role": "user", "content": f"Контекст:\n{context}\n\nВопрос: {question}"}
        ]
    )
    answer = completion.choices[0].message.content
    
    # Шаг 4: оценка
    scores = judge(question, context, answer)
    return answer, scores, top_docs

Запускаешь на тестовом датасете (50-100 вопросов), собираешь метрики. Если средняя верность ниже 4 — копай в контекст: может, реранкер пропускает шум или чанки плохо нарезаны. Об этом хорошо написано в статье Самовосстанавливающийся RAG — там показано, как фиксить галлюцинации в реальном времени.

Грабли, на которые я наступал

  • Забыл про метаданные. Без указания источника LLM не может проверить факты. Добавляй source в контекст — поможет и судье, и пользователю.
  • Слишком большой контекст. Перегружая LLM 10 документами, ты размываешь внимание. Деградация контекста — реальная проблема, особенно при длинных диалогах.
  • Дорогой судья. Каждая оценка стоит денег. Используй дешёвую модель (gpt-4o-mini или llama-3.1-8b) для предварительного фильтра, и только сложные кейсы отправляй на полную оценку.
  • Не тестируешь на краевых случаях. Пустые запросы, вопросы без ответа — проверь, чтобы система не падала. Delegation Filter подскажет, когда лучше вообще не дёргать LLM.

Что дальше?

Через год-два реранкеры станут встроенной фишкой векторных БД, а LLM-as-Judge — стандартным блоком CI/CD. Но уже сейчас, добавив эти два компонента, ты поднимешь качество RAG с «оно работает» до «оно работает круто».

Не ищи серебряную пулю. Возьми код из статьи, воткни свой датасет, замерь метрики. Увидишь — hybryd + reranker + judge дают +30-40% к F1 по сравнению с голым эмбеддингом. А потом напиши мне в комментах, какие грабли встретил — дополним гайд.

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