promptra
← Все статьи
Гайды7 мин чтения

Embeddings и векторный поиск: полный RAG-стек 2026 для русскоязычных проектов

Глубокий разбор RAG-стека 2026 для русскоязычных проектов: модели embeddings (OpenAI text-embedding-3, Cohere v3, Voyage 3), векторные БД (pgvector, Qdrant, Chroma), стратегии chunking, hybrid search и retrieval evaluation. С реальными цифрами стоимости и Python-кодом через единый шлюз Promptra.

Архитектурная инфографика RAG-стека 2026: блок документов → chunking → embeddings модель → векторная БД, рядом блок query → embedding → поиск top-k → передача в LLM с контекстом, плоский векторный стиль в кремово-терракотовой палитре

RAG (Retrieval-Augmented Generation) — это паттерн, при котором LLM получает релевантный контекст из вашей базы знаний прежде, чем формулировать ответ. Это то, как ChatGPT с подключёнными документами, Notion AI и большинство B2B-чатботов на нейросетях знают вашу специфику. Под капотом — embeddings (текст превращается в вектор), векторная БД (хранит и ищет векторы по похожести), и LLM (получает топ-k найденных кусков и отвечает).

В этом гайде — полный стек 2026 для русскоязычных проектов через единый шлюз Promptra: модели embeddings (OpenAI text-embedding-3, Cohere v3, Voyage 3), векторные БД (pgvector, Qdrant, Chroma), стратегии chunking, hybrid search, и метрики оценки качества retrieval. Цены в рублях по курсу ЦБ, оплата в рублях по договору, полный пакет закрывающих документов.

TL;DR — RAG-стек за 5 компонентов

  1. Chunking — режем документы на куски 500–1500 токенов с overlap или по семантическим границам.
  2. Embedding — превращаем каждый chunk в вектор 1024–3072 dim через embeddings модель.
  3. Vector DB — храним векторы с metadata в Qdrant или pgvector, индекс HNSW для быстрого поиска.
  4. Retrieval — на запрос пользователя считаем его embedding, ищем топ-k ближайших векторов (cosine similarity).
  5. Generation — отдаём найденные chunks как контекст в LLM (Claude Opus 4.7 / GPT-5.5 / Gemini 3.1 Pro), модель отвечает. Эта статья — часть pillar-гида: полный технический гид по LLM API на Python — токены, function calling, streaming, RAG, batch.

С hybrid search и rerank качество retrieve в 2 раза выше чем у чистого vector search.

Шаг 1. Embeddings — какую модель выбрать

Embedding-модель превращает текст в вектор фиксированной длины. Близкие по смыслу тексты получают близкие векторы (по cosine similarity). От качества embeddings зависит всё — никакой LLM не спасёт, если retrieve вернул нерелевантные куски.

Топ-модели 2026 для русского языка:

МодельDimКонтекстЦена $/1MСильные стороны
OpenAI text-embedding-3-large30728K$0.13Флагман, Matryoshka (можно урезать)
OpenAI text-embedding-3-small15368K$0.02Дёшево, baseline, RU adequate
Cohere embed-multilingual-v3.01024512$0.10Лучше всех на multilingual задачах
Voyage 3102432K$0.06Premium качество для retrieval
Qwen-embed-v210248K$0.05Сильная альтернатива на русском

Через Promptra все доступны по OpenAI-совместимому endpoint'у — меняется только model:

from openai import OpenAI

client = OpenAI(
    api_key="sk-promptra-...",
    base_url="https://api.promptra.ru/v1",
)

response = client.embeddings.create(
    model="text-embedding-3-large",
    input=["Текст первого документа.", "Текст второго документа."],
    encoding_format="float",
)

vectors = [d.embedding for d in response.data]
print(f"Получили {len(vectors)} векторов размерности {len(vectors[0])}")

Подробное сравнение моделей и цен — в материале «Embeddings API из России». Спецификация OpenAI embeddings — в официальном гайде; подробности Voyage 3 — на сайте Voyage AI.

Сравнительная инфографика моделей embeddings: 5 блоков-карточек в ряд с подписями «text-embed-3-large 3072 dim», «text-embed-3-small 1536», «Cohere v3 1024», «Voyage 3 1024», «Qwen-embed-v2 1024», у каждой звёздочки качества и цены за 1М в рублях; заголовок «Модели embeddings для русского, 2026»

Шаг 2. Chunking — стратегии разбиения

Документы редко помещаются в один вектор. Нужно разбить на куски (chunks) такого размера, чтобы каждый был семантически целостным и помещался в контекст embedding-модели.

Fixed-size с overlap — baseline

Самый простой подход: режем по N символов с overlap M:

