Внутрішня структура векторних баз даних для RAG: зберігання фрагментів, алгоритми HNSW та IVF, а також процес переранжування.
Як векторні бази даних RAG зберігають фрагменти, шукають мільйони векторів за допомогою алгоритмів HNSW та IVF, додають гібридний пошук та переранжування, а також порівнюють показники схожості.
Джерела: Inside Vector Databases for RAG: From Chunk Storage to HNSW & IVF Search
Система генерації з підсиленням через пошук мусить відповісти на одне з перших практичних запитань: де знаходяться фрагменти тексту? Найпоширенішою відповіддю, яку використовують більшість продакшн-систем, є розміщення кожного фрагмента безпосередньо в базі даних векторів поруч із його ембеддингом.
Як виглядає збережений запис
У системах, побудованих на FAISS, Pinecone чи Weaviate, кожен запис містить три елементи: вектор ембеддингу, текст фрагмента та певні метадані.
{
"embedding": [0.123, 0.456, ...],
"text": "Transformer models are powerful...",
"metadata": {
"doc_id": "doc1",
"page": 5
}
}
Зберігання фрагмента та його вектора разом має три переваги. Пошук відбувається швидко, оскільки відповідний текст повертається у тій самій відповіді. Архітектура залишається простою, без необхідності звертатися до другого сховища. Крім того, затримка залишається низькою, що має велике значення, коли ШІ мусить чекати на результат у реальному часі.
Процес отримання даних відбувається у п’яти кроках. Користувач ставить запитання, його перетворюють на ембеддинг, база даних знаходить найближчі збережені ембеддинги, відповідний текст фрагмента повертається безпосередньо, а ШІ формує свою відповідь на основі цього тексту. Жодних додаткових пошуків не потрібно.
Чому пошук залишається швидким
Звичайний однорядковий опис полягає у тому, що база даних векторів знаходить схожі ембеддинги за допомогою косинусової схожості, скалярного добутку чи евклідової відстані. Це правильно, але цей опис не враховує важливі інженерні аспекти. Як при наявності мільйонів чи мільярдів векторів запит може повернутися протягом кількох мілісекунд?
Наївний підхід — це сила brute force: порівнювати запит із кожним збереженим вектором. Це забезпечує ідеальну точність, але дуже повільну роботу. Мільйон векторів означає мільйон порівнянь для кожного запиту, і це не масштабується.
У реальних системах використовується пошук найближчого сусіда приблизно (ANN). Ідея полягає спочатку звузити простір пошуку, а потім перевірити лише невелику підмножину векторів, погоджуючись на незначну втрату точності в обмін на значне прискорення. ANN реалізується за допомогою спеціалізованих структур індексування. Наприклад, FAISS надає індекси такого типу, а там також є HNSW та IVF.
HNSW — шарова графіка
HNSW — це Hierarchical Navigable Small World. Це підхід, заснований на графах, і Pinecone та Weaviate використовують його внутрішньо. Кожен вектор стає вузлом, а кожен вузол поєднується із схожими сусідами. Пошук — це прогулянка по цій графіці.
Граф складається з кількох шарів. Верхній шар містить дуже мало вузлів та дає загальний огляд, тоді як нижній шар містить кожен вузол детально. Тут добре підходить порівняння автомагістралей та місцевих вулиць.
Його створення відбувається так: коли вставляється ембеддинг, йому присвоюється випадковий рівень, тож більшість вузлів опиняються лише на нижньому шарі, а лише деякі — на верхньому. На кожному рівні вузол з’єднується зі своїми найближчими сусідами, що вимірюється за допомогою косинуса, скалярного добутку чи евклідової відстані. У результаті утворюється мережа, у якій близькі вектори тісно з’єднані, а віддалені — лише слабко.
Пошук починається на верхньому рівні. Запит знаходить там найкраще відповідне вузол, переходить на наступний рівень від цього вузла та повторює цей процес. Це триває аж до нижнього рівня. На верхніх та середніх рівнях пошук є суто жадібним: переходять до найкращого сусіда та зупиняються, коли ситуація не покращується, після чого спускаються нижче. Нижній рівень є більш дослідницьким. З черги пріоритетів кандидатів, яка починається від вхідного вузла, пошук постійно додає хороші варіанти та одночасно перевіряє кількох сусідів.
На практиці важливі два параметри:
- M — це кількість з’єднань на кожен вузол. Більше значення M підвищує точність пошуку, але потребує більше пам’яті та часу на обробку.
- efSearch — це кількість кандидатів, які досліджуються під час запиту. Більше значення покращує точність, але призводить до більшої затримки.
Простіше кажучи, M впливає на якість індексу, тоді як efSearch жертвує точністю заради швидкості під час пошуку.
IVF, спочатку кластерування
IVF (інвертований індекс файлів) є поширеним у FAISS та використовує зовсім інший підхід. Він не створює граф. Він здійснює кластерування, групуючи схожі вектори та шукаючи лише в відповідних групах. Хорошим прикладом є бібліотека: книги сортуються за категоріями, тож ніхто не переглядає кожну полицю.
Під час додавання даних алгоритм кластерування, такий як k-means, розділяє вектори на N кластерів. Кожен кластер має центроїд, і кожен вектор призначається до найближчого з них. Під час пошуку ембеддинг порівнюється з усіма центроїдами, обирається найближчий кластер, а потім оцінюється кожен вектор у цьому кластері для отримання найкращих результатів.
Ці два методи відрізняються за використанням пам’яті. IVF зберігає менше з’єднань, тому використовує менше ресурсів. HNSW зберігає граф із багатьма ребрами на кожну вершину, тому використовує більше ресурсів.
Де чистий векторний пошук виявляється недостатнім
Ембеддинги фіксують значення, контекст та концептуальну близькість. Вони менш ефективні для точних ключових слів, чисел та ідентифікаторів. Якщо шукати «kafka error code 504», векторний індекс може повернути «kafka network issues» або «timeouts in distributed systems», пропустивши документ, який насправді містить код 504.
Системи RAG у продакшені вирішують цю проблему за допомогою гібридного пошуку, який поєднує семантичне пошукове забезпечення (ембеддинги та алгоритми типу ANN, такі як HNSW чи IVF) із пошуком за ключовими словами для точних зіставлень за допомогою BM25. У типовій архітектурі обидва види пошуку відбуваються на окремих системах, а їхні результати об’єднуються та переранжовуються.
Сучасніші векторні бази даних обробляють гібридний пошук усередині себе, тому друга система не потрібна. Weaviate та Pinecone підтримують цей режим нативно. Він швидко стає стандартом для продакшну, оскільки поєднує розуміння сенсу з точним відповідним пошуком.
Чи не означає гібридний режим зберігання всього двічі?
З зовнішньою налаштуваннястак: одні й ті самі фрагменти часто знаходяться як у векторній базі даних, так і в двигуні ключових слів, наприклад Elasticsearch. Це робиться навмисно. Векторний сховище зберігає індекс HNSW або IVF для семантичної схожості, тоді як двигун ключових слів зберігає зворотний індекс для точного пошуку. Кожен індекс створений для своєї мети, тому це не є дублюванням.
Команди зазвичай тримають єдине джерело інформації, наприклад базу даних або data lake, та синхронізують його в обидві системи за допомогою пайплайнів. Це забезпечує їхню сумісність, водночас зберігаючи швидкість та точність пошуку.
Поновне ранжування топових результатів
Багато інженерів вважають, що робота завершена, як тільки з бази даних векторів отримано топ-K результатів. Це не так. Ці результати є приблизно релевантними, а не ідеально впорядкованими, і може пропускатися детальний рівень релевантності.
Візьмемо запит „kafka error 504 fix“. Топ-K може повернути „Kafka timeout issues“, потім „Distributed system errors“, а потім „Error code 504 gateway timeout“. Третій результат насправді має бути першим, але векторний пошук не завжди це забезпечує.
Поновне ранжування передбачає перегляд отриманих результатів та їх переупорядкування з використанням глибшого аналізу запиту. Бази даних векторів зазвичай не роблять цього самостійно, тому логіка знаходиться у рівні вашого додатку. Існують два основні підходи:
- Поновлене ранжування на основі LLM. Надсилайте запит та фрагменти до LLM та просіть його ранжувати їх за рівнем релевантності. Це легко реалізувати, а модель добре розуміє контекст. Недоліком є повільність через додатковий виклик, а також високі витрати при масштабуванні.
- Крос-енкодер. Це стандарт для високоякісного поновленого ранжування. Модель бере запит та один фрагмент разом як пару та формує оцінку релевантності. Цей процес повторюється для всіх K найкращих кандидатів, після чого вони сортуються за оцінкою. Цей підхід є детермінованим та значно точнішим.
Приклад компактного крос-енкодера з LangChain:
"""Super-simple CrossEncoder reranking example.
Steps:
1. Define a query and a few short documents.
2. Build (query, doc) pairs.
3. Use a CrossEncoder to get a relevance score for each pair.
4. Print raw scores, then print documents sorted by score.
"""
from sentence_transformers import CrossEncoder
def main() -> None:
# 1. Create model
model_name = "cross-encoder/ms-marco-MiniLM-L-6-v2"
print(f"Loading CrossEncoder model: {model_name}\n")
model = CrossEncoder(model_name)
# 2. Query and documents
query = "What is a vector database?"
documents = [
"A vector database stores embeddings and allows similarity search.",
"Relational databases store structured data in tables.",
"FAISS is a library for efficient similarity search of vectors.",
"Vector databases are used in AI applications like RAG.",
]
# 3. Build (query, doc) pairs
pairs = [(query, doc) for doc in documents]
# 4. Get scores
scores = model.predict(pairs)
print("Query:\n " + query + "\n")
print("Raw scores (higher = more relevant):")
for doc, score in zip(documents, scores):
print(f" score={score:.4f} | doc={doc}")
# 5. Sort by score (descending)
ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
print("\nDocuments sorted by cross-encoder score:\n")
for rank, (doc, score) in enumerate(ranked, start=1):
print(f"Rank {rank}: score={score:.4f}")
print(f" {doc}\n")
if __name__ == "__main__":
main()
Output
****************************************************
Query:
What is a vector database?
Raw scores (higher = more relevant):
score=8.4591 | doc=A vector database stores embeddings and allows similarity search.
score=-7.1590 | doc=Relational databases store structured data in tables.
score=1.0106 | doc=FAISS is a library for efficient similarity search of vectors.
score=7.0736 | doc=Vector databases are used in AI applications like RAG.
Documents sorted by cross-encoder score:
Rank 1: score=8.4591
A vector database stores embeddings and allows similarity search.
Rank 2: score=7.0736
Vector databases are used in AI applications like RAG.
Rank 3: score=1.0106
FAISS is a library for efficient similarity search of vectors.
Rank 4: score=-7.1590
Relational databases store structured data in tables.
Вимірювання схожості
Якість пошуку тихо залежить від того, як обчислюється близькість векторів. Три показники охоплюють майже все.
Косинусова схожість враховує кут між двома векторами, іншими словами, лише напрямок. Дві стрілки, що вказують у одному напрямку, є дуже схожими, а протилежні стрілки — абсолютно різними, незалежно від довжини. Формула: cos(θ) = A·B / (‖A‖‖B‖). Для запиту „Kafka error 504“ документ про „Обробку помилок Kafka“ отримає високий бал (близько 0,9), а документ про „Індексацію баз даних“ — низький (близько 0,2). Цей показник підходить для ембеддингів та є стандартним вибором у технологіях типу RAG, оскільки важливий зміст, а не величина. Спробуйте запит = [1, 1], docA = [2, 2] та docB = [1, 0]: docA вказує у тому ж напрямку, що й запит, тому його косинусова схожість буде ідеальною, навіть якщо він довший.
Скалярний добуток поєднує напрямок та величину: A·B = |A| × |B| × cos(θ). Вектори більшої величини вважаються важливішими, а вектори, що спрямовані в одному напрямку, — більш схожими.
Евклідова відстань вимірює пряму відстань між точками. Чим менша відстань, тим більша схожість. Її переважно використовують для розв’язання задач кластеризації.
Як усе взаємопов’язано
Хороший підхід RAG — це не просто база даних векторів, приєднана до LLM. Напрямок у ембеддингу несе значення, а величина — силу сигналу. FAISS, Pinecone та Weaviate використовують алгоритми HNSW та IVF для пошуку у великих масштабах. Після цього якість залежить ще від кількох факторів:
- ретельно діліть документи на частини, щоб зберегти контекст
- використовуйте гібридний пошук для виявлення семантичних та ключових збігів
- переоптимізуйте ранжування за допомогою крос-кодеру чи LLM
Усі етапи виглядають так: дані, розділення на частини, ембеддинги, індексування, пошук, переранжування, формулювання запитів, LLM, відповідь.