Blog JSystems - uwalniamy wiedzę!

Szukaj
Blog JSystems · AI dla programistów · Architektura

RAG (Retrieval-Augmented Generation) to dziś najpopularniejsza architektura systemów AI dla firm. Zrozumienie jej 7 komponentów daje Ci kontrolę nad jakością, kosztem i utrzymaniem systemu. Bez tego zrozumienia będziesz traktować RAG jako czarną skrzynkę (black box) - i nie będziesz wiedział dlaczego Twój chatbot kłamie albo zwraca błędne dokumenty.

W tym artykule rozłożymy architekturę RAG na czynniki pierwsze: każdy komponent, co robi, jak go skonfigurować, jakie są typowe pułapki. Dla każdego dam przykład kodu w Pythonie z LangChain - możesz wziąć i uruchomić.

Spis komponentów:

  1. Document Loader - ładowanie dokumentów (PDF, DOCX, HTML, baza)
  2. Text Splitter - dzielenie tekstu na fragmenty (chunki)
  3. Embedding Model - zamiana tekstu na wektory (listy liczb)
  4. Vector Store - baza wektorowa (Chroma, Pinecone, Qdrant)
  5. Retriever - wyszukiwanie najtrafniejszych fragmentów
  6. Prompt Template - jak wkleić znalezione fragmenty do promptu
  7. LLM (Generator) - finalna odpowiedź

Diagram architektury RAG

Diagram architektury RAG - faza offline (indeksowanie) i online (zapytanie)
Architektura RAG w dwóch fazach: offline (raz indeksujemy dokumenty do bazy wektorowej) i online (przy każdym pytaniu: embedding pytania → retriever → top-3 chunki → prompt → LLM → odpowiedź z cytatami).

Komponent 1: Document Loader

Loader pobiera surowe dokumenty z różnych źródeł i zamienia na strukturę {text, metadata}. Metadata zawiera np. źródło (filename, URL), datę, autora - przydaje się potem do cytowania.

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("dokumentacja_produktu.pdf")
documents = loader.load()
# documents[0] = {"page_content": "...", "metadata": {"source": "...", "page": 1}}

Typowe pułapki: (1) PDF ze skanami - potrzebujesz OCR (Tesseract, AWS Textract), (2) duże tabele - PyPDF gubi strukturę, użyj Unstructured lub Camelot, (3) wielojęzyczne dokumenty - sprawdź czy loader poprawnie obsługuje polskie znaki.

Komponent 2: Text Splitter

Dzieli długie dokumenty na chunki — krótkie, samodzielne fragmenty tekstu (~500-1000 tokenów, czyli mniej więcej jeden-dwa akapity). Token to dla modelu najmniejsza porcja tekstu — w przybliżeniu 4 znaki albo 3/4 słowa, więc 1000 tokenów to około 750 słów. Po co w ogóle dzielić? (1) model embeddingu ma limit długości wejścia (rzędu 8 tys. tokenów), (2) chcesz znaleźć konkretny fragment, nie cały dokument, (3) prompt modelu też ma limit i nie zmieści się w nim cała książka.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,        # ~750 słów
    chunk_overlap=200,      # 20% overlap = utrzymujemy kontekst
    separators=["\n\n", "\n", ". ", " "]
)
chunks = splitter.split_documents(documents)

Najważniejsza decyzja: chunk_size, czyli rozmiar fragmentu. Za małe fragmenty - tracisz kontekst. Za duże - wyszukiwarka zwraca za dużo nieistotnego tekstu. Wartość optymalna dla większości przypadków: 800-1200 znaków z 15-20% zakładki (overlap — sąsiednie fragmenty częściowo się nakładają, żeby nie urwać zdania czy myśli w pół na granicy cięcia). Dla dokumentów technicznych ze schematami - dziel po sekcji/podsekcji, wg granic znaczeniowych (tzw. semantic chunking).

Komponent 3: Embedding Model

