Learnly

Как устроена RAG-система: путь от вопроса к ответу

Как устроена RAG-система: путь от вопроса к ответу

В предыдущем уроке мы увидели общую схему. Теперь разберём её по компонентам: что именно делает каждый блок, какие у него есть варианты реализации, и какие инженерные решения влияют на качество и стоимость.

RAG-система состоит из двух pipeline:

  • Indexing pipeline (offline, разово или по расписанию), превращает корпус документов в поисковый индекс.
  • Query pipeline (online, на каждый запрос), превращает вопрос пользователя в ответ с цитатами.

Pipeline 1: Indexing (подготовка базы)

[документы]
    │
    ▼
[1. Loaders / parsers]      ── PDF, DOCX, HTML, Confluence, S3, Slack…
    │
    ▼  чистый текст + метаданные
[2. Chunker]                ── recursive / semantic / parent-document
    │
    ▼  чанки 200–800 токенов
[3. Embedder]               ── OpenAI v3 / BGE-M3 / Voyage-3
    │
    ▼  векторы 768–3072 dim
[4. Vector DB writer]       ── pgvector / Qdrant / Weaviate
    │
    ▼
[5. Sparse index writer]    ── Elasticsearch / OpenSearch BM25

Шаг 1. Loaders и parsers

Документы приходят в десятках форматов: PDF, DOCX, XLSX, PPTX, HTML, Markdown, JSON, Confluence/Notion exports, Google Drive, Slack threads, GitHub issues, Salesforce notes.

Реальные инструменты:

  • Unstructured.io (open-source + paid), универсальный парсер с layout-aware PDF, OCR, таблицами.
  • LlamaParse (от LlamaIndex), лучший парсер сложных PDF с таблицами и формулами; платный.
  • PyMuPDF / pdfplumber, легковесные PDF-парсеры на Python.
  • Apache Tika, JVM-based, поддерживает 1000+ форматов.
  • Docling (IBM, 2024), open-source парсер с MLM для понимания layout.

Подводный камень: PDF, самый враждебный формат. В одном PDF могут быть сканы (нужен OCR, Tesseract, AWS Textract, Google Document AI), таблицы (нужен layout-anaware парсер), мультиколоночность, footnotes. У плохого парсера качество RAG падает на 20–40% сразу на этом шаге.

Шаг 2. Chunking (нарезка)

Документ нельзя засунуть в индекс целиком, нужны мелкие куски, которые поместятся в контекст LLM на момент генерации. Стратегии:

Стратегия Описание Когда использовать
Fixed-size Каждые N символов / токенов с overlap 10–20% Быстро, простой baseline
Recursive character Режет по \n\n, \n, . , в порядке приоритета Default в LangChain; универсальный
Sentence-window Чанк = 1 предложение, в индексе хранится с window ±3 предложения Точный поиск, широкий контекст для генерации
Semantic chunking Режет на границах смысловых блоков (по cosine distance соседних предложений) Гетерогенные документы
Parent-document Индексируем мелкие чанки, в LLM передаём родительский документ Точный retrieval + богатый контекст
Markdown / code-aware Учитывает заголовки, code blocks, AST Документация, кодовые базы
Agentic chunking LLM сам делит документ на тематические блоки Дорого, но качественно
Late chunking (Jina, 2024) Эмбеддим весь документ, потом нарезаем эмбеддинги Для длинных контекст-aware embedders

Типичный размер чанка: 256–512 токенов для precision-задач, 800–1200 для exploration. Overlap 10–20% помогает не терять контекст на границах.

Шаг 3. Embedding

Эмбеддер, нейросеть, которая отображает текст в вектор фиксированной размерности (768, 1024, 1536, 3072 dim). Близкие по смыслу тексты получают близкие векторы (cosine similarity).

Топовые модели 2026:

Модель Dim MTEB score Цена / 1M tok Особенности
OpenAI text-embedding-3-large 3072 (truncatable) ~64 $0.13 Default outside-EU
OpenAI text-embedding-3-small 1536 ~62 $0.02 Cheap baseline
Voyage AI voyage-3-large 1024 ~67 $0.18 Лучший public benchmark
Cohere embed-v3 1024 ~64 $0.10 Multilingual, sparse mode
BGE-M3 (BAAI) 1024 ~63 self-hosted Open-source, dense+sparse+multi-vector в одной
Jina jina-embeddings-v3 1024 ~63 self-hosted/API 8K context, мультилингв
Nomic nomic-embed-text-v1.5 768 ~62 open-source Полностью open weights + dataset
multilingual-e5-large-instruct 1024 ~63 self-hosted Топ для русского