def fixed_chunks(text: str, size: int = 800, overlap: int = 150) -> list[str]:
    chunks = []
    start = 0
    while start < len(text):
        end = min(start + size, len(text))
        chunks.append(text[start:end])
        start += size - overlap
    return chunks

Работает, но режет посреди предложений. Качество retrieve среднее.

Semantic chunking — по предложениям/параграфам

Используем nltk или spacy для разбиения на предложения, потом собираем чанки до целевого размера:

from nltk.tokenize import sent_tokenize

def semantic_chunks(text: str, target_size: int = 800, overlap_sentences: int = 2) -> list[str]:
    sentences = sent_tokenize(text, language="russian")
    chunks = []
    current = []
    current_size = 0

    for sent in sentences:
        if current_size + len(sent) > target_size and current:
            chunks.append(" ".join(current))
            # overlap: берём последние N предложений в следующий chunk
            current = current[-overlap_sentences:]
            current_size = sum(len(s) for s in current)
        current.append(sent)
        current_size += len(sent)

    if current:
        chunks.append(" ".join(current))
    return chunks

Качество retrieve существенно выше — куски всегда целые.

Hierarchical chunking — лучший на 2026

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

from dataclasses import dataclass

@dataclass
class HierarchicalChunk:
    child_text: str   # 200-400 токенов — для embedding и поиска
    parent_text: str  # 1500-2000 токенов — отдаём в LLM
    parent_id: str
    child_id: str

def hierarchical_chunks(text: str) -> list[HierarchicalChunk]:
    parents = semantic_chunks(text, target_size=2000, overlap_sentences=0)
    result = []
    for p_idx, parent in enumerate(parents):
        children = semantic_chunks(parent, target_size=350, overlap_sentences=1)
        for c_idx, child in enumerate(children):
            result.append(HierarchicalChunk(
                child_text=child,
                parent_text=parent,
                parent_id=f"p{p_idx}",
                child_id=f"p{p_idx}-c{c_idx}",
            ))
    return result

Это побеждает на 90% задач — точность поиска от маленьких chunks, богатство контекста от больших. Для технических доков — chunking по заголовкам markdown. Для кода — по функциям/классам. Для диалогов — по поворотам.

Сравнение трёх стратегий chunking: «Fixed-size» — куски одного размера резко обрезанные, «Semantic» — куски по предложениям ровные, «Hierarchical» — большие parents и внутри них мелкие children с подписью «индексируем child, отдаём parent» терракотовая; заголовок «Стратегии разбиения документов»

Шаг 3. Векторная БД — pgvector / Qdrant / Chroma

Сохраняем векторы вместе с metadata (id, source, chunk_text) и ищем по cosine similarity.

pgvector — если у вас уже Postgres

CREATE EXTENSION vector;

CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    source TEXT NOT NULL,
    chunk_text TEXT NOT NULL,
    parent_text TEXT,
    embedding VECTOR(1024)   -- размерность под Voyage 3 / Cohere
);

CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

Запрос top-k:

SELECT id, chunk_text, parent_text, embedding <=> $1 AS distance
FROM documents
ORDER BY embedding <=> $1
LIMIT 10;

Из Python через psycopg или asyncpg. Плюс: ACID, обычные SQL-бэкапы, JOIN с реляционными данными. Минус: на сотнях миллионов векторов производительность падает — для такого масштаба нужен Qdrant.

Qdrant — production-grade open-source

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

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="docs",
    vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)

# upsert
points = [
    PointStruct(
        id=i,
        vector=vector,
        payload={"source": doc.source, "chunk_text": doc.text, "parent_text": doc.parent},
    )
    for i, (vector, doc) in enumerate(zip(vectors, chunks))
]
client.upsert(collection_name="docs", points=points)

# search
hits = client.search(
    collection_name="docs",
    query_vector=query_vector,
    limit=10,
    query_filter=Filter(must=[FieldCondition(key="source", match=MatchValue(value="manual"))]),
)

Qdrant — российская команда, доступен on-prem, до сотен миллионов векторов, фильтрация по metadata из коробки, hybrid search встроенный.

Chroma — для прототипирования

import chromadb

client = chromadb.PersistentClient(path="./chroma_data")
collection = client.create_collection("docs")

collection.add(
    embeddings=vectors,
    documents=[c.text for c in chunks],
    metadatas=[{"source": c.source} for c in chunks],
    ids=[c.id for c in chunks],
)

results = collection.query(
    query_embeddings=[query_vector],
    n_results=10,
)

Единственная зависимость pip install chromadb, in-memory или file-based. Для прода с серьёзным трафиком не годится.

Шаг 4. Hybrid search — vector + BM25

Чистый vector search проигрывает на точных совпадениях имён, кодов, дат. Hybrid комбинирует semantic + keyword:

