PRODSoeverein Europees BaaS-platformOpen Dashboard →

Native AI · 10 min gelezen

Zelfstudie: RAG-pijplijn met Postgres en pgvector

Affane Daylami · Fondateur · 13 april 2026

Terug naar blog

Voor het bouwen van een RAG-pijplijn op Postgres moet u doorgaans zelf verschillende onderdelen samenstellen: een tekstslicer, een embeddings-aanroep, een pgvector-tabel, een gelijkheidsquery. Dit is de klassieke LangChain + PGVectorchain. Op Aurabase bestaat deze pijplijn al aan de serverkant: twee oproepen, ragIngest() en rag(), vervangen deze, terwijl ze vertrouwen op standaard PostgreSQL en pgvector, zonder dat er een eigen vectorbasis moet worden toegevoegd.

Deze Engelse tekst is automatisch gegenereerd op basis van het Franse origineel en is nog niet beoordeeld.
Deze pagina is automatisch vertaald. De Engelse versie is gezaghebbend.

De native RAG maakt deel uit van denative AI die is geïntegreerd in de Aurabasebackend, naast NL2SQL: geen service van derden die bovenop een algemene basis moet worden gemonteerd. Vereisten om deze tutorial te volgen: een bestaand Aurabase-project, een geconfigureerde LLM-provider (OpenAI of Google Gemini voor insluitingen, een van de drie native providers voor generatie) en een project-API-sleutel.

De essentie
  • Een Aurabase RAG-pijplijn bestaat uit twee aanroepen: ragIngest() om een document te indexeren, rag() om een query uit te voeren en een antwoord te genereren. Chunking, insluitingen en zoeken naar vectoren worden aan de serverzijde beheerd.
  • Onder de motorkap bevinden zich standaard PostgreSQL en pgvector: een embeddings-tabel met één vectorkolom per dimensieklasse (768, 1536, 3072) en een gedeeltelijke HNSW-index per klasse.
  • Chunking maakt gebruik van een echte tokenizer (tiktoken o200k), met configureerbare overlap en een anti-explosiebeveiliging voor grote documenten.
  • De opgehaalde inhoud wordt geneutraliseerd voordat deze in de prompt wordt geïnjecteerd: een document dat probeert aan zijn tag te ontsnappen om systeeminstructies te vervalsen, wordt expliciet onschadelijk gemaakt.
  • Alleen OpenAI en Gemini genereren inbedding aan de Aurabase-kant. Anthropic/Claude heeft geen openbare insluitings-API, deze blijft gereserveerd voor het genereren van het uiteindelijke antwoord.
#
Begrip

Hoe deze RAG-pijplijn werkt

De pijpleiding vindt plaats in vijf fasen. Bij opname wordt de tekst opgedeeld, elk deel wordt in batch gevectoriseerd en vervolgens opgeslagen. Bij het bevragen wordt de vraag op zijn beurt gevectoriseerd, vergeleken met de brokken die zijn opgeslagen door middel van cosinus-gelijkenis, en de dichtstbijzijnde fragmenten worden geïnjecteerd in de prompt die naar het generatiemodel wordt gestuurd.

Chunking (tiktoken) → Inbedding (batch) → pgvectoropslag (HNSW) → Zoeken naar gelijkenis → Uitgebreide generatie

Een architectonisch detail dat er bij de productie toe doet: er wordt geen Postgres-verbinding tot stand gebracht tijdens netwerkoproepen naar de inbeddings- of generatieprovider. De DB-transactie wordt gesloten vóór de externe oproep en wordt daarna opnieuw geopend, zodat een gedeelde PgBouncer-backend nooit wordt geblokkeerd voor netwerklatentie van derden.

#
Stap 1

Maak het project

In tegenstelling tot een typische zelf-gehoste pgvector hoeft u CREATE EXTENSION vector niet uit te voeren of zelf een tabel te maken voor deze pijplijn. Het embeddings-schema van het project, met zijn vectorkolommen en HNSW-indexen, wordt automatisch ingericht wanneer het project wordt gemaakt.

