Retour au blog
Tutoriel Technique · 10 min de lecture

Tutoriel : construire un pipeline RAG complet avec Postgres et pgvector

Affane Daylami · Fondateur· 24 août 2026

Construire un pipeline RAG sur Postgres suppose en général d'assembler soi-même plusieurs pièces : un découpeur de texte, un appel d'embeddings, une table pgvector, une requête de similarité. C'est la chaîne classique LangChain + PGVector. Sur Aurabase, ce pipeline existe déjà côté serveur : deux appels, ragIngest() et rag(), la remplacent, tout en s'appuyant sur du PostgreSQL et pgvector standard, sans base vectorielle propriétaire à ajouter.

Le RAG natif fait partie de l'IA native intégrée au backend Aurabase, aux côtés du NL2SQL : pas un service tiers à assembler par-dessus une base généraliste. Prérequis pour suivre ce tutoriel : un projet Aurabase existant, un fournisseur LLM configuré (OpenAI ou Google Gemini pour les embeddings, l'un des trois fournisseurs natifs pour la génération), et une clé API projet.

L'essentiel
  • Un pipeline RAG Aurabase tient en deux appels : ragIngest() pour indexer un document, rag() pour interroger et générer une réponse. Chunking, embeddings et recherche vectorielle sont gérés côté serveur.
  • Sous le capot, c'est du PostgreSQL et pgvector standard : une table embeddings avec une colonne vectorielle par classe de dimension (768, 1536, 3072) et un index HNSW partiel par classe.
  • Le découpage en chunks utilise un vrai tokenizer (tiktoken o200k), avec chevauchement configurable et un garde-fou anti-explosion sur les documents volumineux.
  • Le contenu récupéré est neutralisé avant d'être injecté dans le prompt : un document qui tente d'échapper à sa balise pour usurper des instructions système est explicitement désamorcé.
  • Seuls OpenAI et Gemini génèrent des embeddings côté Aurabase. Anthropic/Claude n'a pas d'API d'embeddings publique, il reste réservé à la génération de la réponse finale.
#
Concept

Comment fonctionne ce pipeline RAG

Le pipeline se déroule en cinq étapes. À l'ingestion, le texte est découpé, chaque chunk est vectorisé en batch puis stocké. À l'interrogation, la question est vectorisée à son tour, comparée aux chunks stockés par similarité cosinus, et les extraits les plus proches sont injectés dans le prompt envoyé au modèle de génération.

Chunking (tiktoken)Embeddings (batch)Stockage pgvector (HNSW)Recherche par similaritéGénération augmentée

Un détail d'architecture qui compte en production : aucune connexion Postgres n'est tenue pendant les appels réseau vers le fournisseur d'embedding ou de génération. La transaction DB se referme avant l'appel externe et se rouvre après, pour ne jamais bloquer un backend PgBouncer partagé le temps d'une latence réseau tierce.

#
Étape 1

Créer le projet

Contrairement à un pgvector auto-hébergé classique, vous n'avez pas besoin d'exécuter CREATE EXTENSION vector ni de créer de table vous-même pour ce pipeline. Le schéma embeddings du projet, avec ses colonnes vectorielles et ses index HNSW, est provisionné automatiquement à la création du projet.

terminal
BASH
# Compte Aurabase + CLI
npm i -g @aurabase/cli
aura login
# Créer le projet et le lier à ce dossier
aura projects create mon-assistant --engine postgres
aura link --project-id <uuid-du-projet>
# Récupérer les clés API du projet lié
aura projects api-keys

Le fournisseur d'embedding se configure une fois, côté projet (Studio → IA → Fournisseurs). C'est ce fournisseur qui détermine la dimension effective de vos vecteurs, donc la colonne utilisée sur la table embeddings.

#
Étape 2

Indexer vos documents avec ragIngest()

Un appel suffit pour indexer un document : le texte est découpé en chunks, chaque chunk est vectorisé, puis stocké dans le namespace demandé. Le découpage utilise un tokenizer réel (tiktoken, encodage o200k_base), pas un simple découpage par espaces, ce qui reste correct sur du texte sans espaces comme certaines langues asiatiques.

