Aller au contenu principal
Article

RAG avec Spring AI : Construire un pipeline d'ingestion de données

Le RAG (Retrieval-Augmented Generation) permet d'enrichir les réponses d'un LLM avec vos propres données. Dans cet article, nous construisons le pipeline d'ingestion : lecture de documents, découpage en chunks, génération d'embeddings et stockage dans PostgreSQL/PGVector.

7 min de lecture
spring-aiiallmjavaspring-bootrag
spring-aiiallm

Les LLM sont entraînés sur des données publiques et ont une date de coupure de connaissances. Ils ne connaissent ni vos documents internes, ni vos données métier. Le RAG (Retrieval-Augmented Generation) résout ce problème en enrichissant le prompt avec des informations pertinentes extraites de vos propres sources de données.

Dans cet article, nous allons construire le pipeline d'ingestion : la première étape indispensable du RAG. C'est le processus ETL (Extract, Transform, Load) qui prépare vos données pour la recherche sémantique.

A- Le RAG en un coup d'œil

Le RAG fonctionne en deux phases :

Phase 1 : Ingestion (cet article)

Document → Lecture → Découpage (chunks) → Embeddings → Base vectorielle

Phase 2 : Requête (prochain article)

Question → Embedding → Recherche similaire → Contexte → LLM → Réponse

L'idée est simple : au lieu de faire confiance uniquement aux connaissances du modèle, on lui fournit du contexte pertinent extrait de nos propres documents au moment de la requête.

B- Infrastructure : PostgreSQL + PGVector

Pour stocker les embeddings (représentations vectorielles des documents), nous utilisons PostgreSQL avec l'extension PGVector. Le projet fournit un docker-compose.yml prêt à l'emploi :

name: spring-ai-en-action
services:
  postgres:
    image: pgvector/pgvector:pg17
    container_name: lab-postgres
    environment:
      - 'POSTGRES_DB=demo_db'
      - 'POSTGRES_PASSWORD=demo'
      - 'POSTGRES_USER=demo'
    ports:
      - "5438:5432"
# Démarrer PostgreSQL avec PGVector
docker compose up -d

L'image pgvector/pgvector:pg17 inclut PostgreSQL 17 avec l'extension PGVector préinstallée.

C- Le module data-ingestion

Le module data-ingestion illustre les trois étapes du pipeline ETL de Spring AI.

Dépendances

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
  • spring-ai-starter-model-ollama : fournit le modèle d'embeddings local
  • spring-ai-starter-vector-store-pgvector : auto-configure le VectorStore avec PGVector

Configuration

spring:
  ai:
    ollama:
      chat:
        model: mistral
      embedding:
        model: mxbai-embed-large
  datasource:
    url: jdbc:postgresql://localhost:5438/demo_db
    username: demo
    password: demo

Le modèle d'embeddings mxbai-embed-large est un modèle d'Ollama optimisé pour la génération de vecteurs sémantiques. Il transforme du texte en vecteurs numériques.

ollama pull mxbai-embed-large

Le pipeline complet

@SpringBootApplication
public class DataIngestionApplication {
 
    static void main(String[] args) {
        SpringApplication.run(DataIngestionApplication.class, args);
    }
 
    @Bean
    CommandLineRunner runnerDataIngestion(
            VectorStore vectorStore,
            JdbcTemplate jdbcTemplate,
            @Value("classpath:/corpus/recettes_cuisine_europe_africaine.txt")
            Resource file) {
        return _ -> {
            // Nettoyage du vector store
            jdbcTemplate.update("delete from vector_store");
 
            // 1. Extract : lecture du document
            var documents = new TextReader(file).read();
 
            // 2. Transform : découpage en chunks
            var chunks = TokenTextSplitter.builder()
                    .withChunkSize(300)
                    .withMinChunkLengthToEmbed(20)
                    .build()
                    .apply(documents);
 
            // 3. Load : stockage dans le vector store
            vectorStore.add(chunks);
        };
    }
}

D- Étape par étape : le pipeline ETL

Extract : TextReader

var documents = new TextReader(file).read();

TextReader lit un fichier texte et le convertit en une liste d'objets Document. Spring AI fournit plusieurs readers :