# В Qdrant — встроенный hybrid через named vectors
from qdrant_client.models import Prefetch, FusionQuery, Fusion

results = client.query_points(
    collection_name="docs",
    prefetch=[
        Prefetch(query=dense_query_vector, using="dense", limit=20),
        Prefetch(query=sparse_query_vector, using="bm25", limit=20),
    ],
    query=FusionQuery(fusion=Fusion.RRF),   # Reciprocal Rank Fusion
    limit=10,
)

RRF (Reciprocal Rank Fusion) — стандартный способ объединять списки. Формула: score = sum(1 / (k + rank_i)), где k обычно 60. Документ, который попал в топ обоих списков — получает больший score, чем тот, что в топе только одного.

Качество retrieve на технических документах с hybrid обычно растёт на 10–20% Recall@10.

Схема hybrid search: запрос «как настроить токен» идёт двумя путями — вверх в «Semantic search → top 20 by cosine», вниз в «BM25 keyword search → top 20 by tf-idf», обе линии сходятся в блок «RRF fusion → top 10», финальная стрелка к «LLM с контекстом»; заголовок «Hybrid: семантика + ключевые слова»

Шаг 5. Rerank — последний штрих качества

Поверх hybrid search ставится reranker: модель, которая для каждой пары (query, chunk) даёт точный relevance score. Это дорого (один inference на каждый retrieved chunk), но даёт лучший top-10 из retrieved 50–100.

Топ-модели rerank:

  • Cohere rerank-multilingual-v3.0 — production-grade, низкая latency, ~$2 за 1M запросов
  • Voyage rerank-2 — premium качество, ~$3 за 1M
  • BGE-reranker-v2-m3 — open-source, можно self-host
# через Cohere SDK поверх обычного retrieve
retrieved = vector_search(query, k=50)
rerank_response = cohere_client.rerank(
    query=query,
    documents=[r.text for r in retrieved],
    top_n=10,
    model="rerank-multilingual-v3.0",
)
final = [retrieved[r.index] for r in rerank_response.results]

Recall@10 после rerank обычно +15–25% над hybrid. Совокупный uplift embedding → +chunking → +hybrid → +rerank — это разница между «работает терпимо» и «работает отлично».

Шаг 6. Generation — отдаём контекст в LLM

Финальный шаг — собираем prompt и вызываем LLM:

def rag_answer(query: str, retrieved_chunks: list[str], model: str = "claude-opus-4-7") -> str:
    context = "\n\n---\n\n".join(retrieved_chunks)
    system = (
        "Ты — ассистент по технической документации. "
        "Отвечай ТОЛЬКО на основе предоставленного контекста. "
        "Если в контексте нет ответа — скажи 'В документации нет ответа на этот вопрос'. "
        "Не выдумывай факты."
    )
    user = f"Контекст:\n{context}\n\nВопрос: {query}\n\nОтветь подробно."

    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": user},
        ],
    )
    return response.choices[0].message.content

Что важно:

  • Жёсткий system message — заставляет модель не выдумывать вне контекста. Это снижает галлюцинации.
  • Указание источников — попросите модель отдавать [source_1]-references на куски, это даёт пользователю верификацию.
  • Длина контекста vs стоимость — каждый retrieved chunk это input-токены. На Opus 4.7 (350 ₽ за 1M input) 10 chunks по 1500 токенов = 15K токенов = 5.25 ₽ на запрос. Если запросов 100K в месяц — 525K ₽. Цены — в «Сравнение цен LLM 2026».

Для дешёвого RAG используют Gemini 3.1 Pro (140/860 ₽) или DeepSeek V4 Pro (30/60 ₽) на маленьких задачах. Для критичных продуктовых ответов — Opus 4.7 или GPT-5.5.

Evaluation — без неё это чёрный ящик

RAG нельзя выкатить в прод без замера качества. Минимальная eval:

  1. Соберите 50–200 пар (query, ids_of_relevant_chunks) вручную или через LLM-as-judge.
  2. Прогоните query → embed → search → top-k, замерьте:
  3. - Recall@k — доля релевантных, попавших в top-k. Цель Recall@10 > 0.85.
  4. - MRR — Mean Reciprocal Rank, насколько высоко стоит первый релевантный. Цель > 0.5.
  5. - NDCG@k — учитывает порядок результатов, премиум-метрика.
  6. Если ниже — меняйте модель embeddings, chunking, добавляйте rerank, тюньте hybrid weight.

Дополнительно — end-to-end eval: даёте LLM-судье (через тот же Opus 4.7 / GPT-5.5) пары (query, generated_answer, ground_truth), спрашиваете «насколько ответ точен и обоснован контекстом». Это позволяет ловить регрессии не только в retrieve, но и в формулировке.

