Alan Balcerowiak

RAG, który odmawia: dlaczego prompt nie wystarczy

Alan Balcerowiak ·

Każdy, kto zbudował choć jeden system RAG, zna tę linijkę: „Odpowiadaj wyłącznie na podstawie poniższego kontekstu. Jeśli odpowiedzi tam nie ma, powiedz, że nie wiesz”. Wygląda jak zabezpieczenie. W praktyce jest prośbą.

Prośba, która działa w czterech przypadkach na pięć

Z mojego doświadczenia taka instrukcja działa w jakichś 80% przypadków. Model grzecznie odmawia, gdy pytanie jest wyraźnie z innej bajki. Kłopot zaczyna się przy pytaniach, które brzmią podobnie do treści w bazie, ale dotyczą czegoś innego. Wtedy retriever i tak coś zwraca – zawsze zwraca, bo wyszukiwanie wektorowe szuka najbliższych sąsiadów, a nie trafnych odpowiedzi. Model dostaje trzy fragmenty „mniej więcej o tym”, prompt mówi „odpowiadaj na podstawie kontekstu”, więc odpowiada. Pewnym tonem, z poprawną polszczyzną i z treścią, której nikt w firmie nie napisał.

Te pozostałe 20% nie rozkłada się równo. Halucynacje koncentrują się dokładnie tam, gdzie użytkownik najmniej je wyłapie: w pytaniach o szczegóły, wyjątki i procedury, o których nie ma dokumentu. Taka odpowiedź prędzej czy później ląduje na firmowym komunikatorze jako zrzut ekranu i od tego dnia nikt już asystentowi nie ufa – również wtedy, gdy ma rację.

Dlatego przestałem traktować odmowę jako zachowanie modelu, które trzeba wyprosić. Traktuję ją jako decyzję systemu, która zapada, zanim model w ogóle zobaczy pytanie.

Dwa elementy przed wywołaniem modelu

Schemat, który stosuję, ma dwie warstwy między wyszukiwaniem a generowaniem.

1. Filtr trafności

Wyszukiwarka wektorowa zwraca fragmenty uszeregowane według podobieństwa. Podobieństwo nie oznacza jednak, że fragment odpowiada na pytanie. Pytanie o urlop rodzicielski będzie „blisko” regulaminu urlopów wypoczynkowych, choć ten nie zawiera ani słowa o rodzicielskim. Filtr trafności odcina fragmenty, które są najbliższe semantycznie, ale nie są „o tym”.

W praktyce to kilka sygnałów złożonych razem, a nie jeden magiczny próg:

  • bezwzględny próg podobieństwa – poniżej pewnej wartości fragment nie przechodzi, nawet jeśli jest najlepszym, jaki mamy;
  • odstęp między najlepszym wynikiem a resztą – gdy wszystkie wyniki są do siebie podobnie średnie, zwykle znaczy to, że baza nie zawiera odpowiedzi;
  • ponowne ocenienie par pytanie–fragment osobnym modelem rerankującym, który patrzy na parę razem, a nie na dwa niezależne wektory;
  • proste reguły dziedzinowe, jeśli są tanie: pytanie o konkretny numer dokumentu, którego nie ma w indeksie, nie potrzebuje żadnej sztucznej inteligencji, żeby dostać odmowę.

2. Wyodrębniona warstwa decyzyjna

Drugi element to miejsce w kodzie, w którym zapada jedna decyzja: odpowiadamy czy odmawiamy. Jest to zwykła funkcja z wejściem (pytanie, to, co przeszło przez filtr, metadane) i wyjściem (odpowiedz / odmów / dopytaj). Jeśli wynikiem jest odmowa, model językowy nie jest wywoływany. Użytkownik dostaje komunikat „Nie znalazłem tego w dokumentach”, ewentualnie z listą tematów, które baza obejmuje.

To rozdzielenie zmienia trzy rzeczy. Po pierwsze odmowa staje się deterministyczna i powtarzalna: to samo pytanie przy tej samej bazie daje ten sam wynik. Po drugie da się ją testować jednostkowo, bez uruchamiania GPU. Po trzecie da się ją logować i audytować: zawsze wiadomo, dlaczego system odmówił, bo decyzja ma swoje wejścia zapisane w logu.

Prompt nadal mówi modelowi, żeby trzymał się kontekstu i cytował źródła. Ale to jest już druga linia obrony, a nie jedyna.

Jak mierzyć odmowy