Для русского языка часто лучше работают BGE-M3, multilingual-e5-large, paraphrase-multilingual-mpnet, у OpenAI v3 русский слабее, чем английский.

Важный приём, Matryoshka embeddings (OpenAI v3, Nomic): можно обрезать вектор до 256–512 dim с минимальной потерей качества. Это сильно экономит хранилище и ускоряет поиск.

Шаг 4. Vector DB

Хранит векторы и делает ANN (Approximate Nearest Neighbor) поиск по cosine similarity. Топовые движки:

DB Тип Сильная сторона
pgvector Расширение PostgreSQL Уже есть Postgres → не плодить инфраструктуру; HNSW и IVFFlat
Qdrant Standalone (Rust) Лучшие фильтры по метаданным, payload, hybrid search out-of-box
Weaviate Standalone (Go) Modules для embedders, GraphQL, multi-tenancy
Milvus / Zilliz Standalone (C++/Go) Масштаб 1B+ векторов, GPU-индексы
Pinecone Managed-only Серверлесс, простой API
LanceDB Embedded (Rust) Файловое хранение, S3-native, для AI-агентов
Chroma Embedded / standalone Простая для прототипов
Elasticsearch / OpenSearch Search engine Dense + BM25 в одном движке
Vespa Standalone (Yahoo) Tensor-search, ColBERT first-class

Под капотом, индексы HNSW (Hierarchical Navigable Small World) или IVF (Inverted File). HNSW, стандарт для high-recall, IVF, для очень больших баз.

Шаг 5. Sparse index (BM25)

Параллельно векторам почти всегда строится классический полнотекстовый индекс на BM25. Зачем, увидим в query pipeline. Реализации: Elasticsearch, OpenSearch, Tantivy, или даже просто Postgres tsvector.

Pipeline 2: Query (на каждый запрос)

[вопрос пользователя]
        │
        ▼
[1. Query understanding]    ── классификация intent, decomposition, rewriting
        │
        ▼
[2. Retrieval]              ── dense (vectors) + sparse (BM25), параллельно
        │   topK=50 candidates
        ▼
[3. Reranking]              ── cross-encoder, top-50 → top-5
        │
        ▼
[4. Context assembly]       ── шаблон промпта, source IDs, инструкции
        │
        ▼
[5. Generation]             ── LLM пишет ответ
        │
        ▼
[6. Post-processing]        ── валидация цитат, фильтр PII, guardrails
        │
        ▼
[ответ + citations]

Шаг 1. Query understanding

Опциональный, но мощный шаг. LLM-call, который превращает «сырой» запрос в форму, удобную для retrieval:

  • Decomposition, сложный вопрос делится на под-вопросы. «Сравни нашу политику отпусков с прошлогодней» → [«политика отпусков 2026», «политика отпусков 2025»], два независимых retrieval.
  • Rewriting, переформулировка под язык документов. «Как взять отпуск?» → «оформление ежегодного оплачиваемого отпуска».
  • HyDE (Hypothetical Document Embeddings), LLM сначала пишет «гипотетический ответ», и поиск ведётся по нему, а не по короткому вопросу. Качество поиска +5–15%.
  • Intent classification, определить, нужен ли вообще RAG. «Привет» → ответ без поиска.
  • Routing, выбрать, в какой источник идти (FAQ, регламенты, CRM). Снижает шум.

Шаг 2. Retrieval

Гибридный поиск, почти всегда лучше чистого vector-поиска:

# псевдокод hybrid search
dense_results  = vector_db.search(embed(query), top_k=50)
sparse_results = bm25_index.search(query, top_k=50)
fused = reciprocal_rank_fusion(dense_results, sparse_results, k=60)
candidates = fused[:50]

Reciprocal Rank Fusion (RRF), стандартный способ слить два ranking'а:
score(doc) = Σ 1 / (k + rank_i(doc)) для каждого источника.

Гибридный поиск устойчиво даёт +10–25% к recall@10 по сравнению с чистым dense (Anthropic, 2024). Особенно помогает на запросах с конкретными терминами / артикулами / названиями.

Шаг 3. Reranking

Vector search быстрый, но грубоватый: считает «общую похожесть» вопроса и документа отдельно, а потом сравнивает векторы (это bi-encoder).

Reranker (cross-encoder) работает иначе: берёт пару (вопрос, документ) вместе, прогоняет через трансформер, и возвращает score 0..1, насколько документ отвечает на вопрос. Это медленнее (нельзя предвычислить), но точнее на 10–30%.