Model embeddingu zamienia tekst na wektor — po prostu długą listę liczb (np. 1536 liczb dla modelu OpenAI text-embedding-3-small). Możesz myśleć o tym jak o współrzędnych tekstu w wielowymiarowej „przestrzeni znaczeń". Taki wektor jest semantycznym „odciskiem" tekstu: fragmenty o podobnym znaczeniu mają podobne wektory (leżą blisko siebie), nawet jeśli używają zupełnie innych słów. Samo embedding (po polsku: osadzenie) to właśnie ta operacja zamiany tekstu na wektor.

from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# Embedding całego chunka:
vector = embeddings.embed_query("Polityka zwrotów produktów elektroniki")
# vector = [0.0124, -0.0345, 0.872, ...]  # 1536 liczb
Output embeddingu - wektor 1536 wymiarow dla tekstu
Model text-embedding-3-small zwraca dla każdego fragmentu wektor 1536 liczb. Animacja: tekst zamienia się w wektor 1536 liczb (realne wartości z modelu).

Wybór modelu embedding: dla polskiego najlepiej text-embedding-3-large (3072 wymiary, drogie) lub text-embedding-3-small (1536 wymiarów, 5× tańsze). Lokalne alternatywy: multilingual-e5-large, bge-m3. Embedding model musi być TEN SAM podczas indeksowania i wyszukiwania - inaczej wektory są nieporównywalne.

Komponent 4: Vector Store

Vector store, czyli baza wektorowa — wyspecjalizowana baza danych, która przechowuje wektory razem z tekstem i metadanymi (dodatkowymi informacjami o fragmencie: źródło, numer strony, data) i potrafi błyskawicznie znaleźć wektory najbardziej „podobne" do zadanego. Zwykła baza SQL tego nie potrafi — szukanie po podobieństwie znaczeń to właśnie zadanie bazy wektorowej. Podstawowe operacje: add(wektor, tekst, metadane) i search(wektor_pytania, top_k=3).

Vector Store Kiedy używać Koszt
Chroma Lokalnie, ≤100k chunks, prototyp Darmowe (lokalne)
Pinecone Cloud, ≤1M chunks, produkcja ~70$/mies. start
Qdrant Self-hosted lub cloud, produkcja Lokalnie darmowe, cloud od ~50$
PostgreSQL pgvector Już masz Postgres, chcesz wszystko w 1 bazie Darmowe (rozszerzenie)
Weaviate Hybrydowe wyszukiwanie (semantic + keyword) Lokalnie darmowe
from langchain_chroma import Chroma

vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

Komponent 5: Retriever

Retriever (po polsku: wyszukiwacz) to warstwa nad bazą wektorową: dostaje pytanie w postaci tekstu i zwraca K najtrafniejszych fragmentów (top-K, np. 3 najlepsze). Wewnętrznie robi trzy rzeczy: (1) zamienia pytanie na wektor (embedding), (2) szuka w bazie wektorów najbliższych wektorowi pytania metodą cosine similarity — to podobieństwo cosinusowe, miara zgodności dwóch wektorów oparta na kącie między nimi: im mniejszy kąt, tym bliższe znaczenie (wartość od 0 = brak związku do 1 = identyczne), (3) zwraca fragmenty posortowane od najtrafniejszego.

retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 3}        # top-3 chunks
)
docs = retriever.get_relevant_documents("Jaka polityka zwrotów dla elektroniki?")
# docs = [Document(page_content="...", metadata={"source": "...", "page": 12}), ...]
Output retrievera - top-3 chunki z numerami stron
Retriever zwraca najtrafniejsze fragmenty (tu top-3) wraz z numerem strony w metadanych — gotowe do wklejenia do promptu modelu.

