WITH vector_search AS (
SELECT id, 1 - (embedding <=> $1) AS similarity_score
FROM documents
ORDER BY embedding <=> $1
LIMIT 20
),
text_search AS (
SELECT id, ts_rank_cd(text_searchable, to_tsquery('romanian', $2)) AS text_score
FROM documents
WHERE text_searchable @@ to_tsquery('romanian', $2)
ORDER BY text_score DESC
LIMIT 20
)
SELECT
coalesce(v.id, t.id) AS id,
(coalesce(v.similarity_score, 0) * 0.7) + (coalesce(t.text_score, 0) * 0.3) AS combined_score
FROM vector_search v
FULL OUTER JOIN text_search t ON v.id = t.id
ORDER BY combined_score DESC
LIMIT 10;Dacă vrei RAG, nu te arunca direct la Pinecone sau Qdrant. Postgres cu extensia pgvector face față cu brio în 90% din cazuri și te scapă de o groază de probleme legate de sincronizarea datelor.
Am avut de implementat un sistem de RAG acum câteva luni pentru un client cu un catalog de vreo 80k de documente tehnice. La început, echipa trăgea spre o bază de date vectorială dedicată. Sună bine în prezentări, dar când te gândești că trebuie să menții sincronizate permisiunile userilor, stările documentelor (draft, publicat) și vectorii în două locuri diferite, îți dai seama că e o rețetă sigură pentru bug-uri de consistență.
Am mers pe Postgres și am economisit cam 400 de dolari pe lună, eliminând complet complexitatea unei a doua baze de date.
Compromisul sincer: Când NU e bun pgvector?
Să fim realiști. Dacă ai peste 10 milioane de vectori de dimensiune mare (cum sunt cei de 1536 de la OpenAI) și ai nevoie de timp de răspuns sub 10 milisecunde la mii de interogări pe secundă, pgvector s-ar putea să îți îngenuncheze serverul. Indexul HNSW (Hierarchical Navigable Small World) mănâncă foarte multă memorie RAM.
Dar pentru majoritatea proiectelor de dimensiune medie, unde ai sub un milion de rânduri, Postgres e pur și simplu imbatabil datorită tranzacțiilor ACID.
De ce doar căutarea semantică (vectorială) dă gres
Mulți cred că dacă au embeddings, au rezolvat căutarea. Greșit. Căutarea semantică e excelentă pentru concepte, dar e groaznică la chestii exacte.
Dacă un utilizator caută codul de produs "X-900-PRO", un model de embeddings s-ar putea să returneze "Y-800-PREMIUM" pentru că sunt similare conceptual. Însă utilizatorul voia exact acel cod. De aceea avem nevoie de căutare hibridă: combinăm similaritatea vectorială cu căutarea clasică prin text (Full-Text Search sau BM25).
În Postgres, putem face asta nativ combinând pgvector cu tsvector. Folosim o tehnică simplă de ponderare ca să adunăm scorurile din ambele lumi.
Cum arată interogarea hibridă în producție
În exemplul de mai jos, folosim două Common Table Expressions (CTE). Una calculează scorul de similaritate folosind distanța de cosinus (<=>), iar cealaltă folosește motorul de text search din Postgres cu algoritmul ts_rank_cd. La final, le combinăm cu o pondere de 70% pe semantic și 30% pe text exact.
Această abordare ne-a crescut precizia răspunsurilor în RAG cu peste 25% față de căutarea simplă prin vectori, mai ales pe documentație tehnică plină de jargon și coduri de piese.
Tu ce folosești pentru RAG? Ai trecut prin calvarul sincronizării între o bază relațională și una vectorială, sau ai rămas la Postgres?