Comment j'ai construit un pipeline RAG avec pgvector et Cohere
La plupart des tutoriels sur le RAG (Retrieval-Augmented Generation) s’arrêtent à l’essentiel : embed des documents, stocke les vecteurs, retrouve les plus proches, génère une réponse. En production, la réalité est plus complexe.
Voici l’architecture que j’ai construite pour Galahad — un assistant juridique qui doit retrouver les bons passages parmi des milliers de décisions judiciaires et des documents privés de cabinets d’avocats.
Le modèle d’embedding : Cohere embed-v4.0
J’ai choisi Cohere plutôt qu’OpenAI pour deux raisons : le support natif du français canadien et la spécialisation pour les cas d’usage de recherche (search_document vs search_query — deux types d’embeddings distincts selon qu’on indexe ou qu’on cherche).
Les embeddings sont en 1536 dimensions, traités par batch de 96 pour respecter les limites de l’API.
Le stockage : pgvector avec index HNSW
J’utilise pgvector comme extension PostgreSQL pour stocker et interroger les vecteurs. L’index HNSW (Hierarchical Navigable Small World) est crucial pour les performances à grande échelle.
Configuration retenue : m=16, ef_construction=64, ef_search=80. Ces paramètres définissent le compromis entre vitesse de recherche et précision des résultats. Trop faibles, on rate des résultats pertinents. Trop élevés, la recherche devient lente.
Un problème subtil avec HNSW : le filtre WHERE appliqué après la recherche vectorielle contourne l’index et force un scan séquentiel. La solution — un CTE qui fait d’abord la recherche vectorielle sur un sur-ensemble (LIMIT oversample), puis filtre les résultats.
Le reranking : Cohere rerank-v4.0-pro
La recherche vectorielle retourne les passages les plus proches sémantiquement — mais pas nécessairement les plus pertinents pour répondre à la question. Le reranking applique un second modèle qui évalue chaque passage candidat directement en relation avec la question.
En pratique : on récupère 20 candidats par recherche vectorielle, le reranker en retient les 5 meilleurs. La qualité des réponses augmente significativement.
Le problème du mode hybride
Galahad doit chercher simultanément dans les sources publiques (jurisprudence, codes) et dans les documents privés du cabinet. Naïvement, on pourrait tout mélanger et faire un seul reranking.
Le problème : avec des milliers de décisions publiques dans la base, elles noient systématiquement les documents privés dans les résultats — même quand ces documents sont plus pertinents pour la question.
La solution : deux recherches vectorielles parallèles (PUBLIC + PRIVATE), deux reranks séparés, et des slots garantis pour chaque source dans la réponse finale. ceil(topK/2) résultats publics + floor(topK/2) résultats privés.
La déduplication
Avant le reranking, je déduplique les chunks par contenu (les 200 premiers caractères servent de clé). Les documents publics peuvent apparaître plusieurs fois dans la base si leur indexation a été relancée — sans déduplication, le reranker voit des copies identiques et gaspille des slots.
Le résultat
Une question en langage naturel → embed → recherche vectorielle parallèle → déduplication → reranking → réponse avec sources citées. Latence totale en production : environ 2-3 secondes pour le premier token.
Ce que les tutoriels ne montrent pas : les 80% du travail qui consistent à gérer les cas limites, les index, les conflits entre corpus, et les comportements inattendus des modèles.