Zaawansowane techniki wyszukiwania (omawiamy na szkoleniu RAG): MMR (Maximum Marginal Relevance) — dba o różnorodność wyników, żeby nie zwracać 3× tego samego; wyszukiwanie hybrydowe (hybrid search — łączy dopasowanie znaczeniowe z dopasowaniem po słowach kluczowych metodą BM25); re-ranking (drugi model przegląda 20 wstępnych wyników i wybiera 3 najtrafniejsze); parent document retrieval (do wyszukiwania używa małych fragmentów, ale do modelu wkleja cały nadrzędny akapit, żeby nie zgubić kontekstu).

Komponent 6: Prompt Template

Najczęściej niedoceniany komponent. Tu definiujesz: jak LLM ma rozumieć kontekst, czy ma cytować źródła, co robić gdy nie zna odpowiedzi, jakim tonem odpowiadać.

from langchain.prompts import ChatPromptTemplate

template = """Jesteś asystentem obsługi klienta firmy XYZ.
Odpowiadaj WYŁĄCZNIE na podstawie dostarczonego kontekstu.
Jeśli kontekst nie zawiera odpowiedzi - powiedz "Nie znam odpowiedzi, skieruj zapytanie do biura@xyz.pl".
Zawsze cytuj numer strony źródła.

KONTEKST:
{context}

PYTANIE: {question}

ODPOWIEDŹ:"""

prompt = ChatPromptTemplate.from_template(template)

Dobre prompty RAG zawsze: (1) jasno mówią "tylko z kontekstu", (2) instrukcję co robić gdy nie wie, (3) wymóg cytowania, (4) format odpowiedzi (lista? akapit?), (5) ograniczenia (max długość, ton).

Komponent 7: LLM (Generator)

Wreszcie - sam model językowy generujący ostateczną odpowiedź. Wybór: flagowe modele (GPT, Claude, Gemini) dla najwyższej jakości odpowiedzi; ich lżejsze, tańsze warianty (mini/haiku) dla masowego, tańszego ruchu.

from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

