Tutoriel : construire un pipeline RAG complet avec Postgres et pgvector
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.
- 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
embeddingsavec 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 (
tiktokeno200k), 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.
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.
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.
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.
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.
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.
En HTTP brut, la route équivalente est POST /v1/ai/{project_id}/rag/ingest, authentifiée par la clé API du projet.
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.
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 :
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.
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.
La réponse porte la réponse générée et ses sources, avec le fournisseur réellement utilisé :
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.
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.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.
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.
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.
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.
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.
Comment fonctionne ce pipeline RAG