Skip to main content

Développement Web

Intégrer un moteur RAG souverain dans Drupal 11 : connecter Search API à une base vectorielle PostgreSQL

EN BREF

  • Le RAG (Retrieval-Augmented Generation) transforme la recherche d'entreprise, mais l'utilisation d'API tierces pose des risques de souveraineté et de coûts.
  • L'intégration d'une IA générative open-source locale et de PostgreSQL (avec l'extension pgvector) permet de conserver la maîtrise totale des données.
  • Il est possible d'étendre Drupal 11 et son puissant module Search API pour automatiser la création d'embeddings (représentations vectorielles) à chaque sauvegarde de contenu.
  • Cette architecture hybride combine la recherche par mots-clés classique et la recherche sémantique intelligente pour des résultats ultra-pertinents.

La confidentialité des données et la maîtrise des coûts sont devenues les nouveaux nerfs de la guerre dans le déploiement des architectures d'intelligence artificielle. Si le RAG (Retrieval-Augmented Generation) s'impose pour augmenter les modèles de langage avec des bases de connaissances d'entreprise, la dépendance aux API propriétaires (comme OpenAI ou Pinecone) pose un défi critique pour les CTO et les RSSI.

Comment proposer une expérience de recherche sémantique de pointe sur Drupal 11, sans sacrifier la souveraineté des données, sans coûts de requêtes exponentiels, et en restant sur une infrastructure maîtrisée ?

La réponse réside dans la construction d'un moteur RAG souverain, hébergé sur vos propres serveurs Debian. Cet article détaille l'architecture complète pour coupler Drupal 11, le système Search API, un modèle d'embedding open-source et PostgreSQL équipé de l'extension pgvector.

Pourquoi privatiser l'architecture RAG en entreprise ?

Avant de plonger dans le code, il est essentiel de comprendre pourquoi l'ingénierie applicative se tourne vers des solutions hébergées localement (on-premise ou cloud souverain).

L'architecture RAG standard envoie vos données internes à une API externe pour générer des embeddings (la traduction mathématique d'un texte sous forme de vecteur), puis les stocke dans une base de données tierce. Pour une entreprise, cela soulève trois problématiques majeures :

  • Confidentialité et conformité (RGPD / AI Act) : Envoyer des données métiers sensibles ou des informations personnelles à des modèles propriétaires expose l'entreprise à des fuites de données et à des violations réglementaires (l'AI Act européen encadre de plus en plus ces usages).
  • Maîtrise des coûts de requête : Les API tierces facturent au token. L'indexation initiale d'une volumineuse base de données Drupal, suivie des requêtes quotidiennes des utilisateurs, peut engendrer une explosion budgétaire imprévisible.
  • Indépendance technologique (Vendor Lock-in) : Dépendre d'un fournisseur d'API d'IA, c'est s'exposer à ses changements de tarification, de CGU ou même à l'obsolescence soudaine de ses modèles.