# псевдокод rerank
reranked = cohere.rerank(
    query=query,
    documents=[c.text for c in candidates],
    top_n=5,
    model="rerank-v3.5"
)
top_chunks = [candidates[r.index] for r in reranked.results]

Топовые rerankers 2026:

  • Cohere rerank-v3.5, managed API, $1 / 1K rerank calls, поддерживает 100+ языков.
  • BGE reranker-v2-m3, open-source, ~600M params, можно self-hosted на GPU.
  • mxbai-rerank-large-v1, open-source, легче BGE.
  • ColBERT-v2 / ColBERTv2.0, late-interaction подход, очень точный, требует своего индекса.
  • Jina reranker-v2-base-multilingual, 278M params, быстрый.

Reranker сокращает шум перед LLM: вместо 50 чанков (≈25K токенов) подаём 5 (≈2.5K). Это критически влияет на качество финального ответа из-за lost-in-middle.

Шаг 4. Context assembly

Шаблон промпта собирается так:

<system>
Ты ассистент по корпоративной базе знаний.
Отвечай ТОЛЬКО на основе предоставленных документов.
Каждое утверждение сопровождай ссылкой [^N], где N, id источника.
Если в документах нет ответа, скажи прямо: «Информации недостаточно».
</system>

<documents>
[^1] (Регламент об отпусках 2026, раздел 3.2): ...текст чанка...
[^2] (HR-FAQ, "Как оформить отпуск"): ...текст чанка...
[^3] (Положение о труде, ст. 14): ...текст чанка...
</documents>

<user>
{вопрос}
</user>

Тонкости, которые сильно влияют:

  • Порядок чанков: самые релевантные, в начало или конец, не в середину (lost-in-middle).
  • Источник перед текстом, облегчает цитирование и снижает галлюцинации.
  • Жёсткая инструкция «не знаешь, скажи», снижает выдумки на 30–60%.
  • Few-shot examples в system prompt с примерами правильного цитирования, дополнительные +5–10%.

Шаг 5. Generation

Тот же LLM-вызов, что в обычном чат-боте, но с собранным контекстом. Параметры:

  • temperature: 0..0.3, детерминизм важнее креативности.
  • max_tokens, ограничить длину; обычно 500–2000.
  • Streaming, обязательно для UX (пользователь видит ответ за 0.5 сек, не за 3).

Выбор модели, компромисс цена/качество:

  • Claude Sonnet 4.6, best для длинного контекста, цитирования, reasoning.
  • GPT-5, sтрого следует инструкциям, JSON mode, function calling.
  • Gemini 2.5 Pro, 2M контекст, мультимодальность.
  • Llama 3.3 70B / Qwen 2.5 72B (self-hosted), when on-prem обязателен.
  • Claude Haiku 4.5 / GPT-5-mini, для быстрых дешёвых ответов на простые вопросы.

Шаг 6. Post-processing

Что делают после ответа:

  • Citation validation, проверить, что каждая [^N] есть в переданных документах; если LLM выдумал ссылку, переcпросить.
  • PII filter, Microsoft Presidio, AWS Comprehend; маскировать персональные данные.
  • Guardrails, NVIDIA NeMo Guardrails, Llama Guard 3, фильтр опасных тем.
  • Hallucination check, отдельный LLM-call: «Содержит ли ответ утверждения, которых нет в документах?»

Что может пойти не так на каждом шаге

Шаг Типичная ошибка Симптом
Loader Парсер ломает таблицы Цифры в ответах неверные
Chunker Граница режет таблицу/список Ответ обрывочный
Embedder Слабая модель для русского Нерелевантные чанки в топ-10
Vector DB HNSW параметры по умолчанию Recall ниже теоретического
Retrieval Только dense, без BM25 Не находит точные термины/артикулы
Reranker Не используется LLM получает шум, ответы «о том же, но не о том»
Prompt Нет инструкции «не знаешь, скажи» Галлюцинации
Generation Слишком много чанков в контексте Lost-in-middle, медленно, дорого

Главное

RAG, не одна технология, а конвейер из 8–10 связанных подсистем. Качество финального ответа = произведение качеств каждой ступени. Слабое звено в любом месте обрушивает всю систему.

Хорошая новость: каждый компонент, сменный. Можно начать с pgvector + OpenAI embeddings + GPT-5 без reranker'а, замерить, и точечно улучшать там, где видны проблемы.

В следующих уроках разберём конкретные техники для повышения качества каждого этапа: что такое HyDE, как работает Contextual Retrieval от Anthropic, когда нужен GraphRAG, и как считать метрики качества всего этого через RAGAS.

AI-тест
1 / 6

Выберите стратегию chunking, оптимизированную для документов с кодом и похожую на разметку Markdown.