Dans ce post, nous allons mettre en place un RAG (Retrieval-Augmented Generation) de manière simple. Le but n’est pas de réaliser la version optimisée au maximum mais plutôt de montrer les composants nécessaires et l’architecture la plus simple. Je détaillerai les optimisations possibles dans un futur post.
Le but du RAG est d’utiliser l’IA pour interroger une base de données de document structurés (TXT, CSV, etc) ou non structurés (PDF, DOCX, ec).
Etapes :
Phase 1 : Intégration des documents dans la base de données vectorielle :
- Lecture des documents via Apache Tika ou autre librairie fournie par Spring Boot
- Découpage en bloacs (chunks)
- Transformation des chuncks en embeddings (vecteurs) et stockage dans une BDD vectorielle (SimpleVectorStore, PgVector, etc) via un modèle IA d’embedding
Phase 2 : Interroger la base
- L’utilisateur envoie une question
- On génère un prompt à partir de la question et on crée un embedding de la question (vectorisation de la question toujours via le modèle d’embedding)
- On compare ce vecteur avec les vecteurs stockés dans la base vectorielle (via un similarity search) ce qui permet d’avoir la liste des documents correspondant
- On envoie le tout à un modèle de chat qui analyse et qui répond
- On retourne le résultat à l’utilisateur
Étapes de mise en place technique :
- Installation d’Ollama qui va servir de modèle IA d’embedding et de modèle IA de chat
- Création d’un projet Spring Boot
3 Ajout des dépendances (Spring AI, Apache Tika). - Configuration d’Ollama dans Spring Boot
- Utilisation d’Apache Tika pour extraire le texte de documents (PDF, DOCX, etc.)
- Création d’un magasin de vecteurs en mémoire pour stocker les embeddings des documents
- Mise en place d’un service RAG pour interroger les documents
Liste des modèles d’embedding disponibles : Embeddings Model API :: Spring AI Reference
On installe le modèle en local via ollama puis on pointe dessus via le fichier de configuration spring. On prend un modèle léger à 8 milliards de paramètres afin de pouvoir l’installer en local. Le modèle nécessite 5Go de mémoire disponible.
Prérequis:
- Java 17 ou plus
- Maven ou Gradle
- Ollama installé et en cours d’exécution localement (lien d’installation Download Ollama on Linux)
Récupération du modèle (LLM) :
Une fois ollama installé, il faut récupérer le modèle souhaité. Nous allons utiliser llama3
ollama run llama3.1:8b
Dépendances maven :
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!-- si besoin d'un parsing des PDF -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Configuration Spring :
application.properties :
# Ollama
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.model=llama3.1:8b
# Vector Store (en mémoire)
spring.ai.vectorstore.simple.persistent=false
Lecteur de documents (extrait avec Tika) :
import org.springframework.ai.document.DocumentReader;
import org.springframework.ai.tika.TikaDocumentReader;
import org.springframework.core.io.Resource;
public List<Document> readDocs(Resource resource) {
DocumentReader reader = new TikaDocumentReader(resource);
return reader.get();
}
Stockage vectoriel :
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.vectorstore.SimpleVectorStore;
@Service
public class VectorStoreService {
private final SimpleVectorStore vectorStore;
public VectorStoreService(EmbeddingClient embeddingClient) {
this.vectorStore = new SimpleVectorStore(embeddingClient);
}
public void addDocuments(List<Document> documents) {
vectorStore.add(documents);
}
public List<Document> search(String query) {
return vectorStore.similaritySearch(query);
}
}
Service :
@Service
public class RagService {
private final VectorStoreService vectorStore;
private final OllamaChatClient chatClient;
public RagService(VectorStoreService vectorStore, OllamaChatClient chatClient) {
this.vectorStore = vectorStore;
this.chatClient = chatClient;
}
public String ask(String question) {
// 1. Recherche de documents pertinents
List<Document> relevantDocs = vectorStore.search(question);
// 2. Construction du contexte
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n"));
// 3. Prompt avec contexte
String prompt = """
Contexte:
%s
Question: %s
Réponse:
""".formatted(context, question);
// 4. Appel au modèle
return chatClient.call(prompt);
}
}
Webservice à appeler pour interroger la base documentaire :
@RestController
@RequestMapping("/api/rag")
public class RagController {
private final RagService ragService;
public RagController(RagService ragService) {
this.ragService = ragService;
}
@PostMapping("/ask")
public String askQuestion(@RequestBody String question) {
return ragService.ask(question);
}
}
Chargement des documents au démarrage de l’application :
@Bean
CommandLineRunner init(VectorStoreService vectorStore) {
return args -> {
Resource pdfFile = new ClassPathResource("documents/mon-document.pdf");
List<Document> docs = readDocs(pdfFile);
vectorStore.addDocuments(docs);
};
}
Résumé des étapes :
-
Ollama tourne localement sur http://localhost:11434 -
Tika extrait le texte des documents (PDF/DOCX) -
Les embeddings sont stockés en mémoire (SimpleVectorStore) -
Pour chaque question : Recherche des documents similaires Construction d'un prompt contextuel Génération de réponse via Llama