terminalbash
# Aurabase-account + CLI
npm i -g @aurabase/cli
aura login

# Maak het project aan en koppel het aan deze map
aura projects create mon-assistant --engine postgres
aura link --project-id <uuid-du-projet>

# Haal de API-sleutels van het gekoppelde project op
aura projects api-keys

De inbeddingsprovider wordt eenmalig geconfigureerd, aan de projectzijde (Studio → IA → Leveranciers). Het is deze provider die de effectieve dimensie van uw vectoren bepaalt, dus de kolom die wordt gebruikt in de tabel embeddings.

#
Stap 2

Indexeer uw documenten met ragIngest()

Eén aanroep is voldoende om een document te indexeren: de tekst wordt in stukken verdeeld, elk stuk wordt gevectoriseerd en vervolgens opgeslagen in de gevraagde naamruimte. De splitsing maakt gebruik van een echte tokenizer (tiktoken, codering o200k_base), niet een eenvoudige splitsing door spaties, wat correct blijft bij tekst zonder spaties, zoals in sommige Aziatische talen.

app/api/ingest/route.tstypescript
import { aura } from '@/lib/aurabase'

export async function POST(req: Request) {
  const { content, metadata } = await req.json()

  const { data, error } = await aura.ai.ragIngest({
    namespace: 'docs-produit',
    content,
    metadata,
  })

  if (error) return Response.json({ error }, { status: 400 })
  return Response.json(data) // { id, stukjes }
}

In onbewerkte HTTP is de equivalente route POST /v1/ai/{project_id}/rag/ingest, geverifieerd door de API-sleutel van het project.

terminalbash
curl -X POST https://<votre-gateway>/v1/ai/<project_id>/rag/ingest \
  -H "apikey: <votre-cle-api>" \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "docs-produit",
    "content": "Le texte complet de votre document ici...",
    "metadata": { "source": "guide-utilisateur.pdf" }
  }'

De opname is standaard idempotent: er wordt automatisch een document-ID afgeleid (SHA-256-hash van de inhoud, of metadata.document_id als u deze opgeeft). Door dezelfde inhoud opnieuw op te nemen, worden de bestaande delen vervangen in plaats van deze te dupliceren, waardoor een periodieke synchronisatietaak veilig opnieuw kan worden afgespeeld.

#
Stap 3

Wat er daadwerkelijk in Postgres terechtkomt

Geen eigen magie hier: de tabel die uw vectoren ontvangt is een gewone Postgres-tabel, met één vector kolom per dimensieklasse en een gedeeltelijke HNSW-index per kolom (alleen actief op de rijen die deze bevolken). Hier is de echte definitie, vereenvoudigd:

platformdiagram van het project (vereenvoudigd)sql
create table embeddings (
  id               uuid primary key default gen_random_uuid(),
  project_id       text not null,
  namespace        text not null,
  content          text not null,
  embedding_768    vector(768),
  embedding_1536   vector(1536),
  embedding_3072   vector(3072),
  embedding_model  text,
  dims             int,
  metadata         jsonb default '{}'::jsonb,
  created_at       timestamptz not null default now()
);

-- Een gedeeltelijke HNSW-index per dimensieklasse
create index idx_embeddings_vec_1536 on embeddings
  using hnsw (embedding_1536 vector_cosine_ops)
  where embedding_1536 is not null;

-- Buiten de dimensies 2000 ondersteunt HNSW het vectortype niet:
-- gegoten in halfvec voor klasse 3072
create index idx_embeddings_vec_3072 on embeddings
  using hnsw ((embedding_3072::halfvec(3072)) halfvec_cosine_ops)
  where embedding_3072 is not null;

