Blog · Jak to działa
Czym jest RAG? Jak AI odpowiada z Twoich dokumentów, zamiast zgadywać
RAG to sposób, żeby model językowy przed odpowiedzią przeczytał właściwe fragmenty Twoich dokumentów. Wyjaśniam go na przykładzie systemu, który zbudowałem na własnych 438 decyzjach biznesowych: jak działa, gdzie się wywraca i jak w jedno popołudnie sprawdzić, czy w ogóle go potrzebujesz.
Model wie dużo, ale nie zna Twojej firmy
Zapytaj dowolny czat (ChatGPT, Claude, Gemini, Copilot): „ile kosztuje moja sesja 1:1?”. Nie wie. W najlepszym razie dopyta. W gorszym odpowie pewnym tonem i zgadnie.
Model ma wiedzę z treningu, zamrożoną w dniu, w którym trening się skończył. Twoich cenników, umów i ustaleń z zeszłego tygodnia tam nie ma i nie będzie. Autorzy pracy, która nadała RAG-owi nazwę (Lewis i in., 2020), opisali to jako dwie pamięci: parametryczną, zapisaną w wagach modelu, i nieparametryczną, czyli zewnętrzny indeks, który można przeszukać. RAG łączy obie. Model dostaje do ręki ściągę przygotowaną dla tego jednego pytania.
Najprostsze porównanie: egzamin z otwartą książką. Student może być bardzo dobry, ale wynik zależy od tego, czy otworzy książkę na właściwej stronie.
Mój przykład: 438 decyzji i pytania po polsku
Od lutego zapisuję decyzje biznesowe w osobnych plikach: co ustaliłem, dlaczego i co dalej. W dniu budowy RAG-a było ich 438. Ta sama sprawa wraca w nich kilka razy, a nowsza decyzja zmienia starszą, więc to dobry materiał na RAG-a.
Zbudowałem prototyp: pytam po polsku, dostaję odpowiedź z numerem decyzji w nawiasie, a gdy odpowiedzi w bazie nie ma, słyszę „Nie wiem”. Dwa prawdziwe przebiegi (pierwszy skróciłem, oryginał ma dwa zdania więcej):
$ zapytaj.py --fragmenty "czym zastąpiliśmy Nozbe?"
ZNALEZIONE FRAGMENTY:
1. 2026-10-06-01 · DLACZEGO
2. 2026-10-06-01 · NASTĘPNY KROK
3. 2026-10-06-01 · KONTEKST
4. 2026-10-06-01 · DECYZJA
5. 2026-10-06-01 · TL;DR
Nozbe zastąpiła Asana w planie darmowym. Od 6.10 to jedyne
źródło prawdy o zadaniach (2026-10-06-01). Migrację wykonano
1:1: 11 projektów, 94 zadania i 128 komentarzy (2026-10-06-01).$ zapytaj.py "jaki samochód służbowy kupujemy?"
Nie wiem: w bazie decyzji nie ma odpowiedzi na to pytanie.Druga odpowiedź jest dla mnie ważniejsza od pierwszej. System, który przy braku danych mówi „Nie wiem”, można wpuścić do pracy. System, który wtedy zgaduje, trzeba za każdym razem sprawdzać, więc nie oszczędza czasu.
W pierwszej odpowiedzi widać też problem, do którego wrócę niżej: wszystkie pięć fragmentów pochodzi z tej samej decyzji. Trafienie jest, ale na drugie źródło nie zostało miejsca.
Jak działa RAG krok po kroku
RAG ma dwa etapy. Indeksowanie robisz raz i powtarzasz po każdej zmianie dokumentów. Odpowiadanie dzieje się przy każdym pytaniu. U mnie wszystko poza modelem odpowiadającym działa na moim komputerze, więc do dostawcy modelu trafia tylko pytanie i pięć wybranych fragmentów.
Krok 1. Cięcie dokumentów na fragmenty
Model, który zamienia tekst na liczby, czyta najwyżej 512 tokenów, a dłuższy tekst obcina (tak podaje karta modelu). Cały dokument się więc nie zmieści, a nawet gdyby się zmieścił, jeden wektor na długi plik rozmywa znaczenie.
Tnę każdą decyzję według jej własnej struktury: streszczenie, kontekst, decyzja, uzasadnienie, następny krok. Zbyt długą sekcję dzielę na kawałki po około 1400 znaków, a sąsiednie kawałki mają 200 wspólnych znaków, żeby zdanie na granicy nie zginęło. Z 438 decyzji wyszło 2351 fragmentów.
Do każdego fragmentu doklejam też nagłówek z numerem, datą i tytułem decyzji. Wyjęty z pliku fragment „kosztuje 300 zł” nie mówi, czego dotyczy, a nagłówek dodaje mu brakujący kontekst.
Ten sam problem opisał Anthropic w artykule o Contextual Retrieval (19.09.2024). Doklejenie do fragmentów krótkiego opisu, skąd pochodzą, zmniejszyło liczbę nieudanych wyszukiwań o 35%. Razem z wyszukiwaniem po słowach spadek wyniósł 49%, a po dodaniu rerankingu 67%. Anthropic generuje taki opis modelem. U mnie wystarczył nagłówek, bo pliki mają stałą strukturę.
Dla budujących: nagłówek w kodzie
header = f"Decyzja {id} z {data}: {tytul}"
for sekcja, tekst in sekcje.items(): # KONTEKST, DECYZJA, DLACZEGO…
for kawalek in splitter.split_text(tekst): # max ok. 1400 znaków
fragment = f"{header}\nSekcja: {sekcja}\n\n{kawalek}"Do cięcia używam RecursiveCharacterTextSplitter z LangChain (chunk_size=1400, chunk_overlap=200), ale tylko dla sekcji dłuższych niż 1400 znaków. Pozostałe granice wyznaczają nagłówki sekcji w pliku.
Krok 2. Embeddingi, czyli znaczenie zamienione na liczby
Embedding zamienia tekst na listę liczb (w moim modelu 768) tak, żeby teksty o podobnym znaczeniu dostawały podobne liczby. Pytanie „ile kosztuje sesja 1:1?” ląduje blisko fragmentu „sesja robocza: 300 zł za godzinę”, chociaż prawie nie mają wspólnych słów.
Używam modelu intfloat/multilingual-e5-base. Zna polski i działa lokalnie, więc treść decyzji nie wychodzi z komputera. Gotowe fragmenty razem z liczbami trafiają do bazy wektorowej. U mnie to Chroma, czyli zwykły folder na dysku.
Dla budujących: pułapka modelu e5
Według karty modelu każdy tekst musi zaczynać się od query: (pytanie) albo passage: (dokument). Bez tych przedrostków wyniki są gorsze, a błąd nie daje żadnego komunikatu. Model obcina tekst po 512 tokenach i zwraca 768 liczb na fragment.
Krok 3. Wyszukiwanie: po znaczeniu, po słowach czy jednym i drugim?
Porównałem cztery metody wyszukiwania na 10 pytaniach testowych. Na 8 z nich odpowiedź jest w bazie, a na 2 jej nie ma (to, czy system mówi wtedy „Nie wiem”, sprawdzam osobno, na pełnych odpowiedziach). Miara to hit@5: czy właściwa decyzja jest wśród pięciu pierwszych wyników.
- Wektor (znaczenie): 5 z 8. Radzi sobie z pytaniami zadanymi potocznie, innymi słowami niż w dokumencie.
- BM25 (słowa): 2 z 8. Ogólnie wypadł najsłabiej, ale z dwóch metod podstawowych tylko on znalazł decyzję po jej numerze. Przy pytaniu „co ustala decyzja 2026-06-16-01?” wyszukiwanie po znaczeniu nie miało jej w pierwszej piątce, a BM25 postawił ją na pierwszym miejscu. Numery faktur, symbole produktów i nazwiska najlepiej wyszukuje się po słowach.
- Hybryda (RRF): 5 z 8. Łączy obie listy wyników: każdy fragment dostaje punkty za miejsce na każdej liście, na której się pojawił, więc wygrywają te, które są wysoko na obu. Hybryda znalazła decyzję po numerze, ale zgubiła odpowiedź na pytanie o dni treningów, którą samo wyszukiwanie po znaczeniu miało w pierwszej piątce.
- Hybryda z rerankingiem: 6 z 8. Reranker to osobny, dokładniejszy model. Czyta pytanie razem z każdym z 20 kandydatów i ustawia je od nowa. Jest wolniejszy, więc dostaje tylko krótką listę. Odnalazł odpowiedź na pytanie o treningi i postawił właściwą decyzję na pierwszym miejscu.
Dla budujących: wzór RRF i reranker
# każdy fragment dostaje punkty z obu list wyników
punkty[fragment] += 1 / (60 + pozycja + 1)Pozycja w kodzie liczy się od zera, więc pierwsze miejsce daje 1/61. Wzór pochodzi z pracy Cormacka, Clarke’a i Büttchera (2009). Stałą k = 60 autorzy ustalili w badaniu pilotażowym i piszą, że jej wybór nie był krytyczny. RRF nie wymaga strojenia wag: liczy się tylko miejsce na liście, więc nie trzeba porównywać punktacji obu metod, które mają różne skale.
Jako reranker działa lokalnie wielojęzyczny cross-encoder cross-encoder/mmarco-mMiniLMv2-L12-H384-v1.
Wniosek, który przeniósłbym do każdego projektu: metodę wyszukiwania sprawdź na swoich pytaniach, zanim ją wybierzesz. U mnie dziesięć pytań wystarczyło, żeby zobaczyć, która metoda gubi numery, a która pytania zadane potocznie.
Krok 4. Polecenie, które pozwala powiedzieć „Nie wiem”
Pięć najlepszych fragmentów trafia do modelu razem z poleceniem. Oto całe polecenie, którego używam:
Odpowiadasz na pytanie wyłącznie na podstawie fragmentów decyzji poniżej.
Zasady:
- Używaj tylko informacji z fragmentów. Nie dopowiadaj z własnej wiedzy.
- Po każdym stwierdzeniu podaj w nawiasie ID decyzji, np. (2026-10-14-01).
- Jeśli fragmenty nie zawierają odpowiedzi, napisz dokładnie:
„Nie wiem: w bazie decyzji nie ma odpowiedzi na to pytanie.”
- Jeśli fragmenty są sprzeczne, pokaż obie wersje i wskaż nowszą.
- Odpowiedz po polsku, maksymalnie 5 zdań.
FRAGMENTY:
{context}
PYTANIE: {question}Dlaczego pięć fragmentów, a nie pięćdziesiąt? Badanie „Lost in the Middle” (Liu i in., 2023) pokazało, że modele najlepiej korzystają z informacji na początku i na końcu długiego kontekstu, a wyraźnie gorzej ze środka. Mniej, ale lepiej wybranych fragmentów daje modelowi mniej okazji do pomyłki.
Gdzie RAG się wywraca (u mnie też)
Te miejsca wyszły w pomiarach i w pytaniach testowych. Każde ma swoją naprawę i żadna z nich nie polega na zmianie modelu.
- Czas. Pytanie „jaki jest obecny priorytet działań?” oblały wszystkie cztery metody. Embedding nie wie, co znaczy „obecny”, a stare decyzje o priorytetach brzmią tak samo jak nowe. Naprawa: data w metadanych fragmentu i filtr. Gdy w pytaniu pada „obecny”, „teraz” albo „dziś”, szukam tylko w decyzjach z ostatnich 60 dni.
- Odpowiedź rozsiana po wielu dokumentach. „Jakie produkty mam w ofercie?” też 0 z 4. Oferta zmieniała się w pięciu decyzjach i żadna nie ma pełnej listy. RAG dobrze odpowiada, gdy odpowiedź leży w jednym miejscu. Na pytanie „zbierz wszystko” lepiej odpowie jeden aktualny dokument z pełną listą, poprawiany przy każdej zmianie.
- Pięć fragmentów z jednego pliku. To przypadek Nozbe z początku. Naprawa: najwyżej dwa fragmenty z jednego dokumentu, a resztę miejsc dostają inne źródła.
- Stare wersje. Decyzja zastąpiona nowszą zostaje w indeksie, ale z flagą w metadanych. Domyślnie ją pomijam. Gdy pytam o historię, włączam ją z powrotem.
- Bałagan w źródłach. Polecenie każe pokazać obie wersje sprzecznych fragmentów i wskazać nowszą. Działa tylko dlatego, że każda decyzja ma datę. Dokumenty bez dat i bez jednego aktualnego miejsca na daną informację dadzą Ci RAG-a, który pewnym tonem cytuje nieaktualne ustalenia.
Druga para oczu: weryfikacja i człowiek
Polecenie „odpowiadaj tylko z fragmentów” nie daje pewności, że model się go trzyma. Dlatego w drugiej wersji systemu (graf w LangGraph) model jest wywoływany drugi raz, z innym poleceniem: ma sprawdzić, czy każde zdanie odpowiedzi wynika z fragmentów. Jeśli nie, system raz ponawia szersze wyszukiwanie. Jeśli dalej coś się nie zgadza, zatrzymuje się i czeka na decyzję człowieka.
Prawdziwy przebieg (skrócony w miejscach […]), na pytaniu, które wcześniej oblały wszystkie metody:
$ graf.py "jaki jest obecny priorytet działań?"
→ przygotuj_pytanie (filtr dat: 2026-08-30)
→ wyszukaj (k=20): 2026-10-06-01
→ odpowiedz
→ zweryfikuj: BŁĄD Fragmenty nie podają dzisiejszej daty, więc
nie wynika z nich, że „wszystkie te terminy już minęły”. […]
→ ponów: szersze wyszukiwanie (k=40)
→ wyszukaj (k=40): 2026-09-12-01, 2026-10-06-01
→ odpowiedz
→ zweryfikuj: Nie wiem → koniec
Nie wiem: w bazie decyzji nie ma odpowiedzi na to pytanie.
Fragmenty nie ustalają priorytetu, zawierają tylko listy
„następnych kroków”. […] Obowiązuje więc nowsza wersja
(2026-10-06-01).Weryfikator złapał zdanie, którego nie było we fragmentach, a po drugiej próbie system odpowiedział „Nie wiem”. Widać tu też lukę w moim kodzie: po „Nie wiem” model dopisał jeszcze trzy zdania o treści fragmentów, a weryfikator ich nie sprawdził, bo patrzy tylko na początek odpowiedzi. Odpowiedź „Nie wiem” trzeba więc albo ucinać, albo weryfikować tak samo jak każdą inną. Już to poprawiłem: dopisek po „Nie wiem” jest teraz ucinany, a w śladzie grafu widać, ile znaków odrzucono. W kolejnym przebiegu model znowu dopisał swoje (452 znaki) i tym razem nie przeszło. Druga sprawa: właściwe decyzje o priorytetach w bazie są. Weryfikacja uchroniła mnie przed zmyśloną odpowiedzią, ale nie naprawiła wyszukiwania, więc nad nim trzeba pracować osobno: lepszy filtr dat albo pole „obowiązuje od” w metadanych.
Czy potrzebujesz własnego RAG-a?
Nie zawsze. Przy kilkunastu dokumentach pewnie w ogóle go nie potrzebujesz. Zależy to od tego, ile masz dokumentów i ile kontroli potrzebujesz:
| Twoja sytuacja | Zacznij od |
|---|---|
| Kilka lub kilkanaście dokumentów, które mieszczą się w jednej rozmowie | Wrzuć je do projektu w czacie, którego już używasz. |
| Dokumenty w Microsoft 365 i firma ma licencje | Gotowe narzędzie, np. agent w Copilot Studio z wiedzą z plików i stron. RAG działa wtedy w środku, a Ty konfigurujesz źródła. |
| Setki dokumentów, wrażliwe dane, potrzeba kontroli i pomiaru | Własny RAG. Mój prototyp to kilkaset linii w Pythonie i wszystko poza modelem odpowiadającym działa lokalnie. Taki asystent na dokumentach firmy jest też w mojej ofercie dla firm. |
Gotowe narzędzie też trzeba sprawdzić. Sam testowałem agenta w Copilot Studio na mojej stronie i dwóch dokumentach: 14 odpowiedzi bez ani jednego zmyślenia, za to w połowie przypadków bez źródła albo z błędnym tytułem strony.
Każda odpowiedź zużywała około 9 kredytów, bo agent wykonuje kilka kroków na jedno pytanie. Wbudowana ocena jakości oblała z kolei poprawne „Nie wiem”, uznając je za odmowę pomocy. Bez własnych pytań testowych z wzorcowymi odpowiedziami nie wiedziałbym, której ocenie wierzyć.
Zanim zbudujesz: 4 kroki na jedno popołudnie
- Spisz 10 pytań, które naprawdę zadajesz: 8 takich, na które odpowiedź jest w dokumentach (przy każdym wskaż dokument), i 2, na które jej nie ma. Te dwa sprawdzą, czy system umie powiedzieć „Nie wiem”.
- Przejrzyj źródła. Czy każda ważna informacja ma jedno aktualne miejsce i datę? Jeśli cennik żyje w trzech plikach, RAG znajdzie wszystkie trzy.
- Uruchom najprostszą wersję albo wrzuć dokumenty do gotowego narzędzia i zadaj swoje 10 pytań.
- Policz dwie liczby: ile razy właściwy dokument był w pierwszej piątce i ile razy system powiedział „Nie wiem” wtedy, kiedy powinien. Dopiero z tymi liczbami poprawiaj cięcie, wyszukiwanie albo polecenie.
W moim kodzie model odpowiadający to jedna funkcja, którą można podmienić. Wszystkie poprawki trafności opisane wyżej dotyczyły dokumentów i wyszukiwania: nagłówków, dat, limitu fragmentów z jednego pliku. Od tego bym zaczął.
Chcesz sprawdzić, czy RAG ma sens u Ciebie?
Gdzie u Ciebie leży wiedza, której szukasz najczęściej: w umowach, w mailach, w notatkach? W 20 minut rozmowy sprawdzimy, czy Twoje dokumenty i pytania nadają się na własnego RAG-a, na gotowe narzędzie czy na zwykły projekt w czacie. Rozmowa jest bezpłatna i nic Cię nie zobowiązuje.
Umów 20 minut rozmowySłowniczek
- RAG (retrieval-augmented generation)
- Generowanie odpowiedzi przez model językowy na podstawie fragmentów dokumentów, które system najpierw wyszukał.
- Fragment (ang. chunk)
- Kawałek dokumentu, który wyszukiwanie traktuje jako jedną całość. U mnie sekcja decyzji z doklejonym nagłówkiem.
- Embedding
- Zapis znaczenia tekstu jako listy liczb. Teksty o podobnym znaczeniu mają podobne liczby.
- Baza wektorowa
- Baza, która przechowuje fragmenty razem z ich embeddingami i szybko znajduje te najbliższe pytaniu.
- BM25
- Klasyczne wyszukiwanie po słowach, które liczy, jak często słowa z pytania występują we fragmencie i jak rzadkie są w całym zbiorze.
- RRF (reciprocal rank fusion)
- Sposób łączenia kilku list wyników przez same pozycje, bez porównywania ich punktacji.
- Reranking
- Druga, dokładniejsza ocena krótkiej listy kandydatów: model czyta pytanie razem z każdym fragmentem.
- Token
- Jednostka, w której model liczy tekst: kawałek słowa, całe krótkie słowo albo znak. Polskie słowo to często 2–3 tokeny.
- Metadane
- Dodatkowe pola zapisane przy fragmencie, np. numer decyzji, data albo informacja, że decyzję zastąpiła nowsza. Po nich można filtrować wyniki.
- k
- Liczba kandydatów, których wyszukiwanie zwraca do dalszej oceny (u mnie 20, a przy ponownej próbie 40).
- hit@5
- Odsetek pytań, przy których właściwy dokument jest wśród pięciu pierwszych wyników.
Źródła
- P. Lewis i in., „Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”, NeurIPS 2020 (arXiv, 22.05.2020).
- Anthropic, „Introducing Contextual Retrieval”, 19.09.2024.
- G. V. Cormack, C. L. A. Clarke, S. Büttcher, „Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods”, SIGIR 2009.
- L. Wang i in., „Multilingual E5 Text Embeddings: A Technical Report”, 8.02.2024, oraz karta modelu multilingual-e5-base.
- N. F. Liu i in., „Lost in the Middle: How Language Models Use Long Contexts”, 6.07.2023.
- Pomiary własne: trafność wyszukiwania, odpowiedzi systemu i test Copilot Studio, październik 2026. Źródła zewnętrzne sprawdzone w październiku 2026.