Mettre en place un système de Retrieval-Augmented Generation (RAG) est devenu un enjeu majeur pour les applications d'IA qui doivent s'appuyer sur des connaissances spécifiques et à jour. Utiliser Supabase comme base de données vectorielles offre un avantage unique : combiner la puissance d'une base de données PostgreSQL transactionnelle avec les capacités de recherche sémantique de l'extension pgvector.
Contrairement aux bases de données vectorielles spécialisées, cette approche intégrée simplifie l'architecture, réduit la latence et facilite la synchronisation des données. Vous gérez vos données métier et leurs représentations vectorielles au même endroit, ce qui ouvre la voie à des systèmes plus robustes, sécurisés et performants.
Les fondations d'un RAG avec Supabase
Avant de plonger dans la technique, comprenons pourquoi l'écosystème Supabase est particulièrement adapté pour construire un RAG. La clé réside dans la combinaison de plusieurs outils open source au sein d'une seule plateforme.
- PostgreSQL : Une base de données relationnelle éprouvée, fiable et ultra-performante.
- pgvector : Une extension PostgreSQL qui ajoute le type de données
vectoret des fonctions pour effectuer des recherches de similarité ultra-rapides. - Edge Functions : Des fonctions serverless Deno qui s'exécutent au plus près de vos utilisateurs, idéales pour générer des embeddings à la volée sans surcharger votre base de données.
- Row Level Security (RLS) : Un mécanisme de sécurité PostgreSQL puissant pour s'assurer que les utilisateurs ne peuvent accéder qu'aux données qui les concernent, une fonctionnalité essentielle pour les applications multi-tenants.
Voici comment Supabase se positionne par rapport à d'autres solutions vectorielles :
| Critère | Supabase (avec pgvector) | Bases vectorielles spécialisées (ex: Pinecone) |
|---|---|---|
| Architecture | Intégrée (données applicatives et vecteurs dans la même DB) | Séparée (nécessite une synchronisation entre la DB principale et la DB vectorielle) |
| Complexité | Plus faible, un seul système à gérer | Plus élevée, deux systèmes à maintenir, synchroniser et sécuriser |
| Flexibilité des requêtes | Très élevée (combinaison de filtres SQL et de recherche vectorielle) | Limitée à la recherche vectorielle et au filtrage sur les métadonnées |
| Sécurité | Granulaire et puissante grâce à PostgreSQL RLS | Dépend de la solution, souvent moins flexible pour le multi-tenant |
Étape 1: Configuration de votre environnement Supabase
La première étape consiste à préparer votre projet Supabase pour stocker des vecteurs et les interroger.
Créer un projet et activer l'extension
- Rendez-vous sur la plateforme Supabase et créez un nouveau projet. Le plan gratuit est suffisant pour démarrer, offrant 500 Mo de stockage.
- Dans le tableau de bord de votre projet, allez dans la section "Database", puis "Extensions".
- Recherchez
vectordans la barre de recherche et activez l'extensionpgvector. Cette opération ne prend que quelques secondes.
Créer une table pour vos documents
Vous avez besoin d'une table pour stocker le contenu de vos documents (les "chunks") et leurs embeddings correspondants.
Exécutez la requête SQL suivante dans l'éditeur SQL de Supabase :
CREATE TABLE documents ( id bigserial PRIMARY KEY, content text, embedding vector(1536) );
- id : Un identifiant unique pour chaque fragment de document.
- content : Le texte brut du fragment.
- embedding : Le vecteur représentant le contenu. La dimension
1536correspond à celle du modèletext-embedding-3-smalld'OpenAI, un excellent choix pour démarrer.
Étape 2: Ingestion et embedding des données
Maintenant que votre base est prête, il faut la peupler. Ce processus consiste à découper vos documents sources en morceaux, à générer un embedding pour chaque morceau, et à stocker le tout dans la table documents.
Le processus de chunking
Le "chunking" consiste à diviser de longs documents en plus petits morceaux. C'est une étape cruciale : des morceaux trop petits manquent de contexte, tandis que des morceaux trop grands "noient" le sens sémantique. Une bonne pratique consiste à viser des chunks de 200 à 500 mots, avec un certain chevauchement entre eux pour ne pas perdre le contexte aux frontières.
Générer les embeddings
Pour transformer un morceau de texte en vecteur, vous devez utiliser un modèle d'embedding, comme ceux proposés par OpenAI. Vous appellerez leur API avec votre texte et recevrez en retour un vecteur.
Automatiser l'ingestion avec les Edge Functions
Une approche robuste consiste à utiliser les Supabase Edge Functions pour automatiser ce processus. Voici un workflow type :
- Vous ajoutez un nouveau document source dans une table (par exemple,
source_documents). - Un trigger PostgreSQL déclenche une Edge Function.
- La fonction lit le document, le découpe en chunks.
- Pour chaque chunk, elle appelle l'API d'OpenAI pour générer l'embedding.
- Enfin, elle insère le contenu et l'embedding dans votre table
documents.
Cette architecture est scalable et découplée, évitant de surcharger votre application principale avec le processus d'embedding qui peut prendre du temps.
Étape 3: Créer la logique de recherche sémantique
C'est le cœur du "Retrieval". Lorsqu'un utilisateur pose une question, vous devez trouver les fragments de documents les plus pertinents dans votre base de données.
Créer une fonction de recherche PostgreSQL
Pour simplifier les appels depuis votre application, il est recommandé de créer une fonction PostgreSQL. Cette fonction prendra en entrée l'embedding de la question de l'utilisateur et renverra les documents les plus similaires.
CREATE OR REPLACE FUNCTION match_documents ( query_embedding vector(1536), match_threshold float, match_count int ) RETURNS TABLE ( id bigint, content text, similarity float ) LANGUAGE sql STABLE AS $$ SELECT documents.id, documents.content, 1 - (documents.embedding <=> query_embedding) as similarity FROM documents WHERE 1 - (documents.embedding <=> query_embedding) > match_threshold ORDER BY documents.embedding <=> query_embedding LIMIT match_count; $$;
Choisir le bon opérateur de similarité
pgvector propose trois opérateurs principaux pour mesurer la distance entre les vecteurs. Le choix dépend du modèle d'embedding que vous utilisez.
| Opérateur | Nom | Cas d'usage recommandé |
|---|---|---|
| <=> | Distance Cosinus | Le choix le plus sûr et polyvalent. Idéal pour les embeddings OpenAI. Mesure l'angle entre les vecteurs. |
| <#> | Produit Scalaire Négatif | Le plus performant si vos vecteurs sont normalisés (longueur égale à 1). Les modèles OpenAI le sont. |
| <-> | Distance Euclidienne (L2) | Moins courant pour les embeddings de texte, plus utilisé pour d'autres types de données (ex: couleurs d'image). |
Pour les modèles OpenAI, le produit scalaire négatif (<#>, ordonné de manière ascendante) est souvent le plus rapide. La distance cosinus reste une excellente alternative.
Étape 4: Génération de la réponse avec un LLM
Une fois que vous avez récupéré les chunks les plus pertinents grâce à votre fonction match_documents, l'étape finale consiste à les fournir à un modèle de langage (LLM) comme GPT-4 pour qu'il génère une réponse cohérente.
Le prompt que vous enverrez au LLM ressemblera à ceci :
En te basant uniquement sur le contexte suivant, réponds à la question de l'utilisateur.
Contexte :
[Contenu du chunk 1]
[Contenu du chunk 2]
Question : [Question de l'utilisateur]
Réponse :
Cette technique contraint le LLM à utiliser les informations que vous lui avez fournies, réduisant ainsi drastiquement les risques d'hallucination et garantissant des réponses basées sur vos propres données.
Sécurité et performance: Aller au-delà du prototype
Un RAG simple fonctionne bien pour une démo, mais pour une application en production, la sécurité et la performance sont primordiales.
Isoler les données avec Row Level Security (RLS)
Si votre application gère les données de plusieurs utilisateurs ou organisations, vous ne pouvez pas les laisser accéder aux documents des autres. Le RLS de Supabase est la solution parfaite.
- Activez le RLS sur votre table
documents. - Ajoutez une colonne pour identifier le propriétaire des données (par exemple,
user_idouorg_id). - Créez une politique de sécurité qui autorise la lecture uniquement si l'ID de l'utilisateur correspond à celui stocké avec le document.
CREATE POLICY "Les utilisateurs ne peuvent voir que leurs propres documents" ON documents FOR SELECT USING (auth.uid() = user_id);
Avec cette politique, votre fonction match_documents ne renverra automatiquement que les résultats auxquels l'utilisateur authentifié a droit, sans aucune modification de votre code applicatif.
Optimiser les requêtes avec l'indexation
Par défaut, une recherche vectorielle scanne l'intégralité de la table, ce qui est lent sur de gros volumes. Pour accélérer drastiquement la recherche sur des millions de vecteurs, il est indispensable de créer un index. L'index HNSW est le plus recommandé pour un bon compromis entre vitesse et précision.
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
Avec un tel index, les requêtes de similarité peuvent être exécutées en moins de 100 millisecondes, même sur des millions de documents.
Architecture et coûts: Penser à la mise en production
Construire un système RAG implique des coûts, principalement liés aux appels d'API pour l'embedding et la génération de réponses.
Maîtriser les coûts
- Embedding : Le coût est lié au nombre de tokens que vous traitez. Utilisez un modèle performant mais économique comme
text-embedding-3-small. Intégrez une logique pour ne générer les embeddings qu'une seule fois, lors de la création ou de la mise à jour d'un document. - Génération : Le coût dépend du modèle utilisé (GPT-4 est plus cher que GPT-3.5-Turbo) et de la taille du contexte envoyé. Optimisez la taille et le nombre de chunks que vous incluez dans votre prompt pour trouver le bon équilibre entre qualité de la réponse et coût.
- Infrastructure Supabase : Les plans payants de Supabase sont très compétitifs et s'adaptent à votre usage, vous permettant de faire évoluer votre base de données et vos Edge Functions selon vos besoins.
Architecture de référence pour la production
- Ingestion Asynchrone : Utilisez des triggers et des Edge Functions pour traiter les nouveaux documents en arrière-plan.
- Base de Données Optimisée : Mettez en place des index HNSW sur vos colonnes de vecteurs.
- Sécurité par RLS : Appliquez des politiques RLS pour garantir une isolation stricte des données.
- API de recherche : Exposez une Edge Function qui prend en entrée une question, la transforme en embedding, appelle votre fonction
match_documentset renvoie les résultats. - Application Frontend : Votre client (web ou mobile) appelle cette API de recherche, puis envoie les résultats pertinents avec la question à une autre Edge Function qui se charge de la génération de la réponse finale via un LLM.
En suivant cette approche, vous construisez un système RAG non seulement puissant et précis, mais aussi sécurisé, scalable et optimisé pour les coûts, tirant pleinement parti de la plateforme intégrée qu'est Supabase.



