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?
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.