Сравнение RAG, векторного RAG и GraphRAG
Классический векторный RAG, лексикальный безвекторный поиск и GraphRAG: разбиение на части, эмбеддинги, BM25, многопрыжковые графы и ситуации, когда каждый подход оправдан.
Как современные языковые модели отвечают на вопросы, основанные на материалах, которые никогда не использовались в процессе обучения.
RAG в одном предложении
Retrieval Augmented Generation — это именно то, что означает это аббревиатура. Сначала извлекается материал, связанный с вопросом пользователя. Затем модель пишет ответ, используя этот материал вместе с поставленным запросом. Порядок действий: сначала извлечение информации, затем генерация ответа.
Зачем это нужно?
Модели знают только то, что содержалось в данных, использованных для их обучения. Всё остальное для них недоступно.
Представьте себе редкую книгу объемом 3000 страниц, которая почти не встречается в сети и никогда не попадала ни в один корпус данных для обучения. Если спросить модель о ней, ответ будет пустым. Даже если использовать инструменты для сбора информации, результат останется прежним — ничего, что можно было бы извлечь, там нет.
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. Это лишь один из способов поиска. Вариант RAG без векторов сохраняет подход «загрузить, затем сгенерировать», отказавшись от поиска с использованием векторных представлений.
Почему отказываться от векторов? Высокие затраты на создание и обновление векторных представлений, слабая точность совпадения для идентификаторов, чисел и кодов ошибок, а также необходимость дополнительной инфраструктуры для их работы.
Подход без векторов представляет собой целую группу методов, а не единственное решение:
- Лексический поиск. 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 — это не косметический параметр. Реализации (включая open-source решения от Microsoft) различают глобальный и локальный режимы, поскольку это разные стратегии.
Затраты GraphRAG
Экстракция данных — дорогостоящий процесс. Необходимо прочитать весь документ; длинные книги потребляют много токенов; косвенные ссылки («как обсуждалось ранее») могут так и не превратиться в связи между элементами.
Качество графа определяет качество результатов. Плохая экстракция → плохой граф → плохое восстановление информации. Чтобы исправить ситуацию, часто приходится перечитывать всё сначала, что нежелательно в масштабных системах.
На практике следует выбирать инструмент для поиска в зависимости от типа запроса, а не навязывать один и тот же подход ко всем вопросам.
Кратко: используйте классический RAG для семантического поиска, векторные методы — когда требуется точность или меньше ресурсов, а Graph RAG — когда важны взаимосвязи между элементами.
Выбор стиля поиска без догм
Полезная последовательность принятия решений выглядит следующим образом.
Начните с классического векторного RAG, когда ваш корпус данных велик, языки сильно различаются, и обычно вам нужны приблизительные семантические аналоги. Сразу инвестируйте в качество разбиения данных на чанки и процессы обновления векторных представлений — именно они определяют качество результата.
Обратитесь к техникам без векторов, когда важны точные идентификаторы больше, чем парафразирование, когда корпус достаточно мал для использования лексического поиска или работы с длинным контекстом, или когда вы не хотите нести расходы на инфраструктуру для генерации векторов. BM25 и подобные методы не являются «устаревшими» — они идеально подходят для SKU, цитат и строк с ошибками.
Используйте Graph RAG, когда вопросы о продуктах имеют реляционную структуру: кто связан с кем, какая концепция привела к появлению той или иной идеи, какая комьюнити обобщает ту или иную тему. Ожидайте более высоких затрат на индексацию и рассматривайте качество извлечения данных как ключевой фактор успеха.
Многие команды в итоге используют гибридный подход: сначала лексический анализ, затем векторный, а для подмножества многоэтапных доменов — графовый. Главное не выбрать один конкретный метод, а подобрать инструмент, соответствующий реальному типу сбоев.
Практические аспекты, которые игнорируют демонстрации
Расписание переиндексации имеет большое значение. Устаревшие векторные представления тихо снижают качество работы системы RAG даже при неизменном модели. Настройки перекрытия важны, когда соседние окна содержат схожий контент. Фильтры метаданных необходимы, если абоненты никогда не должны видеть части данных друг друга. Наборы для оценки важны, когда вы утверждаете, что решение «лучше», но не имеете меток.
Для Graph RAG необходимо планировать повторные попытки извлечения информации, частичные обновления графа и пересуммирование данных при изменении документов. Для агентного поиска важно определить лимиты использования инструментов и правила таймаута, чтобы любопытная модель не могла исчерпать запас токенов при одном запросе.
Ни одна из этих заметок не является особенно привлекательной. Они определяют разницу между простым диаграмматическим изображением и системой, способной выдержать месяц реальной нагрузки.