Bez pomiaru „RAG, który odmawia” jest tylko sloganem. Metryka, którą realnie pilnuję, jest prosta: ile ze 100 pytań spoza bazy kończy się odmową.

Zestaw regresyjny pytań poza zakresem buduję z trzech grup:

  • pytania wyraźnie obce – o pogodę, przepis na pierogi, wynik meczu. Te prawie zawsze przechodzą i służą głównie do wykrycia, że coś się całkiem zepsuło;
  • pytania sąsiednie – w tej samej dziedzinie co dokumenty, ale o rzeczy, których w nich nie ma. To jest właściwy test i to tu prompt przegrywa najczęściej;
  • pytania podchwytliwe – z fałszywym założeniem („od kiedy obowiązuje nowa procedura X?”, gdy żadnej nowej procedury nie było) albo mieszające dwa dokumenty w jedną nieistniejącą regułę.

Obok trzymam drugi zestaw: pytania, na które odpowiedź w bazie jest. Bez niego łatwo „wygrać” pierwszą metrykę systemem, który odmawia zawsze. Oba zestawy uruchamiam po każdej zmianie, która może wpłynąć na wyszukiwanie: nowy model embeddingów, inna długość fragmentów, nowe dokumenty, zmiana progów. Wynik zapisuję razem z wersją konfiguracji, bo liczba bez wersji niczego nie mówi.

Pytania do zestawu najlepiej zbierać od prawdziwych użytkowników. Każda zgłoszona halucynacja trafia do zestawu regresyjnego jako nowy przypadek. Po kilku tygodniach zestaw zaczyna przypominać to, o co ludzie naprawdę pytają, a nie to, co wymyślił autor systemu.

Koszty: nadgorliwa odmowa

Ten model ma swoją cenę i trzeba ją powiedzieć wprost. System, który odmawia za często, jest równie bezużyteczny jak ten, który zmyśla – tylko mniej spektakularnie. Użytkownik pyta trzy razy, trzy razy słyszy „nie wiem” i wraca do przeszukiwania folderów ręcznie.

Nadgorliwa odmowa ma kilka typowych przyczyn:

  • pytanie sformułowane potocznie, a dokument urzędowo – podobieństwo wychodzi niskie, choć odpowiedź jest w bazie;
  • za krótkie albo źle pocięte fragmenty, w których kluczowe zdanie trafiło na granicę dwóch kawałków;
  • próg ustawiony na jednym typie dokumentów i przeniesiony bez zmian na inny.

Dlatego progów nie ustawiam „na oko” ani raz na zawsze. Dobieram je na obu zestawach jednocześnie i patrzę na kompromis: ile odmów na pytaniach spoza bazy zyskuję, a ile poprawnych odpowiedzi tracę. Decyzja, gdzie postawić granicę, nie jest techniczna – zależy od tego, co w danej organizacji kosztuje więcej: zmyślona odpowiedź czy odesłanie do człowieka. W miejscach, gdzie odpowiedź ma skutki prawne albo finansowe, wolę system ostrożniejszy. W wewnętrznej pomocy technicznej można pozwolić sobie na więcej.

Pomaga też trzeci wynik warstwy decyzyjnej: zamiast twardego „nie wiem” – „znalazłem dokumenty o podobnym temacie, czy chodziło o…?”. Dla użytkownika to zupełnie inne doświadczenie niż ściana.

Odmowa jako funkcja, nie porażka

„Nie wiem” traktuję jako pełnoprawną odpowiedź. Lepiej raz odmówić niż raz skłamać – bo zaufanie do asystenta buduje się tygodniami, a traci jednym zrzutem ekranu. Prompt jest dobrym miejscem na styl, ton i format odpowiedzi. Na decyzję, czy w ogóle odpowiadać, potrzebny jest kod, który da się przetestować.

Jeśli budujesz podobny system lokalnie, przeczytaj też od czego zacząć wdrożenie Bielika on-premise i jak zbudować asystenta z narzędziami, który niczego nie zepsuje.

  1. Cztery klocki lokalnego stosu (vLLM, BGE-M3, ChromaDB, FastAPI), jak myśleć o pamięci GPU i gdzie naprawdę traci się czas przy wdrożeniu.

    Czytaj →

  2. Tool calling, w którym odpowiedź pochodzi z danych, a tryb tylko do odczytu wynika z konstrukcji systemu, nie z prośby w prompcie.

    Czytaj →