RAG шпаргалка архитектора: код, метрики, 8 граблей продакшена 2026 | AiManual
AiManual Logo Ai / Manual.
17 Июн 2026 Гайд

RAG от А до Я: шпаргалка архитектора с кодом, метриками и 8 граблями продакшена

Полное руководство по production RAG: чанкинг, гибридный поиск, реранкинг, метрики Ragas, код и 8 граблей, которые сломают ваш пайплайн.

Реклама
partv2

Ваш RAG работает только в демо? Добро пожаловать в клуб

Я пересмотрел десятки RAG-пайплайнов. Девять из десяти умирают при переходе из ноутбука в продакшн. Не потому что модели плохие. А потому что архитектура собрана на коленке: чанки без смысла, эмбеддинги без контекста, поиск без гибрида. Знакомо?

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

Внутренности буду показывать кодом. Актуальным на июнь 2026. Никакого вчерашнего дня.

Чанкинг — это не про нарезку колбасы

Самый частый вопрос в чатах: "Какой размер чанка выбрать? 512 токенов? 1024?". А потом удивляются, что ответы LLM — каша. Чанкинг — это семантическая граница, а не механический split.

Ошибка: Простой RecursiveCharacterTextSplitter со статическим размером. Теряется смысл абзацев, таблиц, списков. Результат — контекстная грязь.

В 2026 году стандарт — семантический чанкинг. Разбиваем текст по границам предложений, абзацев, секций, используя эмбеддинги для поиска точек разрыва. Вот пример с библиотекой LlamaIndex (версия 0.12+):

from llama_index.core.node_parser import SentenceSplitter
from llama_index.core import Document

# Плохо: статический размер
text = "...длинный документ..."
splitter_old = SentenceSplitter(chunk_size=1024, chunk_overlap=200)
nodes_old = splitter_old.get_nodes_from_documents([Document(text=text)])
# Результат: разрыв посреди таблицы

# Хорошо: семантический чанкинг с запасом
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
splitter = SemanticSplitterNodeParser(
    buffer_size=1, 
    embed_model=embed_model,
    breakpoint_percentile_threshold=95
)
nodes = splitter.get_nodes_from_documents([Document(text=text)])
# Разрывы там, где меняется тема

Семантический чанкинг берет скользящее окно, вычисляет сходство между соседними предложениями, и режет в точках резкого падения косинусной близости. Работает неидеально на коротких текстах, но для документации, статей, код-базы — must have.

💡
Если работаете с PDF или таблицами — мультимодальный RAG фреймворк RAG-Anything умеет вытаскивать таблицы целиком без потери структуры. Не надо костылей.

Эмбеддинги и хранилище: три кита, на которых всё держится

Выбор модели эмбеддингов в 2026 — это компромисс между латентностью и качеством. Мои фавориты: Voyage-3-large (лучшее качество, 300$/млн токенов), Cohere Embed v3 (многоязычный, 100$/млн), BAAI/bge-m3 (бесплатно, неплохо).

Векторное хранилище. Забудьте про FAISS в продакшене — он не умеет гибридный поиск из коробки. Qdrant (люблю за простоту), Milvus (для масштаба 100M+ векторов), Chroma (прототипирование).

Вот пример подключения Qdrant с гибридным индексом (sparse + dense):

from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, SparseVectorParams, Distance

client = QdrantClient(host="localhost", port=6333)

client.create_collection(
    collection_name="docs",
    vectors_config={
        "dense": VectorParams(size=1024, distance=Distance.COSINE),
    },
    sparse_vectors_config={
        "sparse": SparseVectorParams(index=None),
    }
)

Гибридный поиск — это когда вы ищете и семантически, и по ключевым словам. Я использую fastembed от Qdrant, который генерирует и dense, и sparse эмбеддинги на лету. Дальше — weighted sum или RRF.

from qdrant_client import QdrantClient
from qdrant_client.models import (
    HybridFusion, FusionQuery, PrefetchQuery,
)

