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.
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 vectoriellePhase 2 : Requête (prochain article)
Question → Embedding → Recherche similaire → Contexte → LLM → RéponseL'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 -dL'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 localspring-ai-starter-vector-store-pgvector: auto-configure leVectorStoreavec PGVector
Configuration
spring:
ai:
ollama:
chat:
model: mistral
embedding:
model: mxbai-embed-large
datasource:
url: jdbc:postgresql://localhost:5438/demo_db
username: demo
password: demoLe 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-largeLe 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 :
| Reader | Format |
|---|---|
TextReader | Fichiers texte brut (.txt) |
JsonReader | Fichiers JSON |
TikaDocumentReader | PDF, Word, HTML, etc. (via Apache Tika) |
PagePdfDocumentReader | PDF (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 tokensminChunkLengthToEmbed(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 :
- Génère un embedding (vecteur numérique) via le modèle d'embeddings configuré
- 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 :
| Base | Starter |
|---|---|
| PostgreSQL/PGVector | spring-ai-starter-vector-store-pgvector |
| Chroma | spring-ai-starter-vector-store-chroma |
| Pinecone | spring-ai-starter-vector-store-pinecone |
| Milvus | spring-ai-starter-vector-store-milvus |
| Redis | spring-ai-starter-vector-store-redis |
| Elasticsearch | spring-ai-starter-vector-store-elasticsearch |
| Neo4j | spring-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-ingestionVous 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 vecteurConclusion
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
VectorStoreauto-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 :
- Documentation RAG : https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html
- Code source du projet : spring-ai-en-action
- Retrouvez nos vidéos #autourducode sur notre chaîne YouTube