Wie een RAG-systeem bouwt, krijgt al snel het advies om er een vectordatabase bij te nemen. Draait je applicatie al op PostgreSQL, dan is dat vaak een extra dienst te veel. Met de extensie pgvector slaat Postgres zelf de embeddings op en zoekt het op betekenis. Gemini levert in deze gids zowel de embeddings als het uiteindelijke antwoord.
Het idee van RAG (retrieval-augmented generation) is eenvoudig: het taalmodel antwoordt niet uit zijn geheugen, maar krijgt eerst de relevante stukken uit jouw eigen documenten mee. Wil je het concept eerst rustig uitgelegd zien, lees dan wat RAG is in gewone taal. En wil je helemaal niets bouwen, dan laat onze gids over RAG zonder te bouwen met ChatGPT zien hoe ver je met kant-en-klare functies komt.
Wat pgvector toevoegt aan Postgres
pgvector geeft je een kolomtype vector, afstandsoperators en twee indextypes. Voor RAG gebruik je meestal cosine distance, de operator <=>; daarnaast zijn er <-> (Euclidische afstand), <#> (negatief inner product) en <+> (L1) (Bron: pgvector).
De belangrijkste keuze is de index. HNSW bouwt een gelaagde graaf, zoekt sneller bij dezelfde nauwkeurigheid dan IVFFlat en kan worden aangemaakt voordat er data in de tabel staat, omdat er geen trainingsstap is. Daar staat een langzamere indexbouw en meer geheugengebruik tegenover (Bron: pgvector).
Eén harde grens bepaalt je ontwerp: een gewone vector-kolom kun je indexeren tot 2.000 dimensies, halfvec tot 4.000 (Bron: pgvector). Gemini-embeddings komen standaard met 3.072 dimensies, maar je kunt 128 tot 3.072 opvragen; Google raadt 768, 1536 of 3072 aan (Bron: Gemini API-docs). Kies dus 768 of 1536 en je zit nergens mee.
💡 Beginner-tip: een embedding is een lange rij getallen die de betekenis van een tekst vastlegt. Teksten over hetzelfde onderwerp krijgen rijen die dicht bij elkaar liggen. “Wat kost het abonnement?” vindt zo ook een alinea over “tarieven”, terwijl er geen woord overeenkomt.
De RAG-loop in vier stappen
Installeer eerst de Python-pakketten: pip install google-genai "psycopg[binary]" pgvector numpy. Zet je API-sleutel in de omgevingsvariabele GEMINI_API_KEY; de client pakt die automatisch op.
Stap 1: extensie, tabel en index
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id bigserial PRIMARY KEY,
title text,
content text NOT NULL,
embedding vector(768)
);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
Op een eigen server installeer je pgvector eerst als pakket, bijvoorbeeld postgresql-18-pgvector uit de PostgreSQL-APT-repository; bij veel hostingpartijen zit de extensie er al in (Bron: pgvector).
Stap 2: een embedfunctie
import numpy as np
import psycopg
from pgvector.psycopg import register_vector
from google import genai
from google.genai import types
client = genai.Client() # leest GEMINI_API_KEY
def embed(text: str) -> np.ndarray:
result = client.models.embed_content(
model="gemini-embedding-2",
contents=text,
config=types.EmbedContentConfig(output_dimensionality=768),
)
return np.array(result.embeddings[0].values)
conn = psycopg.connect("postgresql://localhost/rag", autocommit=True)
register_vector(conn)
register_vector zorgt dat psycopg numpy-arrays als pgvector-waarden doorgeeft (Bron: pgvector-python). Bij gemini-embedding-2 normaliseert Google ingekorte vectoren van 768 of 1536 dimensies automatisch; bij het oudere gemini-embedding-001 moet je dat zelf doen (Bron: Gemini API-docs).
Stap 3: documenten opslaan
def store(content: str, title: str | None = None):
doc = f"title: {title or 'none'} | text: {content}"
conn.execute(
"INSERT INTO documents (title, content, embedding) VALUES (%s, %s, %s)",
(title, content, embed(doc)),
)
Let op het voorvoegsel. gemini-embedding-2 kent geen task_type-parameter meer; in plaats daarvan zet je de taak in de tekst zelf. Documenten krijgen het formaat title: {titel} | text: {inhoud} (zonder titel: title: none), vragen het formaat task: question answering | query: {vraag}. Google benadrukt dat je die formaten consequent moet gebruiken (Bron: Gemini API-docs). Splits lange documenten vooraf in stukken van een paar alinea’s; één embedding per heel handboek vindt weinig terug.
Stap 4: zoeken en antwoorden
def ask(question: str) -> str:
q_vec = embed(f"task: question answering | query: {question}")
rows = conn.execute(
"SELECT content FROM documents ORDER BY embedding <=> %s LIMIT 5",
(q_vec,),
).fetchall()
context = "\n\n".join(r[0] for r in rows)
response = client.models.generate_content(
model="gemini-3.8-flash",
contents=(
"Beantwoord de vraag alleen met de context hieronder. "
"Staat het antwoord er niet in, zeg dat dan.\n\n"
f"Context:\n{context}\n\nVraag: {question}"
),
)
return response.text
De modelnaam voor het antwoord is de enige regel die je regelmatig zult aanpassen. Hoe je naar een nieuwer Flash-model overstapt en welke instellingen dan veranderen, staat in onze gids over overstappen naar Gemini 3.8 Flash. Wil je dat het model zelf beslist wanneer het zoekt, maak van ask dan een tool in de tool-calling-loop met Gemini.
⚡ Gevorderden: filter je op metadata (
WHERE afdeling = 'hr'), dan past pgvector het filter pas toe ná de indexscan. Met de standaardhnsw.ef_searchvan 40 en een filter dat 10 procent van de rijen overlaat, houd je gemiddeld maar vier resultaten over. ZetSET hnsw.iterative_scan = strict_order;om de index verder te laten doorzoeken; dat werkt vanaf pgvector 0.8.0. Bouw je een grote index, verhoog dan tijdelijkmaintenance_work_mem, zonder je server leeg te trekken (Bron: pgvector).
Waar het misgaat
De meeste problemen zitten niet in pgvector maar in de embeddings. Wissel je van embedding-model, dan zijn oude en nieuwe vectoren niet vergelijkbaar: Google schrijft letterlijk dat je bij een upgrade naar gemini-embedding-2 al je bestaande data opnieuw moet embedden (Bron: Gemini API-docs). Leg daarom bij elke rij vast met welk model hij is gemaakt. Dat bespaart je later een avond puzzelen over waarom de zoekresultaten ineens nergens op slaan.
Komt het juiste fragment wel terug maar op plek zeven, dan helpt een extra rerank-stap; zie onze gids over Cohere Rerank in je RAG. Liever een framework dat het knippen, embedden en zoeken voor je regelt? Dan is LlamaIndex een logische volgende stap.
Een aparte vectordatabase wordt pas interessant als je collectie zo groot wordt dat Postgres er merkbaar onder lijdt, als je geen Postgres zelf wilt beheren, of als je zoekfuncties nodig hebt die pgvector niet heeft. Tot die tijd geldt: je hebt al een database, je hebt al backups. Zet CREATE EXTENSION vector; in je volgende migratie.
Stand van zaken — bijgewerkt 2026-10-07
Alles hierboven blijft staan, ongeacht welk model er draait. De cijfers en versies hieronder zijn de bederfelijke laag.
| Onderwerp | Stand |
|---|---|
| Nieuwste Gemini-embeddingmodel | gemini-embedding-2 (sinds 22 april 2026), tekst plus beeld, audio, video en pdf, max. 8.192 invoertokens (Bron: Gemini API-docs) |
| Ouder embeddingmodel | gemini-embedding-001, alleen tekst, met task_type; uitfasering gepland op 14 mei 2028 (Bron: Gemini deprecations) |
| Uitgefaseerd | text-embedding-004 (sinds 14 januari 2026), embedding-001 |
| Dimensies | standaard 3.072, instelbaar 128 tot 3.072; aanbevolen 768, 1536 of 3072 |
| Prijs embeddings | gemini-embedding-2: 0,20 dollar per miljoen teksttokens, 0,10 dollar via Batch; gratis tier beschikbaar (Bron: Gemini API-prijzen) |
| Model voor het antwoord | gemini-3.8-flash: 0,75/3,75 dollar per miljoen in/uit tot eind 2026, 1,50/7,50 vanaf 2027; goedkoper alternatief gemini-3.5-flash-lite (0,30/2,50) (Bron: Gemini API-prijzen) |
| pgvector | versie 0.8.7 (1 oktober 2026); iteratieve index-scans sinds 0.8.0 (Bron: pgvector changelog) |
| Indexlimiet | vector tot 2.000 dimensies, halfvec tot 4.000 |
Op de gratis tier van de Gemini API mag Google je invoer gebruiken om zijn producten te verbeteren; voor klant- of bedrijfsdata hoort je toepassing op de betaalde tier (Bron: Gemini API-prijzen).
