Architektura RAG: indeksowanie i pobieranie
Uczestnicy poznają dwie fazy RAG: fazę indeksowania offline, w której dokumenty są dzielone na fragmenty, osadzane i przechowywane, oraz fazę pobierania online, która znajduje odpowiedni kontekst dla każdego zapytania.
Architektura RAG: indeksowanie i pobieranie to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
RAG ma dwa odrębne etapy
System RAG działa w dwóch zasadniczo różnych etapach, uruchamianych w różnym czasie. Etap indeksowania offline przetwarza dokumenty jednorazowo (lub po ich zmianie) i przygotowuje indeks umożliwiający wyszukiwanie. Etap pobierania online działa w czasie rzeczywistym dla każdego zapytania użytkownika. Zrozumienie tego podziału jest niezbędne do projektowania systemów, które są zarówno szybkie podczas obsługi zapytań, jak i łatwe w utrzymaniu w dłuższej perspektywie.
Etap indeksowania: krok pierwszy — wczytanie
Indeksowanie rozpoczyna się od wczytania dokumentów, czyli odczytania surowych plików z systemów źródłowych. Dokumentami mogą być pliki PDF, Word, strony HTML, pliki Markdown, rekordy baz danych lub dowolne źródła tekstu. Każdy dokument jest wczytywany do pamięci jako zwykły tekst, z zachowaniem struktury tam, gdzie jest to możliwe. Biblioteki takie jak pypdf, python-docx i unstructured wykonują większość pracy związanej z analizą poszczególnych formatów.
from pypdf import PdfReader
def load_pdf(path):
reader = PdfReader(path)
pages = []
for i, page in enumerate(reader.pages):
text = page.extract_text()
pages.append({'text': text, 'page': i + 1, 'source': path})
return pages
docs = load_pdf('company_policy.pdf')
print(f'Loaded {len(docs)} pages')Etap indeksowania: krok drugi — podział na fragmenty
LLM-y mają ograniczone okna kontekstu, a pobieranie całych dokumentów jest nieefektywne. Wczytany tekst dzieli się na mniejsze fragmenty liczące zwykle około 200–1000 tokenów. Dobry podział zachowuje spójność znaczeniową: fragment powinien przedstawiać kompletną myśl. Często stosuje się nakładające się okna, dzięki którym zdania znajdujące się w pobliżu granic fragmentów pojawiają się w dwóch fragmentach, co zapobiega utracie informacji w miejscach podziału.
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # characters per chunk
chunk_overlap=50, # overlap between chunks
separators=['\n\n', '\n', '. ', ' ']
)
for page in docs:
chunks = splitter.split_text(page['text'])
for chunk in chunks:
# Each chunk carries metadata from its source page
print(f'Chunk ({len(chunk)} chars): {chunk[:80]}...')Etap indeksowania: krok trzeci — tworzenie embeddingów
Każdy fragment tekstu jest przekształcany w gęsty embedding wektorowy, który numerycznie odwzorowuje jego znaczenie semantyczne. Dla każdego fragmentu wywołuje się model embeddingowy — taki jak text-embedding-3-small firmy OpenAI — i otrzymuje tablicę wartości zmiennoprzecinkowych o dużej liczbie wymiarów. Fragmenty o podobnym znaczeniu generują wektory znajdujące się blisko siebie w tej wielowymiarowej przestrzeni, co umożliwia wyszukiwanie podobieństwa semantycznego.
from openai import OpenAI
client = OpenAI()
def embed_chunks(chunks):
texts = [c['text'] for c in chunks]
response = client.embeddings.create(
model='text-embedding-3-small',
input=texts
)
for i, chunk in enumerate(chunks):
chunk['embedding'] = response.data[i].embedding
return chunks
# Batch up to 2048 texts per API call
embedded = embed_chunks(all_chunks)Etap indeksowania: krok czwarty — zapis
Wygenerowane embeddingi fragmentów są przechowywane w bazie wektorowej wraz z metadanymi (plik źródłowy, numer strony, tytuł sekcji). Magazyn wektorowy tworzy strukturę indeksu (zwykle HNSW), która umożliwia szybkie przybliżone wyszukiwanie najbliższych sąsiadów. Indeks jest utrwalany na dysku, dzięki czemu przetrwa ponowne uruchomienie. Indeksowanie zwykle wykonuje się raz podczas konfiguracji oraz przyrostowo po dodaniu nowych dokumentów.
import pinecone
pc = pinecone.Pinecone(api_key='YOUR_KEY')
index = pc.Index('rag-documents')
# Upsert vectors with metadata
vectors_to_upsert = [
(
chunk['id'],
chunk['embedding'],
{'text': chunk['text'], 'source': chunk['source'], 'page': chunk['page']}
)
for chunk in embedded_chunks
]
# Upsert in batches of 100
for i in range(0, len(vectors_to_upsert), 100):
index.upsert(vectors=vectors_to_upsert[i:i+100])
print('Indexing complete')Etap pobierania: embedding zapytania
Gdy użytkownik przesyła zapytanie, rozpoczyna się etap pobierania online. Pierwszym krokiem jest utworzenie embeddingu pytania użytkownika przy użyciu tego samego modelu embeddingowego, który wykorzystano podczas indeksowania. To kluczowe: jeśli indeksowali Państwo dane za pomocą text-embedding-3-small, do obsługi zapytań również należy używać text-embedding-3-small. Embedding zapytania jest wektorem kodującym semantyczne znaczenie tego, o co pyta użytkownik.
def embed_query(question):
response = client.embeddings.create(
model='text-embedding-3-small', # MUST match indexing model
input=[question]
)
return response.data[0].embedding
user_question = 'What is our parental leave policy?'
query_vector = embed_query(user_question)
print(f'Query embedded: {len(query_vector)}-dim vector')Etap pobierania: wyszukiwanie ANN
Embedding zapytania jest wysyłany do magazynu wektorowego, który wykonuje przybliżone wyszukiwanie najbliższych sąsiadów (ANN), aby znaleźć K fragmentów, których embeddingi są najbardziej podobne do wektora zapytania. Wyszukiwanie jest niezwykle szybkie (zwykle trwa poniżej 10 ms), ponieważ indeksy HNSW poświęcają niewielką część kompletności wyników na rzecz ogromnego wzrostu szybkości w porównaniu z wyszukiwaniem metodą brute force. Pobieranych jest K najlepszych fragmentów — najczęściej od K=5 do K=20.
results = index.query(
vector=query_vector,
top_k=5,
include_metadata=True
)
print(f'Retrieved {len(results.matches)} chunks:')
for match in results.matches:
print(f' Score: {match.score:.3f} | Source: {match.metadata["source"]}')
print(f' Text: {match.metadata["text"][:100]}...')
print()Połączenie obu etapów
Najważniejszy wniosek jest taki, że indeksowanie i pobieranie projektuje się jako współdziałające etapy. Model embeddingowy musi być identyczny w obu etapach, ponieważ matematyczna przestrzeń, w której znajdują się wektory, zależy od modelu. Po zmianie modelu embeddingowego trzeba ponownie zindeksować wszystkie dokumenty. Magazyn wektorowy stanowi pomost: przyjmuje wektory podczas indeksowania i zwraca je podczas pobierania, rozdzielając oba etapy w czasie, a jednocześnie zachowując ich zgodność w przestrzeni wektorowej.
Filtrowanie metadanych podczas pobierania
Wyszukiwanie wektorowe znajduje semantycznie podobne fragmenty, ale czasami trzeba także filtrować według metadanych. Na przykład można pobierać wyłącznie fragmenty z dokumentów przesłanych w 2025 roku albo tylko z folderu działu HR. Magazyny wektorowe obsługują filtrowanie wstępne lub końcowe pól metadanych. Filtrowanie wstępne (obsługiwane przez Pinecone i Qdrant) stosuje filtr przed wyszukiwaniem ANN, dzięki czemu jest szybsze i dokładniejsze niż filtrowanie końcowe wyników top-K.
# Retrieve only from HR department documents
results = index.query(
vector=query_vector,
top_k=5,
filter={'department': {'$eq': 'HR'}},
include_metadata=True
)
# Or filter by date range
results = index.query(
vector=query_vector,
top_k=5,
filter={
'upload_year': {'$gte': 2024},
'doc_type': {'$eq': 'policy'}
},
include_metadata=True
)Przyrostowe indeksowanie aktualizacji
W środowisku produkcyjnym zbiór dokumentów zmienia się z czasem. Skuteczna architektura indeksowania obsługuje aktualizacje przyrostowe: po edycji dokumentu należy usunąć jego istniejące wektory według identyfikatorów i wstawić nowe. Po usunięciu dokumentów trzeba usunąć również ich wektory. Każdemu fragmentowi należy przypisać deterministyczny identyfikator oparty na ścieżce dokumentu źródłowego i pozycji fragmentu, aby zawsze można było znaleźć i zaktualizować właściwe wektory bez ponownego indeksowania całości.
import hashlib
def make_chunk_id(source_path, chunk_index):
# Deterministic, stable ID for each chunk
key = f'{source_path}::chunk_{chunk_index}'
return hashlib.md5(key.encode()).hexdigest()
def update_document(source_path, index):
# Delete old vectors for this document
index.delete(filter={'source': source_path})
# Re-index the updated document
new_chunks = load_and_chunk(source_path)
new_embedded = embed_chunks(new_chunks)
index.upsert(vectors=new_embedded)
print(f'Updated {source_path}: {len(new_chunks)} chunks')Pełny potok RAG w skrócie
Kompletna architektura RAG wygląda następująco: Offline: Dokumenty → Loader → Chunker → Model embeddingowy → Magazyn wektorowy. Online: Zapytanie użytkownika → Model embeddingowy → Magazyn wektorowy (wyszukiwanie ANN) → Fragmenty top-K → Tworzenie promptu → LLM → Odpowiedź. Potok offline uruchamia się raz po każdej aktualizacji dokumentu. Potok online działa w ciągu milisekund dla każdego zapytania użytkownika, a LLM otrzymuje wyłącznie istotny kontekst, nie cały zbiór dokumentów.
Szybkie sprawdzenie
Sprawdź swoją wiedzę na temat koncepcji AI Engineering przedstawionych w tej lekcji.
Podsumowanie lekcji
W tej lekcji poznali Państwo: etap indeksowania offline obejmujący kroki wczytywania, dzielenia na fragmenty, tworzenia embeddingów i zapisu, wykonywane raz dla każdego dokumentu, etap pobierania online, który tworzy embedding zapytania, wykonuje wyszukiwanie ANN i w ciągu milisekund zwraca fragmenty top-K, a także strategie indeksowania przyrostowego służące do aktualizowania magazynu wektorowego wraz ze zmianami dokumentów. W następnej części omówimy tworzenie skutecznych promptów rozszerzonych, które dobrze wykorzystują pobrany kontekst.
Ucz się Python dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 30
- Lekcje
- 120
Często zadawane pytania
Czy lekcja „Architektura RAG: indeksowanie i pobieranie” jest bezpłatna?
Tak — pełny tekst „Architektura RAG: indeksowanie i pobieranie” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Architektura RAG: indeksowanie i pobieranie”?
Uczestnicy poznają dwie fazy RAG: fazę indeksowania offline, w której dokumenty są dzielone na fragmenty, osadzane i przechowywane, oraz fazę pobierania online, która znajduje odpowiedni kontekst dla… Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?
Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „Architektura RAG: indeksowanie i pobieranie”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?
Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Problem rozwiązywany przez RAG
- Architektura RAG: indeksowanie i pobieranie
- Tworzenie rozszerzonego promptu
- RAG a fine-tuning: kiedy stosować którą metodę