Porównanie RAG, RAG bez wektorów oraz GraphRAG
Klasyczny RAG wektorowy, wyszukiwanie bez wektorów leksykalnych oraz GraphRAG: dzielenie na fragmenty, embeddingi, BM25, grafy wieloetapowe i sytuacje, w których każde podejście okazuje się przydatne.
Jak dzisiejsze modele językowe odpowiadają na podstawie materiałów, które nigdy nie pojawiły się podczas treningu.
RAG w jednym zdaniu
Retrieval Augmented Generation to dokładnie to, co oznacza ta skrótowość. Najpierw pobiera się materiały związane z pytaniem użytkownika, a następnie model pisze odpowiedź, wykorzystując te materiały wraz z instrukcjami. Sekwencja działań to najpierw pobieranie danych, a dopiero potem generowanie odpowiedzi.
Dlaczego to konieczne?
Modele znają tylko to, co zawierało ich dane treningowe. Wszystko inne jest dla nich niewidoczne.
Załóżmy rzadką książkę liczącą 3000 stron, która prawie w ogóle nie pojawia się w internecie i nigdy nie trafiła do żadnego korpusu treningowego. Jeśli zapytasz o nią model, otrzymasz puste odpowiedzi. Nawet jeśli użyjesz narzędzi do pobierania danych, nadal nie uzyskasz żadnych informacji – nie ma nic, co dałoby się wydobyć.
RAG wypełnia tę lukę. Załaduj PDF do procesu przetwarzania. Podczas wyszukiwania pobierz najbardziej istotne fragmenty i dołącz je jako kontekst. Zadanie modelu sprowadza się do: udzielania odpowiedzi na podstawie tego tekstu, wykorzystując umiejętności językowe do sformułowania odpowiedzi.
Nie wymagane jest dokładne dostosowywanie modelu. Wystarczy odpowiedni kontekst we właściwym momencie.
Skelet procesu przetwarzania
Najpierw następuje indeksowanie, a ono zaczyna się od dzielenia tekstu na fragmenty.
Strategie dzielenia na fragmenty
PDF liczące tysiące stron nie powinno być przechowywane w bazie wektorowej jako jeden duży plik. Trzeba je podzielić, aby każdy fragment mógł być osobno przechowywany i wykorzystywany.
Powszechne sposoby dzielenia:
Na stronę. Jedna strona → jeden fragment (3 000 stron → 3 000 fragmentów). Skuteczne, gdy chce się uzyskać dokładne i precyzyjne wyniki.
Na paragraf. Dokładniejsze cięcia przy granicach paragrafów. Może to sprawić wrażenie większej precyzji, ale rozmiary są bardzo zróżnicowane (200 tokenów obok 2000). Nierówne długości powodują nierównomierne embeddingi i potajemnie psują funkcjonowanie systemu wyszukiwania.
Stałe okna. Stałe limity tokenów — zazwyczaj 512 — niezależnie od miejsca cięcia. Jednolite rozmiary zapewniają bardziej porównywalne embeddingi. Gdy nie ma pewności, większość zespołów wybiera tę opcję.
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")
Embeddingi
Embedding przekształca tekst (słowo, zdanie, stronę) w gęsty wektor wysokowymiarowy, który koduje znaczenie, dzięki czemu podobne idee grupują się razem.
Cztery fazy w RAG
- Strona korpusu. Wstawia embedding do każdego fragmentu tekstu; przechowuje indeks oraz wektor.
- Strona zapytania. Wstawia embedding do wpisu użytkownika, aby żądanie miało semantyczny „odpis”.
- Wyszukiwanie. Szuka w bazie najbliższych sąsiadów — zwykle na podstawie podobieństwa kosinowego.
Gdzie znajdują się wektory
To specjalistyczne magazyny dla embeddingów, a nie ogólne bazy danych relacyjnych lub obiektowych. Powszechne są AlloyDB, Pinecone i Qdrant; wiele zespołów używa pgvector w PostgreSQL.
Gdzie tradycyjne podejście RAG z wektorami napotyka trudności
- Cięcia mogą ignorować znaczenie. Powiązane okna mogą być oddzielone; większe nakładanie się czasami pomaga, ale nie jest rozwiązaniem na wszystko.
- Podobieństwo może pomijać parafrazy — „sprzedaż spadła” w porównaniu z „przedsiębiorstwo jest w upadku” może nie znaleźć się blisko siebie we przestrzeni wektorowej.
- Fakty wieloetapowe zawodzą, gdy przyczyna i skutek znajdują się w różnych fragmentach, a pobierany jest tylko jeden z nich.
- Budowa, przechowywanie, indeksowanie i ponowne indeksowanie embeddingów jest kosztowne.
Również: baza danych wektorowych nie jest synonimem RAG. Jest to tylko jeden typ backendu do wyszukiwania. Wersja RAG bez wektorów zachowuje model „pobierz, a następnie utwórz”, eliminując jednocześnie wyszukiwanie za pomocą embeddingów.
Dlaczego unikać wektorów? Koszt tworzenia i ponownego indeksowania embeddingów, słabe zachowanie przy dokładnym dopasowaniu identyfikatorów, numerów i kodów błędów oraz konieczność utrzymania dodatkowej infrastruktury do ich obsługi.
Rozwiązania bez wektorów stanowią całą rodzinę technologii, a nie jeden konkretny rozwiązanie:
- Wyszukiwanie leksykalne. BM25, Postgres
tsvector, Elasticsearch — dokładne terminy są skuteczniejsze niż niejasna semantyka w przypadku SKU, cytatów i linii z logów.
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]]
- Wyszukiwanie za pomocą agentów/narzędzi. Brak wstępnego indeksowania; model analizuje treść, wywołuje API wyszukiwania lub otwiera odpowiednie sekcje na żądanie, podobnie jak agent programistyczny przegląda repozytorium. Wyszukiwanie w czasie rzeczywistym, oparte na rozumowaniu.
Ograniczenia modeli bez wektorów
Słowa kluczowe nadal nie radzą sobie z parafrazami — czasem gorzej niż modele wektorowe. Pętle agentywne zwiększają opóźnienie i liczbę tokenów na zapytanie. Ogromne korpusy nadal wymagają dobrze skonstruowanego indeksu wektorowego. Modele bez wektorów zazwyczaj przeważają w małych/średnich skali lub gdy dokładność jest ważniejsza od niejednoznaczności.
GraphRAG
Klasyczny RAG może mylić przyczynę z skutkiem w różnych fragmentach tekstu. Modele bez wektorów zastępują wektory słowami kluczowymi lub dużymi fragmentami kontekstu. Żadne z nich nie modeluje wzajemnych powiązań między ideami. Graph RAG adresuje tę lukę.
Zamiast pytać „który fragment jest najbliżej?”, zapytaj „jak te koncepcje są ze sobą powiązane?”. Odpowiedzią jest graf wiedzy.
Indeksowanie — rozwijanie grafu
Najpierw pomiń fragmenty/wbudowania. Prześlij dokument przez model, który wyodrębnia entity i relacje. Wynikiem są węzły i krawędzie.
W przypadku tej długiej książki teoretycznej możesz otrzymać węzły takie jak Marx, Engels, Kapitał, Manifest Komunistyczny, wartość dodatkowa, materializm dialektyczny, a krawędzie takie jak napisał, współnapisał, wprowadza-koncepcję.
Entity stają się węzłami; relacje stają się krawędziami. Mapujesz znaczenie, a nie dzielisz strony.
Zgrupuj ściśle powiązane węzły w społeczności (popularny jest model Leiden), a następnie streszcz każdą społeczność za pomocą kolejnego przetworzenia modelu.
Działaj na dwóch poziomach:
- Węzły dla konkretnych faktów i bezpośrednich powiązań
- Społeczności dla tematów i streszczeń
Krótkie pytania → węzły. Szerokie pytania dotyczące „głównych idei?” → streszczenia społeczności. Jeden indeks, dwa tryby wyszukiwania.
Zadawanie zapytań — przemierzanie grafu
Pytanie: „Kto współtworzył dzieła z Marksem i co napisali razem?”
Klasyczne RAG ma trudności: współautorstwo znajduje się w jednym fragmencie, a prace są setki stron dalej; trzeba mieć nadzieję, że embeddingi będą zgodne.
RAG oparty na grafie działa w następujący sposób:
- Zidentyfikować Marksa jako punkt odniesienia
- Naładować węzeł i krawędzie związane z Marksem
- Pójść ścieżką „współtworzył z” → Engels
- Pójść ścieżką „napisał” → Manifest, Stan klasy robotniczej, powiązane prace
Kierunkowe krawędzie utrzymują relacje w nienaruszonym stanie. Przyczyna i skutek, które w klasycznym RAG są rozdzielone, stają się połączonymi węzłami. Taki wzorzec wielokrotnych skoków jest trudny do prawidłowego przetworzenia zarówno w prostym RAG, jak i w RAG bez wektorów.
Zebrawszy węzły, krawędzie oraz streszczenia społeczności, tworzy się kontekst; wynik generuje się jak zwykle.
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 nie ma charakteru estetycznego. Implementacje (włącznie z otwartym stackiem Microsoftu) rozróżniają podejście globalne od lokalnego, ponieważ są to różne strategie.
Koszty GraphRAG
Ekstrakcja jest kosztowna. Konieczne jest przeczytanie całego dokumentu; długie książki zużywają wiele tokenów; implicite odniesienia („jak omówiono wcześniej”) mogą nigdy nie stać się krawędziami grafu.
Jakość grafu ogranicza jakość wyników. Zła ekstrakcja → zły graf → słabe rezultaty wyszukiwania. Rozwiązania często polegają na ponownym przeczytaniu wszystkiego, co jest problematyczne w skali większej.
W praktyce należy wybrać narzędzie do wyszukiwania w zależności od typu zapytania, zamiast narzucać jeden konkretny sposób na każde pytanie.
Podsumowanie: używaj klasycznego RAG do wyszukiwania semantycznego, rozwiązań bez wektorów, gdy potrzebujesz dokładności lub mniej zasobów infrastrukturalnych, oraz Graph RAG, gdy kluczowe są relacje między elementami.
Wybór stylu wyszukiwania bez dogmatów
Pożyteczna sekwencja decyzyjna wygląda w ten sposób.
Zacznij od klasycznego RAG wektorowego, gdy twój korpus jest duży, języki są bardzo zróżnicowane i zazwyczaj potrzebujesz przybliżonych sąsiadów semantycznych. Inwestuj od razu w jakość dzielenia na fragmenty oraz procesy aktualizacji wektorów; to one decydują o jakości.
Zastosuj techniki niewektorowe, gdy dokładne identyfikatory są ważniejsze niż parafraza, gdy korpus jest na tyle mały, by można było użyć wyszukiwania leksykalnego lub długiego kontekstu, albo gdy nie chcesz ponosić kosztów infrastruktury wektorowej. BM25 i podobne rozwiązania nie są „przestarzałe” — to odpowiednie narzędzia do SKU, cytatów i ciągów błędów.
Zastosuj Graph RAG, gdy pytanie dotyczące produktu ma charakter relacyjny: kto był powiązany z kim, który koncept wprowadził jaką ideę, która społeczność podsumowuje dany temat. Spodziewaj się wyższych kosztów indeksowania i traktuj jakość ekstrakcji jako kluczowy element.
Wiele zespołów ostatecznie stosuje podejście hybrydowe: pierwszy etap to analiza leksykalna, drugi – wektorowa, a dla podzbioru domen wieloetapowych używa się grafów. Nie chodzi o wybór jednej konkretnej metody, lecz o dopasowanie narzędzia do rzeczywistego sposobu występowania błędów.
Uwagi operacyjne pomijane przez prezentacje
Rozkłady ponownego indeksowania są istotne. Stare embeddingi potajemnie pogarszają jakość systemu RAG opartego na wektorach, nawet jeśli model pozostaje niezmieniony. Ustawienia pokrycia są ważne, gdy sąsiednie okna zawierają podobne znaczenia. Filtry metadanych są konieczne, gdy użytkownicy nie powinni widzieć danych innych użytkowników. Zestawy oceny są istotne, gdy twierdzimy o „lepszej” efektywności bez użycia etykiet.
Dla Graph RAG należy planować ponowne próby wydobywania informacji, częściowe aktualizacje grafów oraz ponowne streszczanie treści w przypadku zmian w dokumentach. W przypadku agentic retrieval trzeba uwzględnić limity użycia narzędzi oraz zasady czasowe, aby ciekawy model nie mógł zużyć całej ilości tokenów przy jednej zapytaniu.