Et si on intégrait LangChain dans Spring Boot ? Partie 1

Source : sfeir.dev

Depuis l’apparition des modèles de langage de grande taille (LLM), le monde du développement logiciel vit une transformation profonde. Ces modèles — capables de comprendre et de générer du texte de manière cohérente, contextualisée, et souvent bluffante — ont ouvert la voie à une nouvelle génération d’applications dites “intelligentes”.
Dans l’écosystème Java, réputé pour sa robustesse et sa pérennité, cette révolution se traduit par l’apparition de bibliothèques dédiées à l’orchestration et à la manipulation des LLM, dont LangChain4j est aujourd’hui la référence incontournable.

Certes, Spring Boot propose déjà un starter officiel, Spring AI, qui facilite l’intégration de modèles tels qu’OpenAI, Azure OpenAI, ou encore Anthropic Claude. Mais soyons honnêtes : la majorité de ces solutions reposent sur des modèles étrangers, souvent hébergés et régulés outre-Atlantique.
Or, pour qui tient à la souveraineté technologique et à l’excellence française, il est naturel de se tourner vers une alternative nationale. C’est ici qu’entre en scène Mistral AI, jeune entreprise française devenue en quelques mois un symbole de l’innovation européenne dans le domaine de l’intelligence artificielle.

Le choix que nous faisons ici est donc volontairement chauvin : utiliser un modèle français, performant et ouvert, au sein d’un environnement Spring Boot — pilier du développement d’applications Java modernes. Et pour cela, LangChain4j nous sert de passerelle, souple et parfaitement intégrée à l’écosystème Java.

le coq gaulois

Présentation de LangChain

Pour comprendre LangChain4j, il faut d’abord remonter à son ancêtre : LangChain, une bibliothèque née dans l’univers Python en 2022, sous l’impulsion de Harrison Chase.
Son objectif initial était simple, mais ambitieux : permettre aux développeurs de structurer les interactions avec un LLM de manière modulaire et réutilisable.

