Розроблення RAG з векторною базою даних Elasticsearch (kNN)
Elasticsearch з версії 8.x підтримує нативний пошук k-Nearest Neighbors на щільних векторах (поле dense_vector). Для команд, які вже використовують Elasticsearch як пошуковий рушій, це найприроднічий шлях до RAG — без додавання нової інфраструктури. Нативна інтеграція повнотекстового BM25 та векторного пошуку робить ES потужним вибором для гібридного пошуку.
Створення індексу з полем dense_vector
from elasticsearch import Elasticsearch
es = Elasticsearch("http://localhost:9200")
# Створення індексу з маппінгом
index_config = {
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "russian", # Нативна підтримка російської морфології
},
"source": {"type": "keyword"},
"doc_type": {"type": "keyword"},
"page": {"type": "integer"},
"date": {"type": "date"},
"embedding": {
"type": "dense_vector",
"dims": 1536,
"index": True,
"similarity": "cosine",
# Параметри HNSW
"index_options": {
"type": "hnsw",
"m": 16,
"ef_construction": 100,
}
}
}
},
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
}
}
es.indices.create(index="knowledge_base", body=index_config)
Індексування документів
from openai import OpenAI
from elasticsearch.helpers import bulk
openai_client = OpenAI()
def generate_actions(chunks: list):
texts = [c["text"] for c in chunks]
# Пакетні вкладення
response = openai_client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
embeddings = [e.embedding for e in response.data]
for chunk, embedding in zip(chunks, embeddings):
yield {
"_index": "knowledge_base",
"_source": {
"content": chunk["text"],
"source": chunk["source"],
"doc_type": chunk["doc_type"],
"page": chunk.get("page", 0),
"embedding": embedding,
}
}
# Пакетне завантаження
bulk(es, generate_actions(document_chunks))
Гібридний пошук: BM25 + kNN
Elasticsearch підтримує гібридний пошук через knn + query в одному запиті:
def hybrid_search_es(
query: str,
doc_type_filter: str = None,
top_k: int = 5
) -> list:
query_embedding = openai_client.embeddings.create(
model="text-embedding-3-small",
input=query
).data[0].embedding
# Пункт фільтру
filter_clause = []
if doc_type_filter:
filter_clause.append({"term": {"doc_type": doc_type_filter}})
# Гібридний: kNN + BM25 через RRF
body = {
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": query,
"analyzer": "russian"
}
}
},
"filter": filter_clause,
}
},
"knn": {
"field": "embedding",
"query_vector": query_embedding,
"k": top_k * 3, # Розширена множина для злиття
"num_candidates": 100,
"filter": filter_clause,
},
"rank": {
"rrf": {
"window_size": 50,
"rank_constant": 20,
}
},
"size": top_k,
"_source": ["content", "source", "doc_type"],
}
response = es.search(index="knowledge_base", body=body)
return [
{
"text": hit["_source"]["content"],
"source": hit["_source"]["source"],
"score": hit["_score"],
}
for hit in response["hits"]["hits"]
]
Перевага: російська морфологія з коробки
Elasticsearch з аналізатором russian підтримує стемінг російських слів через Snowball. Це критично для частини BM25 гібридного пошуку — запит «договором» знайде документи з «договор», «договоры», «договорам».
# Тест морфологічного аналізу
es.indices.analyze(
index="knowledge_base",
body={"analyzer": "russian", "text": "договором аренды"}
)
# tokens: ["договор", "аренд"] — стемовані форми
Практичний кейс: міграція існуючого Elasticsearch до RAG
Контекст: Компанія використовує ES 8.x як пошуковик для 500K документів. Завдання: додати RAG поверх без зміни інфраструктури.
Кроки:
- Додати поле
embedding(dense_vector, dims=1536) до існуючого маппінгу - Пакетна векторизація існуючих документів (2 дні, 500K × $0.02/1M = $10)
- Переіндексування з новим полем (6 годин)
- Додавання RRF злиття до пошукових запитів
- Рівень RAG поверх пошуку ES
Результати (vs чистий BM25):
- NDCG@5: 0.64 → 0.81
- Recall@10: 0.71 → 0.88
- Latency P95: 85ms → 140ms (гібридний)
- Faithfulness (RAGAS): 0.76 → 0.91
Перехід від чистого BM25 до гібридного kNN+BM25 дав +27% до NDCG без зміни інфраструктури.
Часові рамки
- Додавання векторного поля + переіндексування: 2–5 днів
- Розроблення гібридних пошукових запитів: 3–5 днів
- RAG pipeline та оцінка: 1–2 тижні
- Всього: 2–4 тижні







