Zbudowałem wyszukiwarkę po dokumentach, która nie zmyśla — oto jak
RAG, który zamiast wymyślać odpowiedzi, pokazuje plik i linię, z której je wziął. Jak działa hybryda BM25 + embeddingi + RRF i dlaczego pojedyncza metoda zawodzi.
Zwykły chatbot zapytany o twoje dokumenty często wygląda na pewnego siebie i mówi coś, czego w dokumentach nie ma. Zbudowałem coś odwrotnego: zadajesz pytanie po polsku, dostajesz odpowiedź — i dokładnie ten plik i tę linię, z której odpowiedź pochodzi.
O co chodzi
Wyobraź sobie asystenta wiedzy, który zna twoje dokumenty: regulacje, procedury wewnętrzne, umowy, dokumentację. Pytasz go normalnym językiem — “jaka jest procedura przy reklamacji powyżej 5 tysięcy?” — a on nie tylko odpowiada, ale pokazuje, skąd to wie: nazwa pliku, konkretne linie.
To różnica między “ufaj mi na słowo” a “sprawdź sobie sam”. W biurze rachunkowym czy w banku to nie jest detal — to warunek, żeby w ogóle używać takiego narzędzia. Jak odpowiedź dotyczy terminu podatkowego albo zapisu w umowie, nikt nie zaryzykuje decyzji na podstawie tekstu, którego nie da się zweryfikować. Cytat plik:linia zamienia “ładnie brzmiącą odpowiedź” w coś, co możesz kliknąć i przeczytać u źródła.
Pod maską
Pod spodem siedzą dwa różne sposoby szukania, które zwykle stawia się przeciwko sobie. U mnie pracują razem.
- BM25 to wyszukiwanie leksykalne — po słowach. Świetne, gdy w pytaniu pada rzadki, konkretny token: numer artykułu, nazwa pola, symbol. Gubi się, gdy zapytasz o to samo innymi słowami.
- Embeddingi to wyszukiwanie semantyczne — po znaczeniu. Lokalny model (
nomic-embed-text-v1.5, odpalany przez LM Studio) zamienia tekst na wektor 768 liczb — współrzędne jego znaczenia (podobne znaczenie → podobne liczby, leżą blisko siebie), więc parafraza dalej trafia w sedno. Za to potrafi przeoczyć rzadkie, dosłowne słowo, którego “znaczeniowo” nie czuje.
Każda metoda zwraca własny ranking. Łączę je przez RRF (Reciprocal Rank Fusion) — fuzję rang, która nie potrzebuje porównywalnych “wyników punktowych”, tylko pozycji na obu listach. Fragment, który jest wysoko u jednej albo u drugiej metody, ląduje wysoko w finalnym wyniku.
pytanie
├──> BM25 (po słowach) ──> ranking A
└──> embeddingi (po sensie) ──> ranking B
│
└──> RRF (fuzja rang) ──> top fragmenty
│
└──> odpowiedź + cytat plik:linia
Kluczowy detal, przez który to się nie zmyśla: cytat jest walidowany w kodzie, nie obiecany przez model. W trybie asystenta model pisze odpowiedź naturalnym językiem, ale przypisany cytat program sprawdza programatycznie — czy ten plik i te linie faktycznie istnieją i zawierają to, na co się powołuje. Jest też tryb ekstraktywny, który w ogóle nie generuje prozy, tylko zwraca dosłowne fragmenty.
Całość jest zaprojektowana na degradację zamiast wywrotki: jak padną embeddingi, schodzi na sam BM25; jak padnie LLM, schodzi na tryb ekstraktywny — i każdy taki spadek jest adnotowany, żebyś wiedział, w jakim trybie dostałeś odpowiedź. Bez zewnętrznych zależności (czysta biblioteka standardowa), licencja MIT.
Co z tego masz
Najważniejsza i najbardziej uczciwa rzecz: hybryda nie “zawsze wygrywa”. Nie o to chodzi. Chodzi o odporność.
Na zestawie testowym całość trafiła w 9/9 zapytań w top-k. Ciekawe jest dlaczego: samo BM25 raz gubiło parafrazę (pytanie sformułowane innymi słowami niż dokument), same embeddingi raz gubiły rzadki token (dosłowne, nietypowe słowo). Żadna pojedyncza metoda nie była bezbłędna. Dopiero RRF trzymał oba przypadki w top-k — bo wystarczyło, że jeden z dwóch silników złapał fragment.
Wniosek, jak budujesz RAG na produkcję: nie wybieraj “lepszej” metody. Łącz słowa + znaczenie, miej zestaw pytań testowych odzwierciedlający realne zapytania i mierz trafienia. Inaczej trafisz na dzień, w którym twój jedyny silnik akurat zgubi pytanie, którego nie przewidziałeś.
Dowód
Kod jest publiczny: github.com/martin0ne/docs-qa. Nie powstał “na czuja” — najpierw specyfikacja, potem adversarialny review (celowe szukanie dziur w założeniach), a implementacja w stylu TDD: 47 testów, które pilnują m.in. tego, że walidacja cytatów faktycznie odrzuca cytat, którego nie ma w źródle.
Jeśli zastanawiasz się, dlaczego chatboty w ogóle zmyślają i co RAG ma z tym wspólnego — wyjaśniam to bez żargonu w osobnym tekście: czemu chatboty zmyślają.
Powiązane artykuły.
Agent dokumentowy, który sam planuje, czego szukać i co czytać (ReAct)
Zwykły RAG szuka raz i odpowiada. agent-flow działa w pętli myśl→narzędzie→obserwacja: sam decyduje, czego szukać i co przeczytać dalej, a każde twierdzenie w raporcie ma cytat plik:linia. Plus bramka akceptacji dla człowieka.
BM25 kontra embeddingi: mała ławka, która pokazuje, że semantyka bywa krucha
Embeddingi rozumieją znaczenie, więc zawsze wygrywają z wyszukiwaniem po słowach? Zbudowałem ławkę pomiarową, która pokazuje czarno na białym, że to nieprawda — i kiedy semantyka pada.
Co dane mówią o adopcji AI: Polska vs UE — pojedynek dwóch urzędów w SQL
Polska wdraża AI szybko, ale od tak niskiej bazy, że dystans do UE rośnie. Ręcznie pisany SQL uzgadnia ten sam wskaźnik między GUS a Eurostatem — JOIN, reconciliation, funkcje okna.