app/api/ingest/route.ts
TYPESCRIPT
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, chunks }
}

En HTTP brut, la route équivalente est POST /v1/ai/{project_id}/rag/ingest, authentifiée par la clé API du projet.

terminal
BASH
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" }
}'

L'ingestion est idempotente par défaut : un identifiant de document est dérivé automatiquement (hash SHA-256 du contenu, ou metadata.document_id si vous le fournissez). Réingérer le même contenu remplace ses chunks existants au lieu de les dupliquer, ce qui rend un job de synchronisation périodique sûr à rejouer.

#
Étape 3

Ce qui atterrit réellement dans Postgres

Aucune magie propriétaire ici : la table qui reçoit vos vecteurs est une table Postgres ordinaire, avec une colonne vector par classe de dimension et un index HNSW partiel par colonne (actif uniquement sur les lignes qui la remplissent). Voici sa définition réelle, simplifiée :

schéma platform du projet (simplifié)
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()
);
-- Un index HNSW partiel par classe de dimension
create index idx_embeddings_vec_1536 on embeddings
using hnsw (embedding_1536 vector_cosine_ops)
where embedding_1536 is not null;
-- Au-delà de 2000 dimensions, HNSW ne supporte pas le type vector :
-- cast en halfvec pour la classe 3072
create index idx_embeddings_vec_3072 on embeddings
using hnsw ((embedding_3072::halfvec(3072)) halfvec_cosine_ops)
where embedding_3072 is not null;

Chaque recherche compare les vecteurs avec l'opérateur de distance cosinus (<=>), celui que ciblent les classes d'opérateurs vector_cosine_ops et halfvec_cosine_ops de l'index. Pour le détail des compromis rappel/latence de HNSW face à IVFFlat, voir l'article dédié à l'indexation HNSW. Pour le choix de la dimension d'embedding elle-même, voir le comparatif 768 vs 1536 vs 3072.

#
Étape 4

Interroger et générer une réponse avec rag()

Côté requête, rag() enchaîne la vectorisation de la question, la recherche par similarité dans le namespace, la construction du prompt augmenté et l'appel au modèle de génération, en un seul aller-retour réseau côté client.

app/api/ask/route.ts
TYPESCRIPT
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)
}

La réponse porte la réponse générée et ses sources, avec le fournisseur réellement utilisé :

réponse (extrait)
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
}
}

Le contenu récupéré n'est jamais injecté brut dans le prompt système. C'est une donnée non fiable (upload utilisateur, page indexée) : un document qui contiendrait par exemple une balise fermante de section suivie de fausses instructions se voit neutralisé avant assemblage, ses chevrons remplacés par des crochets, texte préservé mais structure désamorcée.

Corpus indexé sous un autre modèle
Si rag() ne retrouve rien alors que votre namespace n'est pas vide, la réponse porte un avertissement explicite plutôt qu'un silence trompeur : votre corpus est probablement indexé sous un autre modèle ou une autre dimension d'embedding. Réindexez-le via POST /v1/ai/{project_id}/rag/{namespace}/reindex.
#
Comparatif

Depuis LangChain et un pgvector fait main : ce qui change

Si vous avez déjà construit un chatbot RAG sur PostgreSQL avec LangChain, chaque brique manuelle a un équivalent géré côté serveur ici, sans changer de base de données sous-jacente.

Découpage du texteRecursiveCharacterTextSplitter à régler soi-mêmeragIngest() : chunking tiktoken intégré, 512 tokens / 64 de chevauchement par défaut
EmbeddingsAppel manuel à OpenAIEmbeddings, gestion des limites de batchBatchés automatiquement, retry transitoire intégré
Stockage vectorielTable pgvector + index HNSW à créer et migrer soi-mêmeSchéma et index provisionnés par projet
Recherche + promptPGVector.similarity_search() puis assemblage manuel du promptrag() : recherche et génération augmentée en un appel
Contenu récupéréInjecté tel quel dans le promptNeutralisation automatique des balises de structure avant assemblage

