Aller au contenu
IA 8 min de lecture

RAG : connecter vos LLMs à vos données internes

Retrieval Augmented Generation (RAG) : architecture, implémentation avec LangChain4j ou Spring AI, choix de la base vectorielle et bonnes pratiques pour un assistant IA sur vos données.

RAGLLMIAJavaSpring AIVecteurs

Un LLM sans contexte répond en généraliste. Avec RAG (Retrieval Augmented Generation), il répond depuis vos données. C’est la différence entre un assistant qui dit “je ne sais pas” et un assistant qui cite votre documentation interne avec précision. Voici comment le construire en Java.

Le problème que RAG résout

Les LLMs ont deux limites fondamentales pour un usage en entreprise :

  1. Données coupées dans le temps : leur connaissance s’arrête à leur date d’entraînement
  2. Hallucination sur des données propriétaires : ils inventent des réponses sur des données qu’ils n’ont pas vues

RAG contourne ces deux limites. À chaque question, on cherche les passages pertinents dans vos documents, on les donne au LLM avec la question, et le LLM répond depuis ce contexte. Il n’invente pas — il synthétise ce qu’on lui donne.

Architecture d’un pipeline RAG

Phase d'indexation (une fois, offline) :
Documents → Découpage (chunking) → Embedding → Base vectorielle

Phase de requête (à chaque question) :
Question → Embedding → Recherche sémantique → Passages pertinents
                                              → LLM → Réponse

Embedding : transformer du texte en vecteur numérique (768 à 3072 dimensions selon le modèle). Des textes sémantiquement proches ont des vecteurs proches. C’est ce qui permet la recherche par sens, pas seulement par mots-clés.

Implémentation avec Spring AI

Spring AI (Spring Boot 3.3+) standardise l’intégration des LLMs et des bases vectorielles.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-sonnet-4-6
    vectorstore:
      pgvector:
        dimensions: 1536
        distance-type: COSINE_DISTANCE
        index-type: HNSW

Indexation des documents

@Service
public class DocumentIndexingService {

    private final VectorStore vectorStore;
    private final DocumentReader pdfReader;

    // Indexer un PDF
    public void indexDocument(MultipartFile file) throws IOException {
        // 1. Lire le PDF
        List<Document> documents = new TikaDocumentReader(file.getResource())
            .get();

        // 2. Découper en chunks (max 800 tokens, overlap 100)
        TextSplitter splitter = new TokenTextSplitter(800, 100, 5, 10000, true);
        List<Document> chunks = splitter.apply(documents);

        // 3. Enrichir les métadonnées
        chunks.forEach(chunk -> {
            chunk.getMetadata().put("filename", file.getOriginalFilename());
            chunk.getMetadata().put("indexed_at", Instant.now().toString());
        });

        // 4. Vectoriser et stocker (embedding + insertion PgVector)
        vectorStore.add(chunks);

        log.info("Indexé {} chunks depuis {}", chunks.size(), file.getOriginalFilename());
    }

    // Indexer une URL (documentation web)
    public void indexUrl(String url) {
        List<Document> docs = new JsoupDocumentReader(url).get();
        TextSplitter splitter = new TokenTextSplitter(600, 80, 5, 10000, true);
        vectorStore.add(splitter.apply(docs));
    }
}

Recherche et génération (le RAG proprement dit)

@Service
public class RagService {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    private static final String SYSTEM_PROMPT = """
        Tu es un assistant expert qui répond uniquement à partir des informations fournies dans le contexte.
        Si la réponse n'est pas dans le contexte, dis clairement que tu ne trouves pas l'information.
        Ne déduis pas et ne complètes pas avec des informations extérieures.
        Cite les sources (nom du document) quand c'est pertinent.
        """;

    public RagResponse answer(String question) {
        // 1. Recherche sémantique des passages pertinents
        List<Document> relevantDocs = vectorStore.similaritySearch(
            SearchRequest.query(question)
                .withTopK(5)                    // 5 passages les plus proches
                .withSimilarityThreshold(0.7)   // score minimum de similarité
        );

        if (relevantDocs.isEmpty()) {
            return new RagResponse(
                "Je ne trouve pas d'information sur ce sujet dans la base de connaissance.",
                List.of()
            );
        }

        // 2. Construire le contexte
        String context = relevantDocs.stream()
            .map(doc -> "Source : " + doc.getMetadata().get("filename") + "\n" + doc.getContent())
            .collect(Collectors.joining("\n\n---\n\n"));

        // 3. Appeler le LLM avec question + contexte
        String answer = chatClient.prompt()
            .system(SYSTEM_PROMPT)
            .user(u -> u.text("""
                Contexte :
                {context}

                Question : {question}
                """)
                .param("context", context)
                .param("question", question))
            .call()
            .content();

        // 4. Retourner réponse + sources
        List<String> sources = relevantDocs.stream()
            .map(d -> (String) d.getMetadata().get("filename"))
            .distinct()
            .toList();

        return new RagResponse(answer, sources);
    }
}

Choisir sa base vectorielle

BaseUsagePoints forts
pgvectorPostgreSQL extensionSimple si vous utilisez déjà Postgres
QdrantService dédiéTrès performant, filtres avancés
WeaviateService dédiéGraphQL natif, multi-tenancy
ChromaEmbedded / serviceIdéal pour prototyper rapidement
OpenSearchService AWSDéjà utilisé en prod dans beaucoup d’entreprises

Pour démarrer : pgvector si vous avez Postgres, Qdrant si vous voulez dédier un service.

Chunking : le point le plus critique

La qualité du chunking détermine 50% de la qualité des réponses RAG.

Trop grands chunks : la recherche retourne des passages trop larges, le LLM est noyé dans du contexte non pertinent.

Trop petits chunks : la recherche retourne des fragments sans contexte, le LLM manque d’information pour répondre.

Bonnes pratiques :

  • 400-800 tokens par chunk avec 10-20% d’overlap
  • Respecter les frontières naturelles (paragraphes, sections)
  • Conserver les métadonnées (titre de section, page, document source)
  • Tester avec vos vraies questions et vos vrais documents

Évaluation de la qualité

Un RAG non évalué est un RAG non fiable. Construisez un jeu de test :

// Jeu de test : questions avec réponses attendues
record RagTestCase(String question, String expectedAnswer, String source) {}

@Test
void evaluateRagPrecision() {
    List<RagTestCase> testCases = loadTestCases();
    int correct = 0;

    for (RagTestCase tc : testCases) {
        RagResponse response = ragService.answer(tc.question());
        // Évaluation par LLM (plus robuste que la comparaison exacte)
        boolean isCorrect = evaluator.isAnswerCorrect(
            tc.question(), response.answer(), tc.expectedAnswer());
        if (isCorrect) correct++;
    }

    double precision = (double) correct / testCases.size();
    log.info("Précision RAG : {}%", Math.round(precision * 100));
    assertThat(precision).isGreaterThan(0.80); // seuil minimum
}

Conclusion

RAG est la brique technique qui transforme un LLM généraliste en expert de votre domaine. La clé du succès : des documents bien structurés, un chunking adapté à votre contenu, et une évaluation continue de la qualité des réponses. Une implémentation RAG bien faite répond mieux qu’un moteur de recherche classique sur des questions en langage naturel.

Amine MEGDICHE

Amine MEGDICHE

Développeur AEM & Java Full Stack — Freelance depuis 2013

Vous avez un projet sur ces sujets ?

Envoyez-moi un message pour qualifier votre besoin, vos contraintes techniques et le bon format d'intervention.

Cadrer mon besoin