def format_docs(docs):
    return "\n\n".join(f"[{d.metadata.get('page', 'N/A')}] {d.page_content}" for d in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

odpowiedz = rag_chain.invoke("Jaka polityka zwrotów dla elektroniki?")

Temperature = 0 dla systemów RAG - chcemy deterministycznych odpowiedzi opartych na kontekście, nie kreatywnych zmyśleń.

Najczęstsze problemy w architekturze RAG

  • Retriever zwraca nieistotne chunki. Najczęściej zbyt małe chunk_size lub embedding model niepasujący do polskiego. Sprawdź czy chunks zawierają sensowne fragmenty (nie pojedyncze zdania).
  • LLM halucynuje mimo RAG. Prompt nie podkreśla "tylko z kontekstu". Dodaj "Jeśli kontekst nie zawiera odpowiedzi - powiedz że nie wiesz".
  • Wolne odpowiedzi. Profilowanie czasów: embedding pytania (~100 ms), wyszukiwanie w bazie wektorowej (~50 ms), generowanie odpowiedzi przez LLM (~2-10 s). Wąskim gardłem (bottleneck) prawie zawsze jest LLM. Użyj lżejszego, szybszego modelu (lekki wariant GPT lub Claude).
  • Stara wersja dokumentu w wynikach. Brak strategii odświeżania bazy. Implementuj scheduler który przy zmianie pliku usuwa stare chunki + dodaje nowe.
  • Cytowane źródła są błędne. Metadata gubi się przy splitcie. Sprawdź czy każdy chunk zachowuje {source, page} z oryginalnego dokumentu.

Najczęstsze pytania

Czy mogę używać RAG bez LangChain?

Tak. LangChain to tylko wygodna nakładka - możesz wszystko zrobić „ręcznie" na bibliotekach niskiego poziomu (openai, chromadb, pypdf). Plus: pełna kontrola. Minus: 3-4× więcej kodu. Alternatywy dla LangChain: LlamaIndex (zoptymalizowany pod RAG), Haystack (gotowy do produkcji), DSPy (podejście deklaratywne).

Ile chunks powinien zwracać retriever?

Top-3 (trzy fragmenty) to dobra wartość domyślna. Top-5 jeśli pytania złożone. Powyżej top-10 - tracisz jakość (model się rozprasza), rosną koszty tokenów. Zaawansowane: zwróć z wyszukiwarki top-20, a potem osobnym modelem oceniającym (re-rankerem, np. Cohere Rerank) wybierz 3 najlepsze.

Czy mogę używać RAG do polskich dokumentów?

Tak. Polski jest dobrze wspierany przez OpenAI embeddings i multilingual modele jak multilingual-e5-large. Pamiętaj o normalizacji: usuwanie polskich znaków diakrytycznych jest błędem (gubi się znaczenie). Zostaw je.

Czy RAG działa offline (bez internetu)?

Tak, jeśli używasz lokalnego LLM (Llama, Qwen) + lokalnego modelu embeddingu (BGE, e5) + lokalnej bazy wektorowej (Chroma). Cały zestaw działa wtedy na własnym serwerze (self-hosted). Wymaga karty GPU dla modelu 7B+. Idealny tam, gdzie dane nie mogą opuścić serwerowni (wymogi compliance).

Chcesz zbudować produkcyjne RAG?

Szkolenie: Tworzenie systemu RAG (LangChain + LLM OpenAI)

3 dni intensywnych warsztatów. Każdy z 7 komponentów RAG - implementacja, optymalizacja, troubleshooting. Wracasz z gotowym produkcyjnym RAG dla swojej firmy. Cena: 2 700 zł netto. Terminy gwarantowane.

Zapisz się na szkolenie RAG →

Powiązane artykuły: LangChain RAG tutorial krok po kroku, RAG vs LLM - kiedy używać, co to jest agent AI.

Najczęściej zadawane pytania

Czy mogę używać RAG bez LangChain?
Tak. LangChain to tylko wygodna nakładka - możesz wszystko zrobić "ręcznie" na bibliotekach niskiego poziomu (openai, chromadb, pypdf). Plus: pełna kontrola. Minus: 3-4× więcej kodu. Alternatywy dla LangChain: LlamaIndex (zoptymalizowany pod RAG), Haystack (gotowy do produkcji), DSPy (podejście deklaratywne).
Ile chunks powinien zwracać retriever?
Top-3 (trzy fragmenty) to dobra wartość domyślna. Top-5 jeśli pytania złożone. Powyżej top-10 - tracisz jakość (model się rozprasza), rosną koszty tokenów. Zaawansowane: zwróć z wyszukiwarki top-20, a potem osobnym modelem oceniającym (re-rankerem, np. Cohere Rerank) wybierz 3 najlepsze.
Czy mogę używać RAG do polskich dokumentów?
Tak. Polski jest dobrze wspierany przez OpenAI embeddings i multilingual modele jak multilingual-e5-large. Pamiętaj o normalizacji: usuwanie polskich znaków diakrytycznych jest błędem (gubi się znaczenie). Zostaw je.
Czy RAG działa offline (bez internetu)?
Tak, jeśli używasz lokalnego LLM (Llama, Qwen) + lokalnego modelu embeddingu (BGE, e5) + lokalnej bazy wektorowej (Chroma). Cały zestaw działa wtedy na własnym serwerze (self-hosted). Wymaga karty GPU dla modelu 7B+. Idealny tam, gdzie dane nie mogą opuścić serwerowni (wymogi compliance).
Chcesz zbudować produkcyjne RAG?
Szkolenie: Tworzenie systemu RAG (LangChain + LLM OpenAI) 3 dni intensywnych warsztatów. Każdy z 7 komponentów RAG - implementacja, optymalizacja, troubleshooting. Wracasz z gotowym produkcyjnym RAG dla swojej firmy. Cena: 2 700 zł netto. Terminy gwarantowane. Zapisz się na szkolenie RAG →

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...