Strona główna / Rozdział 4 · Kontekst i wiedza
Ostatnia zmiana · 8 min czytania
Embeddingi i wyszukiwanie wektorowe
Model embeddingowy zamienia cały fragment tekstu w jeden wektor tak, żeby teksty o podobnym znaczeniu dostawały bliskie wektory. Na tym stoi wyszukiwanie wektorowe w RAG: pytanie też staje się wektorem, a baza zwraca fragmenty o najbliższych wektorach, nawet bez wspólnych słów.
Po ludzkuMapa, na której każdy fragment tekstu ma swoją pinezkę, a teksty o podobnej treści leżą blisko siebie. Szukanie to wbicie pinezki pytania i zebranie najbliższych sąsiadów. Numer umowy prawie nie przesuwa pinezki, więc umowy 48213 i 48231 leżą niemal w tym samym miejscu.
Wybierz pytanie albo wpisz własne i zobacz, który fragment każda metoda stawia na górze
Cała baza wiedzy: 10 fragmentów
Symulacja, nie prawdziwy model: modelu embeddingowego nie da się tu uruchomić. Każdy fragment ma ręcznie przypisane wagi ośmiu nazwanych pojęć, a słowa pytania biorą wagi z małego słownika, więc słowa spoza niego nic nie zmieniają. Prawdziwy wektor ma setki wymiarów bez nazw. Cosinus, BM25 (uproszczony stemmer obcina typowe końcówki) i RRF są liczone na żywo. Firmy i numery umów są zmyślone.
Jeden wektor na cały fragment
- Model embeddingowy, zwykle transformer, liczy wektor dla każdego tokena i łączy je w jeden: uśredniając (mean pooling) albo, w embedderach zbudowanych na LLM-ie dekoderowym, jak Qwen3-Embedding (2025), biorąc wektor ostatniego tokena. Wynik ma stałą długość, zwykle od kilkuset do kilku tysięcy liczb, niezależnie od długości tekstu. To co innego niż wektory wewnątrz LLM-a z tematu „Wektory i macierze”: tam każdy token ma własny wektor, a model uczy się przewidywać następny token, nie porównywać tekstów.
- Model uczy się kontrastowo, na parach: pytanie i fragment z odpowiedzią mają dostać bliskie wektory, a pytanie i inne fragmenty z tego samego batcha odległe. „Blisko” znaczy więc „mówi o tym samym albo na to odpowiada”, w granicach tego, co było w danych treningowych.
- Bliskość mierzy się cosinusem kąta między wektorami. Jeśli wektory mają długość 1 (część modeli tak je zwraca, w pozostałych normalizujesz sam), cosinus to zwykły iloczyn skalarny, a ranking po odległości euklidesowej wychodzi identyczny.
Szukanie w milionach wektorów
- Wyszukiwanie dokładne (brute force) porównuje pytanie z każdym wektorem. Rozmiar indeksu to liczba wektorów × wymiary × bajty, np. 10 mln fragmentów × 1024 wymiary × 4 B (float32) ≈ 41 GB, i każde zapytanie czyta indeks w całości. Przy dziesiątkach tysięcy fragmentów to milisekundy i zero zgubionych wyników, przy milionach i dużym ruchu za wolno i za drogo.
- ANN (approximate nearest neighbours) sprawdza tylko ułamek wektorów, za cenę czasem zgubionego sąsiada. HNSW buduje wielowarstwowy graf „kto jest blisko kogo” i schodzi po nim zachłannie w stronę pytania: szybki i dokładny, ale dla niskiego opóźnienia wektory i graf siedzą w RAM. DiskANN (2019) trzyma w RAM tylko skompresowane wektory, a graf z pełnymi wektorami na SSD: w artykule to miliard wektorów na jednej maszynie z 64 GB RAM, poniżej 3 ms na zapytanie. IVF dzieli wektory na klastry (k-means) i przeszukuje tylko kilka najbliższych: mniej pamięci, zwłaszcza z kompresją, zwykle niższy recall przy tej samej szybkości. Kompromis stroisz parametrem,
efSearchw HNSW albonprobew IVF, i mierzysz recall względem brute force na próbce zapytań. - Pamięć zmniejsza kwantyzacja wektorów: int8 daje 4 razy mniej, binarna (1 bit na wymiar) 32 razy mniej, czyli zamiast 41 GB ok. 1,3 GB. W teście Hugging Face same wektory binarne zachowały ok. 92,5% jakości wyszukiwania, a przeliczenie czołówki pełnym wektorem pytania względem tych samych wektorów binarnych (rescoring) podniosło to do ok. 96%, bez dodatkowej pamięci. Liczby zależą od modelu.
- Drugi sposób to mniej wymiarów. Modele trenowane metodą Matryoshka trzymają najważniejszą informację w pierwszych wymiarach, więc zapisany wektor można przyciąć i znormalizować ponownie, bez przeliczania korpusu. OpenAI podaje, że text-embedding-3-large przycięty z 3072 do 256 wymiarów wypada w benchmarku MTEB lepiej niż starszy ada-002 z 1536.
Słabe punkty, wyszukiwanie hybrydowe i reranking
- Wektor oddaje ogólny sens, a gubi szczegóły. „Umowa 48213” i „umowa 48231” to dla niego prawie to samo: umowa z jakimś numerem. Podobnie kody, liczby, nazwy produktów, których model nie widział w treningu, i firmowy żargon. „Praca zdalna wymaga zgody” i „nie wymaga zgody” też dostają prawie ten sam wektor: w benchmarku NevIR (2023) większość modeli wyszukiwania, także najnowocześniejszych, szeregowała dokumenty różniące się tylko przeczeniem nie lepiej niż losowo, a najlepiej wypadły cross-encodery. Powtórzenie badania z 2025 r. pokazało, że jeszcze lepiej radzą sobie rerankery LLM oceniające całą listę naraz, choć wciąż gorzej niż ludzie.
- Wyszukiwanie wektorowe zawsze zwraca k wyników, także gdy w bazie nie ma odpowiedzi. Stały próg podobieństwa przeniesiony z innego modelu nie zadziała, bo skala zależy od modelu: w multilingual-e5 wyniki skupiają się między 0,7 a 1,0. Próg dobierasz na własnych danych, a pewniejszym sygnałem jest wynik rerankera.
- Dlatego standardem jest hybryda: BM25, czyli klasyczny ranking po wspólnych słowach ważonych ich rzadkością, plus wektory. Listy scala się np. przez RRF (reciprocal rank fusion): dokument dostaje sumę 1/(60 + miejsce) z każdej listy. RRF patrzy tylko na miejsca, więc nie trzeba godzić skal BM25 i cosinusa. Po polsku BM25 potrzebuje lematyzacji albo stemmera, inaczej „umowa” i „umowy” to różne słowa.
- Na końcu reranker, zwykle cross-encoder: czyta pytanie i fragment razem, więc widzi negację i szczegóły, których nie widać w dwóch osobno policzonych wektorach. Jest za wolny na cały korpus, więc porządkuje tylko czołówkę, np. 50–100 kandydatów. W teście Anthropic (contextual retrieval, 2024) dodanie kontekstu do fragmentów razem z BM25 obniżyło odsetek trafnych fragmentów brakujących w top 20 (1 − recall@20) z 5,7% do 2,9%, a reranking do 1,9%. Szerzej w temacie „RAG”.
- Techniki, które omijają słabości jednego wektora na fragment (HyDE, pytania generowane dla fragmentów, multi-query, small-to-big, late interaction w stylu ColBERT), opisuje sekcja „Sztuczki po stronie zapytania i indeksu” w temacie „RAG”.
Decyzje przy budowie indeksu
- Chunking decyduje, co znaczy wektor. Długi fragment uśrednia kilka tematów i nie pasuje dobrze do żadnego pytania. Krótki gubi kontekst: „w tym okresie przysługuje…”, ale w jakim? Pomaga dopisanie tytułu dokumentu i sekcji przed embeddingiem. Tekst dłuższy niż limit modelu jest obcinany: w multilingual-e5 po 512 tokenach, w Qwen3-Embedding po 32 tys.
- Pytanie i dokument to różne teksty: kilka słów kontra akapit. Część modeli oczekuje prefiksów, np.
query:ipassage:w e5, albo parametru typu wejścia w API. Pominięty albo zamieniony prefiks nie rzuca błędu, tylko po cichu psuje ranking. - Dla polskiego wybierasz model wielojęzyczny albo polski i sprawdzasz go na polskich pytaniach, nie tylko w rankingu MTEB. Benchmark PIRB (41 zadań wyszukiwania po polsku) porównuje ponad 20 modeli i pokazuje, że hybryda z wyszukiwaniem po słowach poprawia nawet najlepsze modele wektorowe.
- Zmiana modelu embeddingowego to przeliczenie całego korpusu, bo wektory z dwóch modeli są nieporównywalne, nawet przy tej samej liczbie wymiarów. Przy każdym wektorze trzymasz tekst źródłowy i wersję modelu, nowy indeks budujesz obok starego i przełączasz, gdy wygra na twoim zestawie.
- Wyszukiwanie oceniasz osobno od odpowiedzi, na zestawie pytań z oznaczonymi trafnymi fragmentami. Recall@k: jaka część trafnych fragmentów jest w pierwszych k wynikach. MRR: średnia z 1/miejsce pierwszego trafnego, więc 1. miejsce daje 1, a 3. daje 0,33. Wartość k ustawiasz na tyle fragmentów, ile naprawdę wkładasz do promptu. Zobacz „Ewaluacje”.
Sprawdź się
Jak działa wyszukiwanie wektorowe i czemu samo nie wystarcza?
Model embeddingowy, wytrenowany tak, by teksty o podobnym znaczeniu leżały blisko siebie, zamienia cały fragment w jeden wektor. Wektor pytania liczy się tym samym modelem, a wyszukiwanie zwraca najbliższe wektory po cosinusie. Przy milionach fragmentów indeks przybliżony, np. HNSW, odpowiada w milisekundach kosztem odrobiny recall, a zajmowaną przez niego pamięć zmniejsza kwantyzacja. Wektory łapią parafrazy, ale gubią identyfikatory, liczby, rzadkie nazwy i negację, a do tego zawsze coś zwracają. Dlatego łącz je z BM25 przez RRF, czołówkę porządkuj cross-encoderem, a recall@k mierz osobno od jakości odpowiedzi.
In English
An embedding model turns a whole passage into one vector and is trained so that texts with similar meaning land close together. The query is embedded with the same model, and search returns the nearest vectors by cosine similarity. At millions of passages an approximate index such as HNSW trades a little recall for milliseconds and costs memory, which quantisation reduces. Vectors catch paraphrases but miss identifiers, numbers, rare names and negation, and they always return something. So fuse them with BM25 via RRF, rerank the top candidates with a cross-encoder, and measure recall@k separately from answer quality.
Pytania pogłębiające (5)
- Zmieniasz model embeddingowy. Co z indeksem?
- Przeliczasz cały korpus, bo wektory z różnych modeli są nieporównywalne, nawet przy tej samej liczbie wymiarów. Nowy indeks budujesz obok starego, porównujesz recall@k na tym samym zestawie pytań i dopiero wtedy przełączasz ruch. Dlatego przy wektorach trzymasz tekst źródłowy i wersję modelu.
- HNSW czy IVF?
- HNSW daje wysoki recall przy niskim opóźnieniu, o ile wektory i graf mieszczą się w RAM. IVF dzieli wektory na klastry i przeszukuje kilka z nich. Z kompresją PQ mieści dużo więcej wektorów w tej samej pamięci, kosztem recall, który odzyskujesz większym nprobe albo przeliczeniem czołówki na pełnych wektorach.
- Jak odrzucić fragmenty, gdy w bazie nie ma odpowiedzi?
- Próg cosinusa dobierasz na własnym zestawie, bo skala zależy od modelu. Do zestawu włączasz też pytania bez odpowiedzi w bazie. Pewniejszym sygnałem jest wynik rerankera. Model w prompcie dostaje wprost zgodę na „nie ma tego w źródłach”.
- Jak łączysz wyszukiwanie wektorowe z filtrami, np. uprawnieniami albo datą?
- Filtr nałożony po wyszukiwaniu wycina wyniki z top-k, więc przy wąskim filtrze zostaje ich za mało albo zero. Lepiej filtrować w trakcie przeszukiwania indeksu, co obsługuje wiele baz wektorowych, albo trzymać osobne indeksy, np. per klient. Uprawnienia egzekwujesz przy wyszukiwaniu, nie w prompcie.
- Recall@10 jest wysoki, a odpowiedzi i tak słabe. Gdzie szukasz?
- Najpierw, czy trafny fragment nie ląduje na 8.–10. miejscu, gdy do promptu idą tylko trzy pierwsze: wtedy pomaga reranker. Potem, czy fragment po wycięciu z dokumentu w ogóle zawiera odpowiedź (chunking). Na końcu, czy model jej nie przekręca, co mierzy już ewaluacja generowania.