Comme j’interviens sur des projets développés en Java s’est posé rapidement la question de comment intégrer des fonctionnalités utilisant de l’IA générative au sein de ces projets. Les outils python les plus communs sont bien entendus LangChain et LlamaIndex.
Un collègue m’a montré OpenAi-Java, un client non officiel pour utiliser les APIs OpenAI. A savoir que Azur dans son SDK propose aussi un client supportant la connexion à Azure OpenAi Service et aux API OpenAi.
Ces librairies ne sont que des clients supportant la connexion à un service (enfin deux dans le cas de la lib Azure). Dans le monde en évolution rapide des LLMs il semble dommage de se coupler ainsi à un fournisseur de service. A ma connaissance en Java nous avons actuellement deux choix possibles : LangChain4J ou Spring AI.
LangChain4j est la première librairie que j’ai utilisé. La lib est simple d’utilisation et s’inspire (sans être liée si ce n’est par le nom) des libs plus connues (et plus complètes) LangChain, Haystack, LlamaIndex.
La lib abstraie les ChatLanguageModel (l’accès à un service d’IA générative textuelle d’un fournisseur), les EmbeddingModel (l’accès à un modèle permettant la représentation sémantique sous forme vectorielle d’un texte/d’une image) les EmbeddingsStore (base de données vectorielles), la lecture de documents.
A cela s’ajoute différents éléments bien utile : des mémoires (permettent de maintenir une conversation comme le font les Threads dans l’API OpenAI), des QueryTransformer (très utiles pour le RAG -> par exemple on va réécrire le prompt de l’utilisateur à partir de la conversation pour récupérer le contexte des demandes précédentes de l’utilisateur et c’est ce prompt reformulé que l’on va utiliser pour la recherche de documentation).
On peut donc assez simplement combiner ces éléments pour l’ingestion de documents dans un EmbeddingStores ou, grâce aux AI Services, pour implémenter les cas d’usage les plus courants pour ces outils : RAG, Tools, extraction de données structurées, classification …
Mon avis actuel est que la librairie est très efficace et permet de réaliser rapidement des POCs. Cependant elle manque pour l’instant beaucoup de flexibilité et d’extensibilité pour répondre aux besoins d’une application complexe et complète. (il n’est par exemple pas possible de citer les références des documents utilisés lors de la génération d’une réponse ce qui est bien dommage puisque cela renforce considérablement le niveau de confiance pour l’utilisateur)
Un tuto Baeldung pour la route.
Depuis 2 ans je regarde souvent https://app.dvf.etalab.gouv.fr du coup je me suis dit que ça pourrait être cool de l’avoir en chat et du coup j’ai lancé un petit projet de test avec spring AI : chatDVF. Bon rien de fou, le projet est initialisé avec JHipster et c’est un work in progress mais j’ai appris quelques trucs au passage.
Donc dans SpringAI, comme pour LangChain4J on retrouve tout ce qui permettra de découpler notre application d’un fournisseur, que ce soit au niveau du modèle de langage ou du modèle d’embeddings ou au niveau des embeddingStores.
Par contre je n’ai pas trouvé d’équivalent au AI Services de LangChain4j ce qui est bien dommage puisque le niveau d’abstraction qu’ils fournissent permettent vraiment d’avoir un code concis et efficace.
Dans les faits voici les points sur lesquels j’ai souffert :
- le manque de ce que Langchain4J appelle ChatMemory : je me suis retrouvé à m’appuyer sur le front pour me renvoyer la conversation et avoir les messages précédents
- avec la conversation, le but est de ne pas envoyer juste le dernier message de l’utilisateur au LLM mais une compression des x derniers messages. Le but étant d’être capable de gérer une conversation sur laquelle l’tuilisateur ne répète pas dans chaque message la totalité de sa demande mais la complète au fur est à mesure des réponse qu’il reçoit pour préciser sa demande. A ce niveau Langchain fournit un QueryCompresser qu’on ajoute à la configuration de son agent. Dans le cas de Spring AI j’ai du implémenter un service réalisant cette opération (rien de bien compliqué mais c’est clairement du boilerplate qui sera recopié dans chaque projet…)
- la persistence de mes embeddings n’a pas été aisée non plus, je n’ai pas ajouté de metadata dans mes embeddings et ça causait des soucis (si vous testez le projet en local vous verrez que vous aurez des soucis à ce niveau avec le code en l’état)
- Je n’ai pas encore réussi a avoir la bonne configuration pour les appels de fonction local. Sur cet aspect je n’ai pas trop pris le temps, ça ne doit pas être grand chose et pour le coup l’approche en s’appuyant sur java.util.function.Function est plutôt intéressante.
Un tuto Baeldung pour la route.
Edit: après avoir du gérer un ticket sur langchain4j par rapport a une PR que j’avais envoyé j’ai compris un truc : en fait SpringAI évite soigneusement de proposer des prompts (et donc d’avoir a les maintenir et gérer l’instabilité, les compatibilités, …). En effet l’extraction de POJO depuis un texte libre sur langchain4j se fait très simplement en définissant une interface et il prend ensuite en charge le prompt pour l’extraction du POJO. Simple et efficace mais un changement du code générant ce prompt au niveau de la librairie peut bien sûr avoir des répercussions sur le fonctionnement de votre application. Je n’ai pas trouvé de doc permettant d’affirmer que c’est la politique de Spring AI de ne jamais descendre au niveau du prompt mais c’est ce qu’on constate. On a donc deux librairies avec des approches bien différentes.
Ma conclusion a l’instant T :
Pour l’instant la balance fonctionnalité / prise en main penche clairement en faveur de Langchain4J car plus complète et simple d’appréhension (le repo d’exemples est aussi un point positif) mais les deux librairies sont en développement actif (une dizaine de PR ouverte chacune pendant la dernière semaine) donc ça reste une affaire à suivre…