La privatisation du RAG avec des composants open-source (Debian, PostgreSQL, Drupal, et des modèles comme ceux d'Hugging Face) garantit une maîtrise absolue du cycle de vie de la donnée.

Schéma d'architecture illustrant le flux de données local entre Drupal 11

Configuration de pgvector sous Debian

Pour faire de la recherche sémantique, nous devons stocker nos embeddings et calculer la distance entre eux (pour trouver les contenus les plus proches d'une requête). PostgreSQL, le SGBD robuste par excellence, propose une extension native parfaite pour cela : pgvector.

L'objectif est d'ajouter cette extension à votre instance PostgreSQL existante sous Debian, évitant ainsi d'ajouter une nouvelle brique d'infrastructure complexe (comme une base de données vectorielle dédiée).

1. Installation de l'extension pgvector

Sur un serveur Debian (11 ou 12), l'installation depuis le dépôt officiel PostgreSQL APT est la méthode la plus stable.

# Mise à jour des paquets
sudo apt update

# Installation de pgvector (remplacez 15 par votre version de PostgreSQL, ex: 14, 15, 16)
sudo apt install postgresql-15-pgvector

Note : Si vous utilisez une image Docker, des images pré-compilées pgvector/pgvector sont disponibles.

2. Activation et paramétrage dans la base de données

Connectez-vous à votre base de données (celle utilisée par Drupal, ou une base dédiée aux vecteurs) et activez l'extension.

-- Activer l'extension dans la base courante
CREATE EXTENSION IF NOT EXISTS vector;

3. Création de la table de stockage des embeddings

Bien que Drupal gère ses propres tables, pour une architecture RAG propre, il est souvent préférable de créer une table spécifique pour stocker l'ID de l'entité Drupal et son vecteur.

-- Création de la table avec une colonne vectorielle.
-- Le paramètre 768 correspond à la dimension typique des modèles d'embeddings open-source courants (ex: all-mpnet-base-v2).
CREATE TABLE drupal_embeddings (
    entity_type varchar(50) NOT NULL,
    entity_id integer NOT NULL,
    embedding vector(768),
    PRIMARY KEY (entity_type, entity_id)
);

4. Indexation pour les contenus volumineux

Par défaut, pgvector effectue une recherche exacte (Exact Nearest Neighbor). Pour les grosses bases de données, cela devient lent. Il faut utiliser une recherche approximative (ANN) en ajoutant un index HNSW (Hierarchical Navigable Small World).

-- Création d'un index HNSW utilisant la distance cosinus (vector_cosine_ops)
CREATE INDEX ON drupal_embeddings USING hnsw (embedding vector_cosine_ops);

Interfaçage avec Drupal 11 et Search API

L'écosystème Drupal 11, notamment via le module Search API, est extrêmement flexible. Nous allons créer un pipeline d'indexation automatisé.

Le processus est le suivant : à chaque sauvegarde ou modification d'une entité (un nœud "Article", par exemple), un module Drupal personnalisé intercepte le texte, appelle une API locale d'embedding pour obtenir le vecteur, puis le stocke dans notre table PostgreSQL.

1. Génération locale de l'embedding (L'API interne)

Plutôt que d'appeler OpenAI, vous devez faire tourner un petit service d'inférence en local sur votre serveur Debian (par exemple, un conteneur Python avec FastAPI et sentence-transformers).

# Exemple conceptuel du service d'embedding local (Python)
from fastapi import FastAPI
from sentence_transformers import SentenceTransformer
from pydantic import BaseModel

app = FastAPI()
model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')

class TextRequest(BaseModel):
    text: str

@app.post("/embed")
def get_embedding(req: TextRequest):
    vector = model.encode(req.text).tolist()
    return {"embedding": vector}

2. Le module personnalisé Drupal : Indexation à la sauvegarde

Créons un module Drupal (drupal_rag) qui réagit au hook de sauvegarde d'entité.

<?php
// Fichier : drupal_rag.module

use Drupal\Core\Entity\EntityInterface;
use GuzzleHttp\Client;

/**
 * Implements hook_entity_update().
 */
function drupal_rag_entity_update(EntityInterface $entity) {
  _drupal_rag_index_entity($entity);
}

/**
 * Implements hook_entity_insert().
 */
function drupal_rag_entity_insert(EntityInterface $entity) {
  _drupal_rag_index_entity($entity);
}

/**
 * Fonction interne pour indexer le contenu.
 */
function _drupal_rag_index_entity(EntityInterface $entity) {
  // Restreindre aux noeuds pour l'exemple
  if ($entity->getEntityTypeId() !== 'node') {
    return;
  }

  // 1. Extraire le texte pertinent (Titre + Corps)
  $text = $entity->getTitle() . ' ' . $entity->get('body')->value;
  // Nettoyer les balises HTML
  $clean_text = strip_tags($text);

  // 2. Appeler notre service d'embedding local
  $client = new Client();
  try {
    $response = $client->post('http://localhost:8000/embed', [
      'json' => ['text' => $clean_text]
    ]);
    $data = json_decode($response->getBody(), TRUE);
    $vector = $data['embedding'];

    // 3. Stocker dans PostgreSQL avec pgvector
    $connection = \Drupal::database();
    
    // Le cast ::vector est nécessaire pour pgvector
    // Attention : ceci est un exemple basique, sécurisez vos requêtes en production.
    $vector_string = '[' . implode(',', $vector) . ']';
    
    $connection->query(
      "INSERT INTO {drupal_embeddings} (entity_type, entity_id, embedding) 
       VALUES (:type, :id, :embedding::vector)
       ON CONFLICT (entity_type, entity_id) 
       DO UPDATE SET embedding = EXCLUDED.embedding",
      [
        ':type' => $entity->getEntityTypeId(),
        ':id' => $entity->id(),
        ':embedding' => $vector_string,
      ]
    );

  } catch (\Exception $e) {
    \Drupal::logger('drupal_rag')->error('Erreur d\'embedding : ' . $e->getMessage());
  }
}

3. Restitution : La recherche hybride dans Drupal

La recherche hybride est le Graal actuel : elle combine la précision des mots-clés (recherche lexicale classique de Search API / Solr) avec la compréhension du contexte (recherche sémantique via pgvector).

Pour cela, lors d'une recherche utilisateur dans Drupal, il faut :

  • Générer l'embedding de la requête de l'utilisateur (via le même service local).
  • Interroger la table drupal_embeddings en triant par distance cosinus.
<?php
// Fichier : DrupalRagSearchService.php

namespace Drupal\drupal_rag\Service;

use GuzzleHttp\Client;
use Drupal\Core\Database\Connection;

class DrupalRagSearchService {

  protected $database;

  public function __construct(Connection $database) {
    $this->database = $database;
  }

  public function semanticSearch(string $query, int $limit = 5) {
    // 1. Obtenir l'embedding de la requête
    $client = new Client();
    $response = $client->post('http://localhost:8000/embed', [
      'json' => ['text' => $query]
    ]);
    $vector = json_decode($response->getBody(), TRUE)['embedding'];
    $vector_string = '[' . implode(',', $vector) . ']';

    // 2. Interroger PostgreSQL en utilisant l'opérateur de distance cosinus (<=>)
    $sql = "
      SELECT entity_id, (embedding <=> :query_vector::vector) AS distance
      FROM {drupal_embeddings}
      WHERE entity_type = 'node'
      ORDER BY distance ASC
      LIMIT :limit
    ";

    $results = $this->database->query($sql, [
      ':query_vector' => $vector_string,
      ':limit' => $limit,
    ])->fetchAll();

    // 3. Retourner les IDs des entités pertinentes (pour les charger via EntityTypeManager)
    return array_column($results, 'entity_id');
  }
}

Vous pouvez ensuite utiliser ces IDs pour influencer les résultats de Search API ou construire une vue (Views) sur mesure.

Foire Aux Questions (FAQ)

Qu'est-ce que l'extension pgvector pour PostgreSQL ? Pgvector est une extension open-source pour PostgreSQL qui permet de stocker des vecteurs mathématiques et d'effectuer des recherches de similarité directement dans la base de données (idéal pour le RAG).

Pourquoi utiliser une IA open-source locale plutôt qu'OpenAI ? Pour garantir la souveraineté totale de vos données d'entreprise (conformité RGPD), éviter les coûts récurrents liés aux tokens d'API, et ne pas dépendre d'un fournisseur externe.

Quel est le modèle d'embedding recommandé pour une installation locale ? Pour des serveurs sans gros GPU, des modèles légers comme all-MiniLM-L6-v2 ou all-mpnet-base-v2 (disponibles sur Hugging Face) offrent un excellent compromis entre performances et précision sémantique.

Peut-on combiner cette recherche vectorielle avec Apache Solr dans Drupal ? Absolument. C'est ce qu'on appelle la recherche hybride. Solr peut gérer la recherche lexicale (mots-clés exacts) et les filtres à facettes, tandis que pgvector remonte les résultats sémantiquement proches, offrant le meilleur des deux mondes.

Cyprien Prouvot

Cyprien Prouvot

Associé & Directeur Technique

Associé de l'agence, Cyprien pilote la vision technique et garantit la qualité des développements web. Il encadre les équipes internes et conseille les clients sur les choix d'architecture ou d'outils digitaux les plus pertinents. Il intervient également sur ce blog pour décrypter l'écosystème web, les tendances tech et les bonnes pratiques de conception.


Prêt ? Partez.

Que ce soit pour vous aider à faire le point sur vos besoins ou vous présenter les avantages et fonctionnalités de nos solutions, nous sommes là.
 

Back to top