RAG – vector – opis

1. Typ danych vector(1536) — co to znaczy?

Wyobraź sobie że każde słowo, zdanie lub dokument możesz opisać jako punkt w przestrzeni. Nie w przestrzeni 2D (x, y) ani 3D (x, y, z) — ale w przestrzeni 1536-wymiarowej.

vector(1536) to po prostu tablica 1536 liczb zmiennoprzecinkowych, np.:

[0.0231, -0.0512, 0.1823, 0.0044, -0.2341, ... ]  ← 1536 liczb

Każda liczba to współrzędna w jednym wymiarze. Dlaczego akurat 1536? Bo tyle wymiarów używa model text-embedding-3-small od OpenAI — to jego wewnętrzna decyzja architektoniczna. Inne modele mają inne wymiary, np. text-embedding-3-large ma 3072.

Liczba wymiarów wpływa na dokładność (więcej = lepiej rozróżnia znaczenia) i koszt przechowywania (więcej = więcej miejsca w bazie).

2. Indeks dla pola embedding — co oznaczają parametry?

sql
CREATE INDEX ON document_chunks
    USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);

Rozbijmy to na części:

USING ivfflat — algorytm indeksowania. Skrót od Inverted File Flat. Zamiast porównywać zapytanie z każdym wektorem w bazie (co byłoby wolne przy milionach rekordów), ivfflat dzieli wszystkie wektory na grupy (listy) i szuka tylko w najbliższych grupach. To tzw. przybliżone wyszukiwanie — odrobinę mniej dokładne, ale wielokrotnie szybsze.

vector_cosine_ops — metoda mierzenia podobieństwa między wektorami. Cosine similarity mierzy kąt między dwoma wektorami, nie odległość między nimi. Dwa wektory wskazujące w tym samym kierunku mają cosine similarity = 1 (identyczne znaczenie), prostopadłe = 0 (brak związku), przeciwne = -1.

Dlaczego cosine a nie zwykła odległość euklidesowa? Bo cosine ignoruje „długość” wektora — liczy się tylko kierunek. Dwa dokumenty o tym samym znaczeniu ale różnej długości (krótki i długi) będą miały podobny kąt, ale różną odległość. Cosine daje lepsze wyniki dla tekstu.

WITH (lists = 100) — ile grup (klastrów) ivfflat tworzy podczas budowania indeksu. Więcej list = szybsze wyszukiwanie, ale wolniejsze budowanie indeksu i więcej pamięci. Zasada praktyczna: lists = sqrt(liczba_rekordów). Dla 10 000 rekordów → 100 list. Dla 1 000 rekordów → 30 list. Na razie 100 to dobra wartość domyślna.

3. Co to znaczy „generowanie embeddingów”?

To jest serce całego RAG — warto to dobrze zrozumieć.

Model embeddingowy (np. text-embedding-3-small) to sieć neuronowa wytrenowana na ogromnych ilościach tekstu. Nauczyła się ona że słowa i zdania o podobnym znaczeniu pojawiają się w podobnych kontekstach. Na podstawie tego „rozumienia” potrafi zamieniać tekst na wektor liczb tak, żeby:

"faktura VAT"      → [0.023, -0.051, 0.182, ...]
"rachunek"         → [0.021, -0.048, 0.179, ...]  ← bardzo podobny!
"invoice"          → [0.024, -0.053, 0.185, ...]  ← też podobny (inny język!)
"pogoda w Warszawie" → [-0.234, 0.891, -0.043, ...]  ← zupełnie inny kierunek

Kluczowe właściwości które z tego wynikają:

Rozumie synonimy — „faktura”, „rachunek”, „invoice” lądują blisko siebie w przestrzeni wektorowej, mimo że to różne słowa.

Rozumie kontekst — „zamknięcie” w kontekście finansowym (zamknięcie ksiąg) będzie bliżej wektorów finansowych niż „zamknięcie drzwi”.

Działa między językami — modele wielojęzyczne mapują to samo znaczenie w różnych językach na podobne wektory.

Jest deterministyczne — ten sam tekst zawsze da ten sam wektor. Dlatego indeksujesz raz, a potem tylko szukasz.

Praktycznie w Twoim systemie wygląda to tak:

Faktura XML z KSeF
       ↓
  parsowanie → tekst: "Sprzedawca: Firma ABC, NIP: 123, Usługi IT, 5000 zł netto"
       ↓
  OpenAI API (text-embedding-3-small)
       ↓
  [0.023, -0.051, 0.182, ... 1536 liczb] ← zapisujesz w PostgreSQL
       
Pytanie użytkownika: "ile zapłaciłem za usługi informatyczne?"
       ↓
  OpenAI API (ten sam model!)
       ↓
  [0.019, -0.048, 0.175, ... 1536 liczb]
       ↓
  porównaj z wektorami w bazie → znajdź najbliższe → to są Twoje faktury IT

Ważne — musisz używać tego samego modelu do indeksowania i do wyszukiwania. Wektory z różnych modeli są nieporównywalne, jakbyś miał mapy w różnych skalach.