Vous migrez un projet existant construit sur Supabase avec ce genre d'assemblage manuel ? La logique de migration du reste du backend (schéma, policies RLS, SDK) est couverte dans notre guide de migration Supabase vers Aurabase.

#
Production

Réglages et limites à connaître avant la mise en production

Trois réglages jouent directement sur le coût et la latence, tous vérifiés dans le code du service aura-ai. Le nombre de tentatives sur un appel d'embedding transitoire (panne réseau, erreur 429 du fournisseur) est de 3 par défaut, avec un backoff de base de 100 ms. Les documents volumineux sont embeddés par sous-lots bornés à 2048 chunks par défaut, pour respecter les plafonds d'API des fournisseurs sans échouer sur un seul document massif. Un garde-fou (10 000 chunks par défaut, configurable) rejette explicitement l'ingestion d'un document qui produirait un nombre de chunks aberrant.

Côté recherche, la taille de la liste de candidats HNSW s'ajuste automatiquement à max(64, top_k × 4) : plus vous demandez de résultats, plus l'index explore de candidats pour préserver le rappel. Une valeur fixe reste possible via une variable d'environnement si votre corpus a un profil particulier.

Les espaces vectoriels de modèles différents ne sont jamais comparés entre eux : chaque recherche reste bornée au modèle d'embedding courant, et changer de modèle nécessite une réindexation explicite plutôt qu'une bascule silencieuse qui romprait la cohérence des résultats.

#
Honnêteté

Limites actuelles à connaître

La dimension d'embedding doit tomber dans l'une des trois classes supportées : 768, 1536 ou 3072. Un fournisseur qui retourne une autre dimension est rejeté avec une erreur explicite, jamais tronqué ni casté en silence.

La génération d'embeddings n'est disponible que via OpenAI ou Google Gemini parmi les trois fournisseurs natifs : Anthropic/Claude n'expose pas d'API d'embeddings publique, il ne sert donc qu'à la génération de la réponse finale dans ce pipeline, jamais à la vectorisation.

Le SDK JavaScript n'expose pas encore les overrides top_k et threshold sur l'appel rag() : ils restent accessibles en HTTP direct, bornés respectivement à [1, 50] et [0, 1], mais pas depuis aura.ai.rag() tel quel aujourd'hui. Le seuil de similarité par défaut (0,3) est volontairement permissif ; resserrez-le sur un corpus dense pour éviter des sources peu pertinentes dans le prompt.

#
Aller plus loin

Pour aller plus loin

Le RAG couvre les questions sur du contenu non structuré (documents, notes, tickets). Pour des questions sur vos données relationnelles, le NL2SQL natif d'Aurabase traduit directement une question en SQL validé. Pour le détail des paramètres HNSW et des classes de dimension évoqués plus haut, consultez l'article sur l'indexation HNSW et le comparatif des dimensions d'embeddings. La référence API complète reste la documentation RAG & pgvector et la documentation de l'AI Gateway.

#
Questions Fréquentes

FAQ

Faut-il gérer soi-même l’extension pgvector et l’index HNSW ?+
Non. Le schéma embeddings, l’extension pgvector et les index HNSW partiels (un par classe de dimension : 768, 1536, 3072) sont provisionnés automatiquement à la création du projet. Vous appelez directement ragIngest() puis rag() ; le réglage fin de l’index (ef_search, mode iterative_scan) reste accessible côté serveur si votre corpus a un profil particulier.
Quelle dimension d’embedding choisir pour mon cas d’usage ?+
Aurabase valide la dimension retournée par votre fournisseur contre trois classes supportées : 768, 1536 et 3072. Le choix dépend du modèle d’embedding configuré (par exemple text-embedding-3-small chez OpenAI produit 1536 dimensions) et implique un compromis entre rappel, coût et taille de stockage détaillé dans notre comparatif dédié aux dimensions d’embeddings.
ESSAYEZ MAINTENANT

Indexez votre premier document en quelques minutes.

Un projet Aurabase suffit : pgvector, le schéma et les index HNSW sont déjà prêts.

Créer un projet Documentation RAG & pgvector
Aucune carte bancaire requise · 500 MB gratuits · 50 000 MAU