Порівняння RAG, векторного RAG та GraphRAG
Класичний векторний RAG, лексичне пошукове забезпечення без векторів та GraphRAG: чанкування, ембеддинги, BM25, багатоетапні графи та ситуації, коли кожен підхід є доцільним.
Як сучасні моделі мови відповідають на запитання, пов’язані з матеріалом, який ніколи не використовувався під час навчання.
RAG у одному реченні
Retrieval Augmented Generation — це саме те, що означає ця абревіатура. Спочатку завантажується матеріал, пов’язаний із запитанням користувача. Потім модель формулює відповідь, використовуючи цей матеріал та посилання. Порядок дій: спочатку отримання інформації, потім її обробка.
Навіщо це потрібно?
Моделі знають лише те, що було в їхньому навчальному датасеті. Усе інше для них недоступне.
Уявіть собі рідкісну книгу обсягом 3 000 сторінок, яка майже не зустрічається в Інтернеті та ніколи не потрапляла до жодного навчального корпусу. Якщо запитати про неї модель, ви отримаєте порожні відповіді. Навіть якщо використати інструменти для збору даних, результат залишиться таким самим — немає нічого, що можна було б знайти.
RAG заповнює цю прогалину. Завантажте PDF у потік обробки даних. Під час запиту вийміть найбільш релевантні розділи та додайте їх як контекст. Завдання моделі скорочується до: відповісти на запит на основі цього тексту, використовуючи свої мовні навички.
Не потрібна додаткова налаштування. Просто правильний контекст у правильний час.
Основа потоку обробки даних
Спочатку відбувається індексування, а воно починається з розділення тексту на частини.
Стратегії розділення
PDF із кількома тисячами сторінок не підходить для зберігання у векторному сховищі як єдиний блок. Його потрібно розділити, щоб кожну частину можна було окремо зберігати та отримувати.
Поширені способи розділення:
За сторінкою. Одна сторінка → один блок (3 000 сторінок → 3 000 блоків). Ефективно, коли потрібні точні та швидкі результати.
За абзацом. Більш дрібне розділення на межах абзаців. Може створювати враження більшої чіткості, але розміри сильно коливаються (200 токенів поруч із 2 000). Нерівні довжини призводять до нерівномірних ембеддингів та потайки погіршують процес пошуку.
Фіксовані вікна. Константний бюджет токенів — зазвичай 512 — незалежно від місця розділення. Однакові розміри забезпечують більш порівнянні ембеддинги. Якщо немає впевненості, більшість команд обирають саме цей підхід.
from langchain_text_splitters import CharacterTextSplitter
from langchain_core.documents import Document
def perform_fixed_size_chunking(document, chunk_size=1000, chunk_overlap=200😞
"""
Performs fixed-size chunking on a document with specified overlap.
Args:
document (str): The text document to process
chunk_size (int): The target size of each chunk in characters
chunk_overlap (int): The number of characters of overlap between chunks
Returns:
list: The chunked documents with metadata
"""
# Create the text splitter with optimal parameters
text_splitter = CharacterTextSplitter(
separator="\n\n",
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len
)
# Split the text into chunks
chunks = text_splitter.split_text(document)
print(f"Document split into {len(chunks)} chunks")
# Convert to Document objects with metadata
documents = []
for i, chunk in enumerate(chunks):
doc = Document(
page_content=chunk,
metadata={
"chunk_id": i,
"total_chunks": len(chunks),
"chunk_size": len(chunk),
"chunk_type": "fixed-size"
}
)
documents.append(doc)
return documents
# Example usage
if __name__ == "__main__":
# Create the dummy document
document = create_dummy_document()
# Process with fixed-size chunking
chunked_docs = perform_fixed_size_chunking(
document,
chunk_size=1000,
chunk_overlap=200
)
# Display results
print("\n----- CHUNKING RESULTS -----")
print(f"Total chunks: {len(chunked_docs)}")
# Print an example chunk
print("\n----- EXAMPLE CHUNK -----")
middle_chunk_idx = len(chunked_docs) // 2
example_chunk = chunked_docs[middle_chunk_idx]
print(f"Chunk {middle_chunk_idx} content ({len(example_chunk.page_content)} characters):")
print("-" * 40)
print(example_chunk.page_content)
print("-" * 40)
print(f"Metadata: {example_chunk.metadata}")
# For integration with Databricks Vector Search
print("\nThese documents are ready for embedding and storage in Databricks Vector Search")
print("Example next steps:")
print("1. Create embeddings using the Databricks embedding endpoint")
print("2. Store documents and embeddings in Delta table")
print("3. Create Vector Search index for retrieval")
Ембеддинги
Ембеддинг перетворює текст (слово, речення, сторінку) на щільний високовимірний вектор, який кодує значення так, що схожі ідеї збираються в групи.
Чотири етапи в рамках RAG
- Сторона корпусу. Ембеддити кожен фрагмент; зберігати індекс та вектор.
- Сторона запиту. Ембеддити запит користувача, щоб запит мав семантичний «відбиток».
- Пошук. Шукати в базі найближчих сусідів — зазвичай за косинусовою схожістю.
Де знаходяться вектори
Це спеціалізований сховище для ембеддингів, а не загальна реляційна чи об’єктна база даних. Поширеними є AlloyDB, Pinecone та Qdrant; багато команд використовують pgvector у PostgreSQL.
Де мають проблеми звичайні векторні системи RAG
- Обрізання може призводити до ігнорування сенсу. Суміжні фрагменти можуть розходитися; більша перекриття іноді допомагає, але не є універсальним рішенням.
- Схожість може не враховувати парафрази — фрази на кшталт „продажі знизилися“ та „компанія в стані занепаду“ можуть не опинятися поруч у векторному просторі.
- Багатоетапні факти втрачають значення, коли причина та наслідок знаходяться в різних фрагментах, а отримується лише один з них.
- Створення, зберігання, індексування та переіндексування ембеддингів є дорогим процесом.
Також: база даних векторів — це не синонім RAG. Це лише один із способів пошуку. Vectorless RAG зберігає підхід «завантажити, потім створити», відмовляючись від пошуку за допомогою ембеддингів.
Чому уникати векторів? Високі витрати на створення та оновлення ембеддингів, слабка здатність до точного пошуку для ідентифікаторів, чисел та кодів помилок, а також необхідність додаткової інфраструктури для їх функціонування.
Vectorless — це родина методів, а не єдиний підхід:
- Лексичний пошук. BM25, Postgres
tsvector, Elasticsearch — точні терміни краще працюють для SKU, цитат та рядків журналу, ніж нечітка семантика.
from rank_bm25 import BM25Okapi
def vectorless_retrieve(query, corpus_chunks, top_k=3):
"""
Lexical retrieval over raw text chunks - no embeddings, no vector DB.
"""
tokenized_corpus = [chunk.lower().split() for chunk in corpus_chunks]
bm25 = BM25Okapi(tokenized_corpus)
tokenized_query = query.lower().split()
scores = bm25.get_scores(tokenized_query)
ranked = sorted(zip(corpus_chunks, scores), key=lambda x: x[1], reverse=True)
return [chunk for chunk, score in ranked[:top_k]]
- Пошук за допомогою агентів/інструментів. Відсутній попередній індексування; модель самостійно аналізує, викликає API пошуку чи відкриває потрібні розділи за потреби, як це робить кодувальний агент у репозиторії. Пошук з використанням розумових процесів у реальному часі.
Обмеження без векторів
Ключові слова все ще не враховують парафрази — іноді навіть гірше, ніж векторні представлення. Агентні цикли збільшують затримку та кількість токенів на запит. Великі корпуси все одно вимагають добре структурованого векторного індексу. Методи без векторів зазвичай є ефективними на невеликих/середніх масштабах або тоді, коли точність важливіша за нечіткість.
GraphRAG
Класичний підхід RAG може спричиняти плутанину, розриваючи зв’язок між причиною та наслідком у різних фрагментах тексту. Методи без векторів замінюють векторні представлення на ключові слова чи довгий контекст. Жоден з цих підходів не моделює взаємозв’язки між ідеями. Graph RAG призначений для подолання цього недоліку.
Замість питання «Який фрагмент знаходиться найближче?» поставте питання «Як пов’язані ці концепції?» Відповіддю буде граф знань.
Індексування — розширення графа
Спочатку пропустіть етап розділення на фрагменти/ембеддинги. Пропустіть документ через модель, яка виділяє ентитети та взаємозв’язки. Результатом будуть вузли та ребра.
У тій довгій теоретичній книзі можуть з’явитися вузли на кшталт Маркс, Енгельс, «Капітал», «Маніфест комуністичної партії», додаткова вартість, діалектичний матеріалізм, а ребра — на кшталт «був автором», «співавторство», «представляє концепцію».
Ентитети стають вузлами; взаємозв’язки — ребрами. Ви картаєте значення, а не розрізаєте сторінки.
Згрупуйте тісно пов’язані вузли у спільноти (метод Лейдена є популярним), а потім узагальніть кожну спільноту за допомогою ще одного проходження моделлю.
Працюйте на двох рівнях:
- Вузли — для конкретних фактів та прямих зв’язків
- Спільноти — для тем та узагальнень
Вузькі запитання → вузли. Широкі запитання на кшталт „Основні ідеї?“ → узагальнення спільноти. Один індекс, два режими пошуку.
Запитування — прогулянка графом
Запитання: „Хто співпрацював з Марксом, і що вони написали разом?“
Стандартний RAG має труднощі: співавторство знаходиться в одному фрагменті, а твори — на сотнях сторінок далі; потрібно сподіватися, що ембеддинги збігатимуться.
Graph RAG працює так:
- Виявити Маркса як вузол-анкор
- Завантажити вузол та краї Маркса
- Пройти за лінією „співпрацював з“ → Енгельс
- Пройти за лінією „написав“ → Маніфест, Стан робітничого класу та пов’язані твори
Направлені краї зберігають взаємозв’язки цілими. Причина та наслідок, які у класичному RAG розділяються, стають пов’язаними вузлами. Таку багатоетапну структуру важко чисто обробити як у базових, так і у безвекторних системах RAG.
Об’єднати вузли, краї та узагальнення спільноти в контекст; генерувати результат як зазвичай.
from graphrag import GraphRAGPipeline
# Indexing — runs once
pipeline = GraphRAGPipeline(llm="claude-3", graph_store="neo4j")
pipeline.index(documents=["book.pdf"])
# Under the hood: entity extraction → graph build → community detection → summaries
# Querying
result = pipeline.query(
"Who co-wrote with Marx and what did they write together?",
mode="global" # uses community summaries for broad questions
# mode="local" # uses node-level traversal for specific facts
)
print(result.answer)
print(result.sources) # returns actual nodes + edges used, fully traceable
mode — це не косметичний параметр. Різні реалізації (включаючи відкритий стек Microsoft) розрізняють глобальний та локальний режими, оскільки це різні стратегії.
Витрати GraphRAG
Екстракція є дорогою процедурою. Потрібно прочитати весь документ; довгі книги споживають багато токенів; неявні посилання (“як обговорювалося раніше”) можуть так і не стати краями графа.
Якість графа впливає на загальну якість. Погана екстракція → поганий граф → погане пошукове функціонування. Часто для виправлення ситуації доводиться перечитувати все заново, що є проблемою при великих обсягах даних.
На практиці вибирайте інструмент пошуку залежно від типу запиту, а не примушуйте всі запити проходити одним і тим самим шляхом.
Підсумок: використовуйте класичний RAG для семантичного пошуку, безвекторні методи — коли потрібна точність або менше інфраструктури, а Graph RAG — коли ключовим є визначення взаємозв’язків.
Вибір стилю пошуку без догм
Корисна послідовність прийняття рішень виглядає ось так.
Почніть із класичного векторного RAG, коли ваш корпус великий, мова сильно варіюється, і зазвичай потрібні приблизні семантичні сусіди. Вкладіть зусилля у якість розділення тексту на частини та процеси оновлення векторних представлень з самого початку — саме ці фактори визначають якість.
Використовуйте техніки без векторів, коли точні ідентифікатори важливіші за парафразу, коли корпус достатньо малий для лексичного пошуку або роботи з довгим контекстом, або коли ви не хочете нести витрати на інфраструктуру для створення векторів. BM25 та подібні методи не є „старомодними“ — вони є ідеальними інструментами для SKU, цитат та рядків з помилками.
Використовуйте Graph RAG, коли запитання щодо продукту має взаємозв’язковий характер: хто пов’язаний з ким, яка концепція запровадила певну ідею, яка спільнота узагальнює певну тему. Очікуйте вищих витрат на індексацію та розглядайте якість вилучення інформації як ключовий фактор.
Багато команд використовують гібридний підхід: спочатку лексичний аналіз, потім векторний, а для підмножини багатоетапних доменів — графовий підхід. Справа не в тому, щоб обрати один конкретний метод. Справа в тому, щоб підібрати інструмент знаходження інформації, який підходить саме до способу виникнення проблем, який ви фактично бачите.
Оперативні нотатки, які ігнорують демонстрації
Часові плани переіндексації мають значення. Старі ембеддинги тихо погіршують якість векторного RAG навіть тоді, коли сама модель залишається незмінною. Налаштування перекриття важливі, коли сусідні вікна мають спільний зміст. Фільтри метаданих важливі, коли орендарі ніколи не повинні бачити чужі фрагменти даних. Набори для оцінки мають значення, коли ви стверджуєте про „кращі“ результати без використання міток.
Для Graph RAG необхідно планувати повторні спроби вилучення інформації, часткові оновлення графу та переусумовлювання даних у разі змін документів. Для агентного пошуку потрібно враховувати ліміти на використання інструментів та політики таймауту, щоб цікава модель не могла витратити всі ваші токени на один запит.
Жодна з цих приміток не є гламурною. Саме вони визначають різницю між діаграмою та системою, яка витримує місяць реального трафіку.