Elke zoekopdracht vergelijkt de vectoren met de cosinusafstandsoperator (<=>), degene waarop de operatorklassen vector_cosine_ops en halfvec_cosine_ops van de index gericht zijn. Voor details over de afweging tussen terugroeping en latentie van HNSW versus IVFFlat, zie het artikel gewijd aan HNSW-indexering. Voor de keuze van de inbeddingsdimensie zelf, zie de vergelijking 768 vs 1536 vs 3072.

#
Stap 4

Query's uitvoeren en antwoorden genereren met rag()

Aan de kant van de zoekopdracht combineert rag() de vectorisering van de vraag, het zoeken op gelijkenis in de naamruimte, de constructie van de uitgebreide prompt en de aanroep naar het generatiemodel, in een enkele netwerkrondreis aan de clientzijde.

app/api/ask/route.tstypescript
import { aura } from '@/lib/aurabase'

export async function POST(req: Request) {
  const { question } = await req.json()

  const { data, error } = await aura.ai.rag({
    question,
    namespace: 'docs-produit',
  })

  if (error) return Response.json({ error }, { status: 400 })
  return Response.json(data)
}

Het antwoord bevat het gegenereerde antwoord en de bronnen ervan, waarbij de provider daadwerkelijk gebruikt:

reactie (uittreksel)json
{
  "data": {
    "answer": "D'après la documentation, ...",
    "sources": [
      { "id": "...", "content": "...", "similarity": 0.87 }
    ],
    "model": "claude-3-5-sonnet",
    "tokens": 412,
    "provider": "anthropic",
    "fallback_used": false
  }
}

De opgehaalde inhoud wordt nooit onbewerkt in de systeemprompt geïnjecteerd. Dit zijn onbetrouwbare gegevens (door gebruiker geüpload, geïndexeerde pagina): een document dat bijvoorbeeld een tag voor het afsluitende gedeelte bevat, gevolgd door valse instructies, wordt vóór de montage geneutraliseerd, de punthaken worden vervangen door haakjes, de tekst blijft behouden maar de structuur wordt onschadelijk gemaakt.

Corpus geïndexeerd onder een ander model

Als rag() niets vindt, ook al is uw naamruimte niet leeg, bevat het antwoord een expliciete waarschuwing in plaats van een misleidende stilte: uw corpus is waarschijnlijk geïndexeerd onder een ander model of een andere inbeddingsdimensie. Indexeer het opnieuw via POST /v1/ai/{project_id}/rag/{namespace}/reindex.

#
Vergelijking

Van LangChain en een handgemaakte pgvector: wat verandert

Als je al een RAG-chatbot op PostgreSQL met LangChain hebt gebouwd, heeft elke handmatige steen hier een beheerd equivalent aan de serverzijde, zonder de onderliggende database te wijzigen.

Het opbreken van de tekstRecursiveCharacterTextSplitter om zelf in te stellenragIngest(): geïntegreerde tiktoken chunking, standaard 512 tokens / 64 overlappend
InbeddingHandmatige oproep naar OpenAIEmbeddings, beheer van batchlimietenAutomatisch batchgewijs, ingebouwde tijdelijke nieuwe poging
Vectoropslagpgvector table + HNSW index om zelf te creëren en te migrerenSchema en indexen ingericht per project
Zoeken + promptPGVector.similarity_search() en vervolgens de prompt handmatig samenstellenrag(): zoeken en verbeterde generatie in één aanroep
Herstelde inhoudGeïnjecteerd zoals aangegeven in de promptAutomatische neutralisatie van structuurtags vóór montage

Migreert u een bestaand project gebouwd op Supabase met deze vorm van handmatige montage? De migratielogica voor de rest van de backend (schema, RLS-beleid, SDK) wordt behandeld in onze Supabase naar Aurabase migratiegids.

#
Productie

Instellingen en limieten waar u rekening mee moet houden voordat u in productie gaat