ReaderFormat
TextReaderFichiers texte brut (.txt)
JsonReaderFichiers JSON
TikaDocumentReaderPDF, Word, HTML, etc. (via Apache Tika)
PagePdfDocumentReaderPDF (page par page)

Transform : TokenTextSplitter

var chunks = TokenTextSplitter.builder()
        .withChunkSize(300)
        .withMinChunkLengthToEmbed(20)
        .build()
        .apply(documents);

Le TokenTextSplitter découpe les documents en chunks (morceaux) de taille contrôlée :

  • chunkSize(300) : chaque chunk contient environ 300 tokens
  • minChunkLengthToEmbed(20) : les chunks trop courts (< 20 caractères) sont ignorés

Le découpage est crucial : des chunks trop grands diluent l'information, des chunks trop petits perdent le contexte.

Load : VectorStore

vectorStore.add(chunks);

Pour chaque chunk, Spring AI :

  1. Génère un embedding (vecteur numérique) via le modèle d'embeddings configuré
  2. Stocke le chunk et son vecteur dans la base vectorielle PGVector

Le VectorStore est auto-configuré grâce au starter PGVector. Spring AI crée automatiquement la table vector_store avec les colonnes nécessaires.

E- Les embeddings : comment ça marche ?

Un embedding est une représentation numérique d’un texte dans un espace vectoriel. Deux textes sémantiquement proches y sont représentés par des vecteurs proches.

"recette de risotto" → [0.12, -0.45, 0.78, ..., 0.33]  (1024 dimensions)
"comment faire un risotto" → [0.11, -0.44, 0.79, ..., 0.34]  (très proche !)
"météo à Paris" → [0.89, 0.23, -0.56, ..., -0.12]  (très différent)

Le modèle mxbai-embed-large génère des vecteurs de 1024 dimensions. La similarité cosinus est utilisée pour mesurer la proximité entre deux vecteurs.

F- Bases vectorielles supportées

Spring AI supporte de nombreuses bases vectorielles via une API unifiée :

BaseStarter
PostgreSQL/PGVectorspring-ai-starter-vector-store-pgvector
Chromaspring-ai-starter-vector-store-chroma
Pineconespring-ai-starter-vector-store-pinecone
Milvusspring-ai-starter-vector-store-milvus
Redisspring-ai-starter-vector-store-redis
Elasticsearchspring-ai-starter-vector-store-elasticsearch
Neo4jspring-ai-starter-vector-store-neo4j

Changer de base vectorielle ne nécessite que de modifier la dépendance et la configuration, le code Java reste identique.

G- Exécution et vérification

# 1. Démarrer PostgreSQL
docker compose up -d
 
# 2. Télécharger les modèles
ollama pull mistral
ollama pull mxbai-embed-large
 
# 3. Lancer l'ingestion
./mvnw spring-boot:run -pl rag/data-ingestion

Vous pouvez vérifier les données dans PostgreSQL :

SELECT count(*) FROM vector_store;
-- Résultat : nombre de chunks ingérés
 
SELECT content, embedding FROM vector_store LIMIT 1;
-- Contenu du chunk et son vecteur

Conclusion

Le pipeline d'ingestion est la fondation du RAG. En quelques lignes de code, Spring AI nous permet de :

  • Lire des documents
  • Découper en chunks optimisés avec TokenTextSplitter
  • Vectoriser et stocker avec le VectorStore auto-configuré

Dans le prochain article, nous verrons comment interroger ces données : du RAG naïf avec recherche par similarité au RAG avancé avec réécriture et expansion de requêtes.

J'espère que cet article vous a été utile. Merci de l'avoir lu.

Pour en savoir plus :


Série « Spring AI en Action »

  1. Introduction à Spring AI
  2. ChatClient API : Premiers pas avec l'API
  3. Chat Memory : contexte conversationnel
  4. RAG : Pipeline d'ingestion
  5. RAG : Du Naïf à l'Avancé
  6. Function Calling
  7. Tools + Security
  8. Orchestration multi-agents
  9. Model Context Protocol (MCP)
PartagerXLinkedIn