Зачем вам это? Потому что векторы — это дорого и медленно
Вы построили RAG-систему. Она работает. Но каждый запрос к векторной базе — это 50-200 мс ожидания, пока GPU посчитает косинусное сходство. Ваш сервер нагружен, как грузовик в гору. А точность все равно скачет. Знакомо?
В 2026 году специализированные векторные хранилища (Pinecone, Weaviate, Qdrant) стали стандартом де-факто. Но они создали новую проблему — избыточную сложность. Зачем держать отдельный кластер для векторов, если у вас уже есть Elasticsearch или OpenSearch для логов и текстового поиска? Они умеют в семантику. Про это почему-то молчат.
Это не призыв удалить все векторы. Это способ сделать поиск быстрее, дешевле и проще в эксплуатации, особенно когда ваши данные — это не только научные статьи, но и техдокументация, переписка, код.
Серьезно? Elasticsearch для семантического поиска?
Да. И вот что изменилось к 2026 году:
- Elasticsearch 9.x и OpenSearch 3.x имеют встроенную поддержку dense vector fields и приближенного поиска k-NN. Больше не нужны отдельные плагины.
- Появились легкие модели для эмбеддингов, которые работают на CPU быстрее, чем вы успеваете произнести "трансформер". Например, BGE-M3-Compact или GTE-Qwen2-1.5B — они дают качество, близкое к большим моделям, при размере в 5-10 раз меньше.
- Гибридный поиск (BM25 + векторное сходство) стал штатной функцией. Вы можете взвешивать результаты, комбинировать запросы и не выбирать что-то одно.
Суть подхода проста: используйте то, что уже есть в вашем стеке. Elasticsearch/OpenSearch — это не просто поиск по логам. Это полноценный движок для information retrieval, который пережил хайп вокруг векторов и вышел обновленным. Если вы читали наш материал про безвекторный RAG, то это его практическая реализация с использованием промышленных инструментов.
1 Готовим Elasticsearch: индекс для гибридного поиска
Забудьте про отдельный сервис для векторов. Создаем один индекс, который будет хранить и текст, и его векторное представление. Вот mapping для Elasticsearch 9.4 (актуален на март 2026).
{
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "russian"
},
"vector": {
"type": "dense_vector",
"dims": 384,
"index": true,
"similarity": "cosine"
},
"metadata": {
"type": "object"
}
}
},
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 0
}
}
}
Обратите внимание на "dims": 384. Это размерность эмбеддингов для легкой модели all-MiniLM-L12-v3 (да, она все еще в строю в 2026, но есть и новее). Если используете BGE-M3-Compact, размерность будет 1024. Главное — соответствие модели и mapping.
2 Индексация: генерируем эмбеддинги на лету или заранее?
Тут два пути. Первый — классический: предрассчитать эмбеддинги для всех документов и загрузить в индекс. Второй — использовать ingest pipeline с Painless скриптом для генерации векторов на стороне Elasticsearch (если у вас стоит ML-нода). Но я рекомендую первый вариант для контроля.
Вот как это выглядит на Python с использованием SentenceTransformers. Возьмем актуальную на 2026 год легкую модель из Hugging Face Hub (партнерская ссылка).
from sentence_transformers import SentenceTransformer
from elasticsearch import Elasticsearch
import numpy as np
# Используем компактную модель, оптимизированную для CPU
# На март 2026 актуальна версия 2.3.0 фреймворка
model = SentenceTransformer('BAAI/bge-m3-compact', device='cpu')
# Ваши документы
documents = [
{"text": "Как настроить Kubernetes кластер для production", "category": "devops"},
{"text": "Оптимизация запросов PostgreSQL с использованием индексов", "category": "database"},
]
# Генерация эмбеддингов
texts = [doc["text"] for doc in documents]
embeddings = model.encode(texts, normalize_embeddings=True)
# Подключение к Elasticsearch
# Для OpenSearch используйте from opensearchpy import OpenSearch
es = Elasticsearch("http://localhost:9200")
# Индексация
for i, doc in enumerate(documents):
es.index(
index="rag_documents",
document={
"text": doc["text"],
"vector": embeddings[i].tolist(),
"metadata": {"category": doc["category"]}
}
)
Почему BGE-M3-Compact? Потому что она дает качество на уровне больших моделей, но в 5 раз быстрее на CPU. А если у вас совсем мало ресурсов, присмотритесь к all-MiniLM-L6-v4 (размерность 384) — она стала стабильнее в последних релизах.
3 Поисковый запрос: гибрид BM25 и векторов
Вот где начинается магия. Мы не просто ищем по векторам. Мы комбинируем лексический поиск (который отлично ловит конкретные термины) и семантический (который понимает смысл). В Elasticsearch 9.4 для этого есть запрос hybrid или простая комбинация через bool.should.
def hybrid_search(query, es_index="rag_documents", top_k=5):
# 1. Генерируем эмбеддинг для запроса
query_embedding = model.encode(query, normalize_embeddings=True).tolist()
# 2. Формируем тело запроса
search_body = {
"size": top_k,
"query": {
"hybrid": {
"queries": [
{
"match": {
"text": {
"query": query,
"boost": 0.7 # Вес для BM25
}
}
},
{
"knn": {
"field": "vector",
"query_vector": query_embedding,
"k": top_k,
"num_candidates": 100,
"boost": 0.3 # Вес для векторного поиска
}
}
]
}
}
}
# 3. Выполняем запрос
response = es.search(index=es_index, body=search_body)
return [hit["_source"] for hit in response["hits"]["hits"]]
Коэффициенты boost (0.7 для текста, 0.3 для вектора) — это отправная точка. Настройте их под свои данные. Если у вас много синонимов или жаргона — увеличьте вес векторов. Если важны точные формулировки — поднимите BM25.
Не повторяйте ошибку, которую я видел в трех проектах подряд: не используйте векторный поиск для запросов типа "ошибка 404". Это лексический запрос, и BM25 справится в 10 раз быстрее. Векторы хороши для смысла: "как исправить проблему с доступностью сервиса".
4 Интеграция с RAG пайплайном
Теперь нужно встроить этот поиск в вашу RAG-систему. Допустим, у вас есть цепочка на LangChain или LlamaIndex. Вот пример с минимальными изменениями.
from langchain.llms import Ollama # Ollama 0.6.x в 2026
from langchain.prompts import ChatPromptTemplate
# Ваша функция гибридного поиска (см. выше)
retrieved_docs = hybrid_search("Как настроить репликацию в PostgreSQL?")
# Формируем контекст
context = "\n".join([doc["text"] for doc in retrieved_docs])
# Промпт с учетом контекста
prompt_template = ChatPromptTemplate.from_messages([
("system", "Ты — экспертный ассистент. Ответь на вопрос, используя только предоставленный контекст. Если в контексте нет ответа, скажи 'Не знаю'."),
("human", "Контекст: {context}\n\nВопрос: {question}")
])
prompt = prompt_template.format(context=context, question=question)
# Запрос к локальной LLM, например, через Ollama
llm = Ollama(model="qwen2.5:7b", temperature=0) # Актуальная компактная модель на 2026
response = llm.invoke(prompt)
Если вы используете более сложную архитектуру, например, с re-ранкерами или декомпозицией запросов, то гибридный поиск станет только первым этапом. Но даже он один дает прирост точности на 15-25% по сравнению с чистым векторным поиском на разнородных данных.
Ошибки, которые убьют вашу производительность
- Использование огромных эмбеддингов (1024+ размерности) без необходимости. Каждая лишняя размерность — это память и время. Для большинства задач хватит 384-512. Тестируйте.
- Отсутствие фильтрации по метаданным. Elasticsearch позволяет фильтровать результаты до или после векторного поиска. Если вы ищете "ошибка аутентификации" только в документах по API, добавьте
filterв запрос. Иначе получите мануалы по настройке сети где-то в результатах. - Индексация без чанкинга. Не загружайте целые PDF-файлы как один документ. Разбивайте на логические части (абзацы, разделы). Поиск по 10-20 предложениям работает точнее.
- Игнорирование настройки анализаторов. Как уже говорил, без правильной обработки русского языка ваш BM25 будет бесполезен.
А что насчет OpenSearch?
OpenSearch — это форк Elasticsearch с открытой лицензией. На момент марта 2026 года, версия 3.2 практически догнала Elasticsearch по функциональности для RAG. Плагин k-NN встроен по умолчанию. Если вы предпочитаете полностью open-source стек без коммерческих лицензий — выбирайте OpenSearch. API почти идентичен.
Главное отличие в экосистеме: у Elasticsearch больше готовых интеграций с облачными провайдерами, у OpenSearch — активное сообщество и плагины для специфичных задач. Для внутренних проектов я чаще выбираю OpenSearch. Для корпоративных — Elasticsearch с официальной поддержкой.
Когда этот подход не сработает?
Есть сценарии, где специализированные векторные базы все еще лучше:
- У вас миллиарды векторных эмбеддингов (да, именно миллиарды). Специализированные базы оптимизированы для ultra-large scale.
- Вам нужен поиск по мультимодальным эмбеддингам (картинки, аудио, видео). Elasticsearch/OpenSearch работают с векторами, но их сила — в тексте.
- Ваша команда уже глубоко вложилась в экосистему Pinecone или Weaviate, и переписывание кода обойдется дороже, чем экономия на инфраструктуре.
Но для 95% проектов RAG, где данные — это тексты (до 100 миллионов документов), Elasticsearch/OpenSearch — это разумный, производительный и простой в сопровождении выбор. Вы избавляетесь от лишнего сервиса, сокращаете latency и получаете контроль над всем пайплайном.
Попробуйте. Начните с одного индекса. Сравните latency и точность с вашим текущим решением. Скорее всего, вы удивитесь. А если упретесь в ограничения — всегда можно добавить дополнительные этапы фильтрации или ранжирования. Удачи.