prefetch = [
    PrefetchQuery(
        query=vector_dense,
        using="dense",
        limit=20
    ),
    PrefetchQuery(
        query=vector_sparse,
        using="sparse",
        limit=20
    )
]

query = FusionQuery(
    prefetch=prefetch,
    fusion=HybridFusion(alpha=0.7)  # вес dense vs sparse
)
results = client.query_points(
    collection_name="docs",
    query=query,
    limit=10
)

Совет. Если у вас много специфической терминологии (юридические, медицинские тексты) — обязательно добавьте sparse. Иначе запросы вроде "исключительная лицензия" превратятся в поиск по "лицензия" и потеряют нюанс.

Реранкинг — второй проход, который превращает мусор в золото

Векторный поиск возвращает 20-30 кандидатов. Среди них всегда есть шум. Реранкер (cross-encoder) проходит по парам (запрос, чанк) и выставляет точную оценку релевантности. Он медленнее, но гораздо точнее.

Лучшие на июнь 2026: Cohere Rerank v3 (дата-центричный, отлично работает с русским), BAAI/bge-reranker-v2-m3 (бесплатно, но требует GPU).

from cohere import Client

co = Client(api_key="YOUR_API_KEY")

query = "Как настроить KPI для отдела продаж?"
candidates = ["текст чанка1", "текст чанка2", ...]

response = co.rerank(
    model="rerank-v3.5",
    query=query,
    documents=candidates,
    top_n=3
)

for hit in response.results:
    print(hit.index, hit.relevance_score)

Реранкер обязателен, если вы отдаете пользователю топ-3 чанка. Без него — 30% шанс, что второй или третий чанк нерелевантен. С ним — единицы процентов.

Метрики Ragas: как не врать самому себе

Большинство RAG-систем тестируют "на глаз". Задали три вопроса, посмотрели ответы — "вроде норм". Это самообман. Нужны объективные метрики.

Библиотека Ragas на 2026 год — стандарт. Четыре ключевые метрики:

  • Faithfulness (верность) — не выдумывает ли LLM факты, которых нет в чанках. 0.9+ это хорошо.
  • Answer Relevancy (релевантность ответа) — ответ соответствует вопросу, а не уходит в дебри.
  • Context Precision (точность контекста) — насколько первые чанки содержат ответ. Если ответ во втором чанке — метрика падает.
  • Context Recall (полнота контекста) — все ли нужные факты извлечены.
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset

questions = ["Что такое RAG?"]
answer = "RAG это метод дополнения LLM внешними знаниями."
contexts = [["RAG (Retrieval-Augmented Generation) использует поиск по базе знаний для обогащения промпта."]]

data = Dataset.from_dict({
    "question": questions,
    "answer": [answer],
    "contexts": contexts
})