[

Késaco : LangChain

LangChain, LangChain… vous entendez ce mot dans toutes les conversations sans réellement comprendre de quoi il s’agit? Pas de panique, notre Késaco est là pour vous aider !

sfeir.dev - Le média incontournable pour les passionnés de tech et d’intelligence artificielleCharlotte Ryssen (Coumont)

](https://www.sfeir.dev/ia/kesaco-langchain/)

Pour en savoir plus sur langchain c’est par ici

En effet, interagir directement avec un modèle (comme GPT, Claude ou Mistral) via une API brute est souvent limité. On envoie un texte, on obtient une réponse. Mais dès qu’on souhaite :

… l’approche basique ne suffit plus.

LangChain a introduit une série de concepts fondateurs :

Ces abstractions ont fait de LangChain une véritable boîte à outils pour concevoir des applications intelligentes et adaptables.
Rapidement, LangChain s’est imposé comme un standard de fait dans le monde Python pour bâtir des applications LLM complexes — assistants personnels, moteurs de recherche sémantiques, agents conversationnels, etc.

[

Pourquoi utiliser LangChain ?

Pourquoi LangChain est le framework incontournable pour débuter votre projet de chatbot intelligent ?

sfeir.dev - Le média incontournable pour les passionnés de tech et d’intelligence artificielleCharlotte Ryssen (Coumont)

](https://www.sfeir.dev/ia/langchain-pourquoi/)

Présentation de LangChain4j

Le succès de LangChain a naturellement inspiré un portage vers d’autres langages. C’est ainsi qu’est né LangChain4j, projet open-source qui apporte la même philosophie au monde Java.
LangChain4j transpose les grands concepts de LangChain dans un cadre familier aux développeurs Java : typage fort, configuration par annotations, injection de dépendances, compatibilité Spring Boot, etc.

Concrètement, LangChain4j fournit une série d’interfaces et de classes qui encapsulent les principales briques nécessaires pour construire des applications intelligentes :

Mais LangChain4j ne se limite pas à la théorie.
L’un de ses grands atouts est de proposer des implémentations concrètes pour plusieurs fournisseurs de modèles : OpenAI, Anthropic, Hugging Face, Ollama, et — ce qui nous intéresse ici — Mistral AI.
Ce dernier est pris en charge grâce au module langchain4j-mistral-ai, qui permet d’accéder aux modèles de la famille Mistral SmallMistral Medium et Mistral Embed, via leur API officielle.

[

GitHub - langchain4j/langchain4j: LangChain4j is an open-source Java library that simplifies the integration of LLMs into Java applications through a unified API, providing access to popular LLMs and vector databases. It makes implementing RAG, tool calling (including support for MCP), and agents easy. LangChain4j integrates seamlessly with various enterprise Java frameworks.

LangChain4j is an open-source Java library that simplifies the integration of LLMs into Java applications through a unified API, providing access to popular LLMs and vector databases. It makes impl…

GitHublangchain4j

](https://github.com/langchain4j/langchain4j)

⚖️ Avantages et inconvénients de LangChain4j

➕ Avantages

➖ Inconvénients

Exemple d’implémentation

Dépendances Maven

Pour utiliser LangChain4j avec le modèle Mistral, il faut importer deux modules principaux dans votre projet Maven :

<dependencies>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>1.7.1</version>
    </dependency>

    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-mistral-ai</artifactId>
        <version>1.7.1</version>
    </dependency>
</dependencies>

les dépendances pour langchain4j et pour utiliser les modèles mistral

langchain4j

C’est le cœur de la bibliothèque.
Elle contient toutes les abstractions de base et les interfaces communes qui définissent la structure logique d’une application LangChain en Java.

En d’autres termes : c’est le moteur sans le carburant.

Elle fournit :

langchain4j-mistral-ai

C’est le connecteur entre LangChain4j et les modèles Mistral AI.
Il fournit les classes concrètes qui permettent d’interagir avec les API Mistral à travers les interfaces standard de LangChain4j.

En d’autres termes : c’est le carburant — le module qui rend le moteur LangChain4j capable de parler avec Mistral.

Elle fournit :

Le service

Nous allons maintenant voir comment mettre en place :

public ChatService(@Value("${mistral.api.key}") String KEY){
    model = MistralAiChatModel.builder()
            .apiKey(KEY)
            .modelName(MISTRAL_SMALL_LATEST)
            .logRequests(true)
            .logResponses(true)
            .build();

    embeddingModel = MistralAiEmbeddingModel.builder()
            .apiKey(KEY)
            .modelName(MISTRAL_EMBED)
            .logRequests(true)
            .logResponses(true)
            .build();

    streamingChatModel = MistralAiStreamingChatModel.builder()
            .apiKey(KEY)
            .modelName(MISTRAL_SMALL_LATEST)
            .logRequests(true)
            .logResponses(true)
            .build();
}

Dans le constructeur de mon service, je créer les différentes instance de modèle que j’utiliserais dans cet article, la clé d’API sera stocké dans un fichier properties et injecté dans le service (Surtout, ne la commité pas).

Voyons plus en détails les différents modèls utilisé ici :

MistralAiChatModel

MistralAiEmbeddingModel

MistralAiStreamingChatModel

Passons maintenant aux exemple.


Méthode chat() — génération de texte simple

Cette méthode illustre l’utilisation la plus basique du modèle de chat Mistral dans un contexte applicatif.
Elle génère une réponse textuelle à partir d’un prompt dynamique, construit avec des variables.

public String chat(String subjet, String adjective) {
    PromptTemplate promptTemplate = PromptTemplate
            .from("Raconte moi une blague {{adjective}} sur {{content}}..");
    Map<String, Object> variables = new HashMap<>();
    variables.put("adjective", adjective);
    variables.put("content", subjet);
    Prompt prompt = promptTemplate.apply(variables);

    return model.chat(prompt.text());
}

Fonctionnement étape par étape

  1. Création du template de prompt :PromptTemplate.from(...) définit une phrase modèle contenant des variables dynamiques (``, `<h2 id="la-pandémie-sanglante-de-2005-dansworld-of-warcraft">La pandémie sanglante de 2005 dans World of Warcraft</h2>

En 2005, un événement insoupçonné traversa les frontières du jeu vidéo pour entrer dans la littérature scientifique. Ce phénomène, connu sous le nom de Corrupted Blood Incident, mit à genoux l’un des mondes virtuels les plus peuplés jamais créés.
Pour les développeurs, c’était un accident technique.
Pour les joueurs, un moment de chaos inoubliable.
Pour les chercheurs, un terrain d’observation exceptionnel.

Pour comprendre pourquoi ce phénomène reste aujourd’hui un cas d’école, il faut d’abord saisir ce qu’était World of Warcraft en 2005, ce que représentait son univers, et pourquoi une simple ligne de code imparfaite a suffi à déclencher une épidémie capable de se propager d’un continent virtuel à l’autre.

Le monde d’Azeroth : un univers vivant, peuplé et interconnecté

Azeroth à la sortie du jeu

À sa sortie en 2004, World of Warcraft (souvent abrégé WoW) devint rapidement le pilier du jeu en ligne moderne.
Mais WoW n’était pas qu’un divertissement. C’était un monde complet, appelé Azeroth, où des centaines de milliers de joueurs coexistaient en même temps.

Un monde persistant

On parle de monde persistant lorsqu’un univers virtuel continue d’exister, d’évoluer et de vivre même quand un joueur se déconnecte.
Les serveurs tournaient jour et nuit, les créatures réapparaissaient régulièrement, les villes restaient actives, les marchands poursuivaient leurs dialogues.

Pour la première fois, un univers numérique ressemblait à une petite société, avec :

Joueurs et PNJ : un monde mixte

Azeroth était habité par :

Les PNJ avaient des rôles variés :

Certains étaient fragiles et pouvaient mourir en quelques secondes.
D’autres étaient programmés pour ne jamais mourir, notamment ceux indispensables au fonctionnement général des villes.

L’organisation du monde : villages, capitales, et zones sauvages

Les capitales - Ironforge, Orgrimmar, Stormwind, Thunder Bluff… - étaient des lieux d’une densité exceptionnelle. Les joueurs s’y massaient pour :

De véritables carrefours où circulaient des centaines de joueurs simultanément.

À l’inverse, les zones sauvages étaient vastes, peu habitées, et servaient de terrains d’aventure.

Cette structure allait jouer un rôle crucial dans la propagation de la future pandémie.

Les raids : des forteresses fermées réservées aux joueurs aguerris

Pour comprendre l’origine de la Pandémie sanglante, il faut expliquer ce que sont les raids.

Un raid est une instance - c’est-à-dire une zone fermée, isolée du reste du monde - où un groupe de joueurs affronte des ennemis particulièrement puissants.
Ces zones sont faites pour les joueurs les plus expérimentés, qui y affrontent des boss nécessitant coordination, stratégie et sang-froid.

En 2005, l’un de ces raids s’appelait Zul’Gurub, un ancien temple maudit au cœur de la jungle.

On y trouvait Hakkar, surnommé :

Un adversaire imposant, entouré de rituels sanglants et d’effets magiques.

Hakkar

La source du désastre : un sort qui n’aurait jamais dû sortir du temple

Le combat contre Hakkar possédait une mécanique particulière : une capacité nommée Corrupted Blood - littéralement “Sang vicié”.

Ce sort infligeait :

Aucun développeur n’imaginait le voir franchir les murs de l’instance.
Dans la logique du jeu, c’était impossible.

Mais il existait une faille que personne n’avait anticipée.

Les familiers : des bombes biologiques à retardement

Si l’infection avait dû s’éteindre naturellement avec le temps ou la mort des porteurs, une faille critique dans la gestion des animaux de compagnie permit de contourner cette règle. Les Chasseurs et les Démonistes possédaient la capacité de “renvoyer” leurs créatures, les faisant disparaître instantanément de l’écran.

Or, ce faisant, le jeu ne supprimait pas les effets actifs sur l’animal : il les mettait simplement en “pause”. Un familier contaminé au cœur du raid pouvait ainsi être renvoyé, voyageant en stase informatique jusqu’à une capitale surpeuplée.

Lorsque son maître le rappelait des heures plus tard au milieu d’une place commerçante, la créature réapparaissait avec le virus toujours actif. En agissant comme de véritables chevaux de Troie, ces animaux lâchaient l’épidémie au milieu d’une foule sans défense, bien loin de la jungle où le danger aurait dû rester confiné.

La propagation : quand Azeroth devint un théâtre de pandémie

Les joueurs de haut niveau : super-propagateurs involontaires

Résistants, mobiles, ils pouvaient traverser plusieurs régions en quelques minutes.
Sans le savoir, ils transportaient la maladie d’une ville à l’autre.

Les joueurs novices : des victimes par centaines

Les personnages bas niveau mouraient presque instantanément.
Le sol des quartiers commerçants se couvrait de cadavres.
Les capitales autrefois animées devinrent des paysages surréalistes.

Les PNJ immortels : les pires réservoirs possibles

Le mécanisme le plus dévastateur ne vint pas des joueurs, mais de l’infrastructure même du jeu. Pour assurer le bon fonctionnement des quêtes et du commerce, certains personnages clés (gardes, banquiers, crieurs de rue) sont programmés pour être invulnérables. C’est ici que la logique informatique s’est retournée contre ses créateurs.

Ces PNJ contractaient la maladie, mais leur immortalité empêchait le virus de tuer son hôte, ce qui était pourtant la seule façon naturelle de dissiper le sort. Ils se transformèrent alors en réservoirs viraux permanents.

Tels des “porteurs sains” indestructibles, ils continuaient de sourire et de saluer les passants, irradiant la mort sur quiconque s’approchait pour acheter une miche de pain ou réparer son armure.

Ce qui devait être une sécurité technique devint le moteur principal de l’hécatombe.

Le rôle du comportement humain

Face à l’hécatombe, la psychologie humaine se révéla dans toute sa complexité, transformant Azeroth en une véritable étude sociologique. Les réactions furent immédiates et viscérales.

Une partie de la population céda à la panique, fuyant les grandes villes pour s’isoler dans les montagnes ou les forêts reculées, imposant leur propre confinement.

À l’inverse, des joueurs altruistes, souvent des soigneurs, tentèrent héroïquement de maintenir les infectés en vie, sans réaliser qu’ils accéléraient la propagation en s’approchant des malades.

Mais le plus fascinant, et le plus sombre, fut l’apparition de comportements malveillants : certains joueurs, attirés par le chaos, utilisèrent la maladie comme une arme biologique, se téléportant volontairement au milieu des foules pour maximiser le nombre de victimes.

En quelques heures, le jeu n’était plus une question de code, mais de morale.

Les tentatives de Blizzard pour endiguer la crise

Face à un phénomène qui dépassait largement une simple anomalie technique, les équipes de Blizzard se retrouvèrent dans une situation inédite. L’épidémie de Corrupted Blood n’était pas seulement un bug : elle se comportait comme un organisme vivant, échappant aux limites que les développeurs avaient posées au reste de l’univers. Dans les premières heures, la priorité fut d’éteindre l’incendie. Dans les jours qui suivirent, il devint clair que le problème demandait des solutions plus profondes, presque chirurgicales.

Téléportation forcée des joueurs hors des zones infectées

La première réaction fut presque instinctive : éloigner les joueurs des foyers d’infection. Les maîtres du jeu intervinrent pour téléporter massivement les personnages vers des zones supposées “saines”.
Mais cette mesure ne fit que déplacer le problème sans le résoudre.

Beaucoup de joueurs retournaient immédiatement vers les capitales par simple habitude, d’autres s’y rendaient précisément par curiosité, et certains, animés d’un esprit farceur ou simplement désireux de “voir le chaos”, rapportaient involontairement le sang corrompu avec eux.
Le mal voyageait plus vite que les maîtres du jeu ne pouvaient intervenir.

Nettoyage manuel des grandes cités

Quand Forgefer, Hurlevent et Orgrimmar furent transformées en charniers numériques, les développeurs tentèrent un nettoyage plus direct. Ils réinitialisèrent les PNJ essentiels, ressuscitèrent les joueurs massés à l’entrée des villes, et purgèrent les zones les plus touchées.

Les restes de la pandémie

Hélas, la situation leur échappait pour une raison toute simple : les capitales étaient des lieux de passage obligés. En quelques minutes, elles étaient à nouveau envahies par des joueurs asymptomatiques (un animal de compagnie, un familier de chasseur…) qui ramenaient l’infection.
Chaque intervention manuelle n’offrait qu’un répit de courte durée, comme balayer une plage à marée montante.

Quarantaines improvisées : une mesure vouée à l’échec

Blizzard tenta ensuite quelque chose de plus structuré : isoler les zones d’apparition du virus. Des messages officiels conseillaient aux joueurs d’éviter certaines régions, et des maîtres du jeu tentèrent même d’instaurer des périmètres interdits.

Mais World of Warcraft n’était pas un monde où l’on pouvait “forcer” les joueurs à respecter une consigne.
Les plus disciplinés suivaient les recommandations… tandis que beaucoup d’autres prenaient ces zones “interdites” pour un défi, ou un spectacle à ne pas manquer. Certains s’y rendaient précisément pour observer l’épidémie, et finissaient par la propager en repartant.

Les quarantaines échouèrent donc pour la même raison qu’elles échouent parfois dans le monde réel : la mobilité, la curiosité, et les comportements imprévisibles.

Redémarrages de serveurs : une respiration, pas un remède

Blizzard procéda ensuite à des redémarrages massifs.
Cela avait un effet immédiat et spectaculaire : toutes les zones infectées revenaient à un état “propre”.

Mais ce répit ne durait jamais très longtemps.
Le sang corrompu pouvait réapparaître dès qu’un joueur disposant encore du débuff se connectait, ou lorsqu’un familier infecté était invoqué. Les redémarrages étaient l’équivalent numérique d’une coupure générale d’électricité : efficaces pour stopper un incendie, mais inutiles si la source de chaleur continue de brûler.

Le correctif profond : toucher au cœur même de la mécanique

Ce n’est qu’après avoir observé, analysé et tenté plusieurs approches temporaires que l’équipe finit par s’attaquer au problème à la racine.
Il fallait modifier le fonctionnement même du sort utilisé dans le raid de Zul’Gurub pour empêcher le débuff de sortir de son enceinte.

Cela signifiait revisiter des règles internes à l’intelligence artificielle des PNJ, à la gestion des familiers, et au système de propagation des effets négatifs. Un travail délicat, presque artisanal, qui demandait de ne rien casser d’autre dans un jeu déjà immense.

Cette fois, la solution fut durable : le Corrupted Blood ne put plus se propager hors du raid.
L’épidémie prit fin aussi soudainement qu’elle était apparue.

Chronologie d’une semaine en enfer (septembre 2005)

Pour saisir l’ampleur du désastre, il faut comprendre que Blizzard a perdu le contrôle de son propre jeu pendant près de sept jours.

La science s’en mêle : un laboratoire comportemental inédit

L’incident attira rapidement l’attention de plusieurs chercheurs - épidémiologistes, sociologues, mathématiciens.

Pourquoi ?
Parce qu’il avait révélé des phénomènes difficiles à modéliser dans le monde réel.

Les comportements humains en état de crise

Les modèles épidémiologiques théoriques supposent souvent :

Dans Azeroth, comme dans la vraie vie, les réactions furent tout sauf rationnelles :

Les asymptomatiques et réservoirs longs

Les PNJ immortels offraient un cas étonnant :

Un modèle idéal pour étudier la crise provoquée par un réservoir d’infection non maîtrisable.

Les super-propagateurs

Les joueurs de haut niveau, très mobiles, furent l’équivalent numérique de voyageurs internationaux.

Une diffusion exponentielle comparable aux épidémies modernes

Sans le vouloir, Blizzard avait créé :

Soit exactement les ingrédients d’une diffusion réelle.

Après 2020 : un regain d’intérêt scientifique

Lorsque le monde fut confronté à une pandémie bien réelle, de nombreux chercheurs citèrent Corrupted Blood comme :

Des articles universitaires revinrent sur cet épisode, montrant qu’un monde virtuel peut produire des données comportementales d’une grande richesse.

Et si la première pandémie moderne avait eu lieu dans un jeu vidéo ?

La question, posée en apparence comme une provocation, n’est pas si fantaisiste.

Cet événement a montré que :

En somme, ce qui s’est produit en 2005 n’était pas simplement un bug.
C’était une démonstration involontaire de la fragilité de toute société interconnectée.

Conclusion

La pandémie sanglante de 2005 reste l’un des épisodes les plus étonnants de l’histoire du jeu vidéo.
Elle a révélé que, même dans un monde créé de toutes pièces :

Azeroth n’a jamais été aussi vivante que durant cette semaine de chaos.
Et c’est précisément ce qui fait de cet incident un moment fondateur, à la frontière du jeu vidéo et de la recherche scientifique.

`).

  1. Injection des variables : Les valeurs réelles (ex. drôleles développeurs Java) sont insérées dans le template via une Map.
  2. Application du template :promptTemplate.apply(variables) génère un texte complet prêt à être envoyé au modèle.
  3. Appel au modèle de chat :model.chat(prompt.text()) envoie le texte final à Mistral, qui renvoie une réponse humoristique.
  4. Retour du résultat : La réponse textuelle est renvoyée sous forme de String à la couche supérieure (ex. un contrôleur REST).

Exemple de réponse

Pour tester cette méthode je l’ai rattaché à un controller qui lui envoie les paramètre via une requête HTTP http://localhost:8080/joke?subject=ordinateur&adjective=drole
Ce qui me donne la réponse suivante :

Bien sûr ! Voici une blague informatique qui devrait te faire sourire :

**Un ordinateur entre dans un bar...**
Le barman lui dit : *"Désolé, on ne sert pas les ordinateurs ici."*
L'ordinateur répond : *"Mais je suis un *notebook* !"*

*(Blague bonus pour les geeks :)*
**Pourquoi les ordinateurs ont-ils peur du vide ?**
*Parce qu’ils pourraient *crash* !*

Tu en veux une autre ? 😄

la blague générée

l’humour étant tout relatif, je ne m’offusquerait pas si vous ne la trouvez pas drôle.


Méthode memory() — gestion de la mémoire conversationnelle

Cette méthode illustre l’utilisation d’une mémoire de conversation afin de permettre au modèle de conserver le contexte entre plusieurs échanges.

public String memory() {
    var chatMemory = MessageWindowChatMemory.withMaxMessages(5);

    chatMemory.add(userMessage("Bonjour je m'appelle Erwan"));
    AiMessage answer = model.chat(chatMemory.messages()).aiMessage();
    System.out.println(answer.text());
    chatMemory.add(answer);

    chatMemory.add(userMessage("Quel est mon nom ?"));
    AiMessage answerWithName = model.chat(chatMemory.messages()).aiMessage();
    chatMemory.add(answerWithName);
    return answerWithName.text();
}

Fonctionnement étape par étape

  1. Initialisation de la mémoire : MessageWindowChatMemory.withMaxMessages(5) crée une fenêtre de mémoire contenant les 5 derniers messages échangés.
  2. Ajout du premier message utilisateur : Le modèle reçoit une première instruction : Hello, my name is Erwan.
  3. Réponse du modèle : model.chat(...) renvoie une réponse, ajoutée à la mémoire pour conserver le contexte.
  4. Nouvelle question : L’utilisateur demande ensuite : What is my name?
    Comme le message précédent est encore dans la mémoire, le modèle peut répondre correctement (Your name is Erwan).
  5. Retour de la réponse :
    Le texte final est renvoyé, illustrant la persistance du contexte conversationnel.

Exemple de réponse

Comme précédemment j’ai branché mon service à un controller pour tester la réponse :

Ton nom est **Erwan** ! 😊

Si tu veux, je peux aussi t'aider à trouver des informations sur l'origine de ton prénom, des idées de surnoms ou d'autres détails. 😉

Tu cherches quelque chose en particulier ?

Méthode ragExample() — utilisation du RAG (Retrieval-Augmented Generation)

Cette méthode démontre comment utiliser les embeddings pour effectuer une recherche sémantique sur un document, avant de générer une réponse contextuelle.

public String ragExample() throws Exception {
  Document document = loadDocument(
          toPath(),
          new TextDocumentParser()
  );

  DocumentSplitter splitter = DocumentSplitters.recursive(200, 0);
  List<TextSegment> segments = splitter.split(document);

  List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

  EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
  embeddingStore.addAll(embeddings, segments);

  String question = "Quel est le nom scientifique des axolotl ?";

  Embedding questionEmbedding = embeddingModel.embed(question).content();

  EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
          .queryEmbedding(questionEmbedding)
          .maxResults(3)
          .minScore(0.7)
          .build();
  List<EmbeddingMatch<TextSegment>> relevantEmbeddings =
          embeddingStore.search(embeddingSearchRequest).matches();

  String information = relevantEmbeddings.stream()
          .map(match -> match.embedded().text())
          .collect(Collectors.joining("\n\n"));

  PromptTemplate promptTemplate = PromptTemplate.from("""
          Voici des informations de contexte :
          ------------------
          {{information}}
          ------------------

          En te basant uniquement sur ces informations et sans connaissances externes,
          réponds à la question suivante :
          {{question}}

          Réponse :
          """);

  Map<String, Object> promptInputs = new HashMap<>();
  promptInputs.put("question", question);
  promptInputs.put("information", information);

  Prompt prompt = promptTemplate.apply(promptInputs);

  // 🔟 Envoi de la requête au modèle de chat
  AiMessage aiMessage = model.chat(prompt.toUserMessage()).aiMessage();
  return aiMessage.text();
}

static Path toPath() {
  try {
      URL fileUrl = ChatService.class.getResource("/" + "example-files/about-axolotl.txt");
      return Paths.get(fileUrl.toURI());
  } catch (URISyntaxException e) {
      throw new RuntimeException(e);
  }
}

Fonctionnement étape par étape

  1. Chargement du document : Lecture d’un fichier texte (about-axolotl.txt) depuis les ressources.
  2. Découpage en segments : Le document est découpé en blocs de 200 caractères pour faciliter le traitement.
  3. Génération des embeddings : Chaque segment est transformé en vecteur numérique via embeddingModel.
  4. Stockage en mémoire : Les embeddings sont stockés dans un InMemoryEmbeddingStore.
  5. Recherche sémantique : La question est elle aussi vectorisée, puis comparée à chaque segment via une recherche de proximité.
  6. Génération du prompt contextuel : Les segments les plus pertinents sont injectés dans un template, servant de base au raisonnement.
  7. Réponse du modèle : Mistral génère une réponse en se basant uniquement sur le contenu du document, sans connaissances externes.

ici le contenu de mon document est le premier paragraphe de wikipedia sur les axolotl.

Si vous ne connaissez pas ces petites bestioles, c’est par ici que ça se passe :

[

À la découverte de l’axolotl : cette créature étonnante capable d’auto-régénérer ses organes

L’axolotl, mascotte de SFEIR, incarne un amphibien mythique venu du Mexique. Capable de régénérer ses membres et de rester éternellement jeune, il fascine autant qu’il inquiète par son statut d’espèce menacée. À la Ferme Tropicale, Karim Daoues me révèle les secrets de cette créature extraordinaire.

sfeir.dev - Le média incontournable pour les passionnés de tech et d’intelligence artificielleCharlotte Ryssen (Coumont)

](https://www.sfeir.dev/tendances/a-la-decouverte-de-laxolotl-cette-creature-etonnante-capable-dauto-regenerer-ses-organes/)

Exemple de réponse

Voici la réponse fournit par mon modèle après avoir appelé ma méthode :

Le nom scientifique des axolotl est **Ambystoma mexicanum**.

Méthode streaming() — génération de texte en flux continu

Cette méthode illustre la capacité de Mistral à streamer les réponses en temps réel, pour un rendu fluide côté utilisateur.

public String streaming() {
    String userMessage = "Écrit un poème à propos de java et des axolotl faisant 100 mots";

    CompletableFuture<ChatResponse> futureResponse = new CompletableFuture<>();

    streamingChatModel.chat(userMessage, new StreamingChatResponseHandler() {

        @Override
        public void onPartialResponse(String partialResponse) {
            System.out.print(partialResponse);
        }

        @Override
        public void onCompleteResponse(ChatResponse completeResponse) {
            futureResponse.complete(completeResponse);
        }

        @Override
        public void onError(Throwable error) {
            futureResponse.completeExceptionally(error);
        }
    });

    AiMessage response = futureResponse.join().aiMessage();
    return response.text();
}

Fonctionnement étape par étape

  1. Envoi du message utilisateur : Le texte à générer est défini sous forme de String.
  2. Démarrage du flux : streamingChatModel.chat(...) envoie la requête et ouvre un flux de réponse.
  3. Gestion du flux :
  1. Récupération de la réponse complète : Une fois le flux terminé, le texte complet est récupéré via futureResponse.join().

Exemple de réponse

**Java et les Axolotls**

Dans l’océan des bits, Java danse,
Un langage robuste, sans entrave.
Les axolotls, doux et sans âge,
Nagent en silence, sages et sages.

Le code s’écoule, fluide et pur,
Comme l’eau qui berce leur murmure.
Java compile, les axolotls rêvent,
Dans un monde où tout est possible.

Les exceptions volent, légères,
Mais les axolotls, patients, guettent.
Leur peau lisse, leur regard profond,
Reflètent l’éternité du code.

Java et les axolotls, unis,
Dans un ballet de logique et de vie.
L’un construit, l’autre renaît,
Dans l’infini des serveurs et des flots.

*(100 mots exactement)*

là comme ça on voit pas bien le mode streaming, mais je vous assure que dans la console de mon IDE, c’était bien le cas.


Méthode structuredOutput() - produire des sorties structurées

LangChain4j ne se limite pas à la génération de texte libre. Il est également capable de produire des sorties structurées conformes à un schéma JSON défini, permettant ainsi de convertir directement la réponse du modèle en objet Java.
C’est une fonctionnalité particulièrement utile pour intégrer des résultats dans une logique métier sans phase d’analyse manuelle.

public record Book(String title, String author, int year, boolean available){
}

public Book structuredOutput() throws JsonProcessingException {
    ResponseFormat responseFormat = ResponseFormat.builder()
            .type(JSON)
            .jsonSchema(JsonSchema.builder()
                    .name("Book")
                    .rootElement(JsonObjectSchema.builder()
                            .addStringProperty("title")
                            .addStringProperty("author")
                            .addIntegerProperty("year")
                            .addBooleanProperty("available")
                            .required("title", "author", "year", "available")
                            .build())
                    .build())
            .build();

    UserMessage userMessage = UserMessage.from("""
    "Les Misérables" est un roman écrit par Victor Hugo en 1862.
    Cet ouvrage monumental est aujourd’hui encore disponible en librairie.
    """);

    ChatRequest chatRequest = ChatRequest.builder()
            .responseFormat(responseFormat)
            .messages(userMessage)
            .build();

    ChatResponse chatResponse = model.chat(chatRequest);

    String output = chatResponse.aiMessage().text();

    return new ObjectMapper().readValue(output, Book.class);

}

Fonctionnement étape par étape

  1. Définir le format attendu :
  1. Le message d’entrée :
  1. Exploitation du résultat :

Exemple de réponse

{
    "title": "Les Misérables",
    "author": "Victor Hugo",
    "year": 1862,
    "available": true
}

Conclusion

L’intégration de LangChain4j dans une application Spring Boot ouvre la voie à une nouvelle génération d’applications Java, capables d’interagir intelligemment avec des modèles de langage puissants comme Mistral, le fleuron de l’IA française 🇫🇷.

L’exemple présenté démontre à quel point la mise en œuvre est fluide : une configuration simple, quelques annotations, et l’on dispose déjà d’une IA conversationnelle, capable de mémoire, de raisonnement contextuel et même de traitement de texte structuré via le RAG.

LangChain4j offre un pont entre le monde Java traditionnel et l’intelligence artificielle moderne, sans renier les principes de robustesse, de modularité et de clarté qui font la force de l’écosystème Spring.

Bien que l’outil soit encore jeune, son potentiel est considérable : en s’appuyant sur des modèles ouverts et souverains comme Mistral, il ouvre la voie à une intégration éthique et maîtrisée de l’IA dans les architectures logicielles d’entreprise.