Дашборд retrieval-метрик: 4 карточки в ряд — «Recall@10: 0.87 цель», «MRR: 0.62», «NDCG@10: 0.79», «E2E judge score: 4.3/5», под ними график изменения метрик при апгрейдах: «baseline → +rerank → +hybrid» с восходящей терракотовой линией; заголовок «Evaluation: без неё это чёрный ящик»

Стоимость RAG на 1M документов

Пример экономики для базы 1M документов, средний размер 500 токенов, 10K запросов в день:

СтатьяРасчётСумма
Embedding индексации500M токенов × $0.06 (Voyage 3)~430K ₽ разово
Vector DBQdrant on-prem (4 vCPU, 32GB RAM)~25K ₽/мес
Embedding запросов10K × 50 токенов × 30 дней~650 ₽/мес
Rerank10K × 50 × 30 (Cohere v3)~6500 ₽/мес
LLM generation (Opus 4.7)10K × 15K input + 800 output~2.27 М ₽/мес
LLM на дешёвом (Gemini 3.1 Pro)10K × 15K input + 800 output~700K ₽/мес

Главная статья — это LLM-generation. Если ответы не требуют флагмана — переход с Opus 4.7 на Gemini 3.1 Pro экономит 1.5 М ₽/мес. Через Promptra это смена model в одной строке.

Оплата и закрывающие документы

Все компоненты RAG-стека — embeddings, rerank, LLM generation — оплачиваются на юр.лицо российское юр.лицо, резидент РФ. Сервисная комиссия 5% берётся только при пополнении баланса, на токены наценки нет, цены строго по курсу ЦБ. Полный пакет закрывающих документов (договор-оферта, счёт на оплату, акт оказанных услуг, счёт-фактура, УПД) приходит через ЭДО — Диадок, СБИС, Контур. Подробнее — на странице «Тарифы».

Что дальше

RAG в 2026 — это не один запрос к LLM с приклеенным контекстом. Это связка: chunking → embedding → vector DB → hybrid search → rerank → generation, и evaluation поверх каждого слоя. С правильной архитектурой Recall@10 переваливает 0.9, end-to-end judge score выходит за 4.5/5, и продукт перестаёт галлюцинировать. Полезные следующие шаги: «Function calling и tool use» для агентов-аналитиков поверх RAG, «Async-вызовы и Batch API» для индексации больших баз и «Как считать токены в LLM» для расчёта стоимости context window. Если нужно подобрать модель под ваш RAG или подключить ключ через юрлицо — напишите команде Promptra в Telegram.

> 📚 Главный гайд по теме: Лучшая нейросеть 2026: какую LLM выбрать под задачу — связанные материалы и обзор всей категории.

FAQ

Какая модель embeddings лучше для русского языка в 2026?

По бенчмаркам хорошо работают Cohere embed-multilingual-v3.0 (1024 dim), Voyage 3 (1024 dim, premium), OpenAI text-embedding-3-large (3072 dim). Для дешёвых пайплайнов — text-embedding-3-small или Qwen-embed-v2. Разница в качестве — единицы процентов, выбирайте по цене-скорости-dimensions.

Какую векторную БД взять?

pgvector — если уже Postgres и до 5М векторов. Qdrant — production-grade, российская команда, до сотен миллионов векторов, hybrid из коробки. Chroma — только для прототипирования. Для прода в РФ — Qdrant или pgvector.

Какие стратегии chunking работают?

Hierarchical: parent 1500–2000 токенов индексируется по child 200–400, при retrieve возвращается parent. Побеждает на 90% задач. Также — semantic chunking по предложениям через nltk, для технических доков — chunking по заголовкам markdown.

Что такое hybrid search?

Комбинация семантического поиска (через embeddings) и keyword-поиска (BM25). Объединение через RRF (Reciprocal Rank Fusion). Качество retrieve растёт на 10–20% Recall@10. Qdrant поддерживает hybrid из коробки.

Сколько стоит embeddings для 1M документов?

Около 430K ₽ разовая индексация на Voyage 3. После индексации запросы дешёвые: один query embedding — 50 токенов. Главная статья — LLM-generation, выбор Opus 4.7 vs Gemini 3.1 Pro может разнести бюджет в 3 раза.

Как оценить качество retrieval?

Соберите 50–200 пар (query, relevant_chunks), замерьте Recall@k и MRR. Цель Recall@10 > 0.85, MRR > 0.5. Дополнительно — LLM-as-judge для end-to-end оценки точности и обоснованности ответов.

Финальная инфографика: 6 пронумерованных блоков по диагонали — «1. chunking», «2. embedding», «3. vector DB», «4. hybrid search», «5. rerank», «6. LLM generation», под ними подпись «RAG-стек 2026», восходящая линия Recall с подписями uplift'ов; заголовок «Полный pipeline RAG»