result = evaluate(
    dataset=data,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
print(result)

Грабли: Ragas использует LLM для оценки (по умолчанию GPT-4o-mini). Если ваша модель-судья — та же, что и в RAG, метрики будут завышены. Берите другую модель для оценки, например, Claude 4 Haiku.

8 граблей продакшена (и как не наступить)

1 Грабли: Одинаковые чанки для всех типов запросов

Факт: для фактоида ("Когда родился Пушкин?") нужны мелкие чанки. Для аналитики ("Сравните подходы к RAG") — крупные. Решение: мульти-чанкинг. Храните чанки разного размера, на этапе поиска подбирайте под тип запроса.

2 Грабли: Игнорирование перекрытия (overlap)

Без overlap вы теряете контекст на стыках. 15-20% — минимум. Лучше сливать чанки в раннере с помощью sliding window.

3 Грабли: Хранение метаданных только для отладки

Метаданные (источник, дата, автор) должны активно использоваться в фильтрации запроса. Пользователь спросил "новые правила?". Значит, отфильтровать по дате > 2025. Без фильтрации поиск найдет устаревшие документы.

4 Грабли: Использование одной модели эмбеддингов для всего

Код, текст, таблицы — разные домены. Лучше натренировать адаптеры или использовать мультимодальные эмбеддинги (например, гибридный RAG с AST и графами знаний из Ragex).

5 Грабли: Забыть про кеширование запросов

Одна и та же фраза от 10 пользователей — 10 дорогих поисков. Поставьте Redis-кеш на уровне эмбеддингов или результатов реранкинга. latency упадет в 2-3 раза.

6 Грабли: Подавать в LLM слишком много контекста

Окно 128k токенов — не значит, что надо пихать 127к. Это убивает точность. Эксперименты показывают: оптимально 3-5 чанков (около 4-6k токенов). Реранкинг должен оставлять топ-3. Больше — шум.

7 Грабли: Не учитывать latency поиска

Добавили гибридный поиск, реранкинг, а суммарно — 5 секунд. Пользователь уйдет. Решение: кэшировать частые запросы, стримить ответ LLM параллельно с поиском (предсказание следующего чанка), использовать быстрые модели эмбеддингов на CPU (ONNX).

8 Грабли: Не мониторить качество в продакшене

Метрики Ragas считаются разово — и забываются. В 2026 стандарт — онлайн-мониторинг. Собирайте логи запросов, ответов, релевантность от пользователей (лайк/дизлайк) и пересчитывайте faithfulness раз в час. Если метрика упала — алерт в PagerDuty.

Собираем production-пайплайн: код

Склеиваю всё вместе на базе LangChain 0.3+. Акцент на надежность и скорость.

import asyncio
from langchain_core.documents import Document
from langchain_qdrant import QdrantVectorStore
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI

# Сначала загружаем документы с семантическим чанкингом
from langchain_text_splitters import SemanticChunker
from langchain_community.embeddings import HuggingFaceEmbeddings

chunker = SemanticChunker(
    HuggingFaceEmbeddings(model_name="BAAI/bge-small-en-v1.5"),
    breakpoint_threshold_type="percentile",
    breakpoint_threshold_amount=95
)

docs = chunker.split_documents(raw_documents)

# Сохраняем в Qdrant с dense + sparse
from langchain_qdrant import FastEmbedSparse

store = QdrantVectorStore.from_documents(
    docs,
    embedding=HuggingFaceEmbeddings(model_name="BAAI/bge-m3"),
    sparse_embedding=FastEmbedSparse(model_name="Qdrant/bm25"),
    location=":memory:",  # или url
    collection_name="production_rag",
)

# Гибридный поиск
retriever = store.as_retriever(search_type="hybrid", search_kwargs={"k": 20})

# Реранкинг
rerank = CohereRerank(model="rerank-v3.5", top_n=3)
compressor = ContextualCompressionRetriever(base_compressor=rerank, base_retriever=retriever)

# QA цепочка
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
qa = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=compressor,
    return_source_documents=True
)

response = qa({"query": "Что такое Retrival-Augmented Generation?"})
print(response["result"])

Запустили — работает? Нет. Теперь добавьте метрики.

from ragas.integrations.langchain import evaluate_langchain
from ragas.metrics import faithfulness, context_recall

result = evaluate_langchain(
    qa_chain=qa,
    testset=test_questions,
    metrics=[faithfulness, context_recall]
)
print(result)
# Если faithfulness < 0.8 — меняйте промпт или чанкинг
📌
Если вы строите RAG для юридических или узкоспециализированных доменов — обязательно используйте графы знаний LightRAG. Они ловят неявные связи, которые обычный поиск пропустит.

RAG умрет. Да здравствует Retrieval.

Не ждал? А зря. В 2026 году тренд четкий: LLM с большими окнами (1M+ токенов) убивают необходимость в RAG для коротких контекстов. Но retrieval остается — как слой, который подгружает актуальные данные. Сама генерация уходит в агентов, которые сами решают, когда искать, а когда ответить из памяти. Мой совет: не зацикливайтесь на RAG как на пайплайне. Стройте систему, которая умеет выбирать между поиском и генерацией, переключаться между разными источниками, переспрашивать пользователя.

А грабли? Их все равно будет 8. Или 12. Потому что продакшн — это сплошные грабли. Хотите меньше? Тестируйте метрики до деплоя, мониторьте после, и не бойтесь переписывать чанкинг с нуля. Удачи.

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