Drie instellingen zijn rechtstreeks van invloed op de kosten en latentie, allemaal geverifieerd in de aura-aiservicecode. Het aantal pogingen voor een tijdelijke inbeddingsoproep (netwerkfout, providerfout 429) is standaard 3, met een basisuitstel van 100 ms. Grote documenten worden standaard ingebed in subbatches die beperkt zijn tot 2048 delen, om de API-plafonds van de leveranciers te respecteren zonder dat er één groot document mislukt. Een vangrail (standaard 10.000 chunks, configureerbaar) verwerpt expliciet de opname van een document dat een afwijkend aantal chunks zou produceren.

Aan de zoekzijde wordt de grootte van de HNSW-kandidatenlijst automatisch aangepast naar max(64, top_k × 4): hoe meer resultaten u opvraagt, hoe meer kandidaten de index onderzoekt om de herinnering te behouden. Een vaste waarde blijft mogelijk via een omgevingsvariabele als uw corpus een bepaald profiel heeft.

De vectorruimten van verschillende modellen worden nooit met elkaar vergeleken: elke zoekopdracht blijft beperkt tot het huidige inbeddingsmodel, en het veranderen van modellen vereist een expliciete herindexering in plaats van een stille omschakeling die de consistentie van de resultaten zou verbreken.

#
Eerlijkheid

Huidige limieten waar u rekening mee moet houden

De insluitingsdimensie moet in een van de drie ondersteunde klassen vallen: 768, 1536 of 3072. Een provider die een andere dimensie retourneert, wordt afgewezen met een expliciete fout en mag nooit worden afgekapt of stilzwijgend worden gecast.

Het genereren van inbedding is alleen beschikbaar via OpenAI of Google Gemini van de drie native providers: Anthropic/Claude stelt geen openbare inbeddings-API beschikbaar, dus deze wordt alleen gebruikt voor het genereren van het uiteindelijke antwoord in deze pijplijn, nooit voor vectorisatie.

De JavaScript SDK maakt de overschrijvingen top_k en threshold op de aanroep rag() nog niet zichtbaar: ze blijven toegankelijk in directe HTTP, respectievelijk beperkt tot [1, 50] en [0, 1], maar niet vanaf aura.ai.rag() zoals nu het geval is. De standaarddrempel voor gelijkenis (0,3) is opzettelijk tolerant; beperk het tot een compact corpus om irrelevante bronnen in de prompt te vermijden.

#
Ga verder

Om verder te gaan

De RAG behandelt vragen over ongestructureerde inhoud (documenten, notities, tickets). Voor vragen over uw relationele gegevens vertaalt Aurabase's native NL2SQL een vraag direct naar gevalideerde SQL. Voor details over de hierboven genoemde HNSW-parameters en dimensieklassen, zie het artikel over HNSW-indexering en de vergelijking van inbeddingsafmetingen. De volledige API-referentie blijft de RAG & pgvector-documentatie en de AI Gateway-documentatie.

#
Veelgestelde vragen

Veelgestelde vragen

Moet u de pgvector-extensie en de HNSW-index zelf beheren?+
Nee. Het insluitingsschema, de pgvector-extensie en de gedeeltelijke HNSW-indexen (één per dimensieklasse: 768, 1536, 3072) worden automatisch ingericht wanneer het project wordt gemaakt. Je roept rechtstreeks ragIngest() aan en vervolgens rag(); fijnafstemming van de index (ef_search, iterative_scan mode) blijft toegankelijk aan de serverzijde als uw corpus een bepaald profiel heeft.
Welke inbeddingsdimensie moet ik kiezen voor mijn gebruiksscenario?+
Aurabase valideert de door uw leverancier geretourneerde dimensie op basis van drie ondersteunde klassen: 768, 1536 en 3072. De keuze hangt af van het geconfigureerde insluitingsmodel (tekst-embedding-3-small bij OpenAI produceert bijvoorbeeld 1536 dimensies) en omvat een compromis tussen terugroepen, kosten en opslaggrootte, zoals beschreven in onze vergelijking gewijd aan het insluiten van dimensies.

KLAAR VOOR IMPLEMENTATIE?

Uw backend in vijf minuten.

Geen creditcard vereist · 500 MB gratis · 50.000 MAU