Articles de blog

Présentation de LangChain4j pour simplifier l'intégration de LLM dans les applications Java

LangChain4j (LangChain pour Java) est un ensemble d'outils puissants pour construire votre application RAG en Java simple.

Le cadre LangChain4j a été créé en 2023 avec cet objectif:

L'objectif de LangChain4j est de simplifier l'intégration des LLM dans les applications Java.

LangChain4j fournit une méthode standard pour :

  • créer des embeddings (vecteurs) à partir d'un contenu donné, par exemple un texte

  • stocker les embeddings dans un magasin d'embeddings

  • recherche de vecteurs similaires dans le magasin d'intégration

  • discuter avec les LLM

  • utiliser une mémoire de chat pour se souvenir du contexte d'une discussion avec un LLM

Cette liste n'est pas exhaustive et la communauté LangChain4j est toujours en train d'implémenter de nouvelles fonctionnalités.

Ce billet couvrira les premières parties principales du cadre.

Ajouter LangChain4j OpenAI à notre projet

Comme dans tous les projets Java, c'est juste une question de dépendances. Nous utiliserons ici Maven, mais la même chose pourrait être réalisée avec n'importe quel autre gestionnaire de dépendances.

Comme première étape du projet que nous voulons construire ici, nous utiliserons OpenAI, il nous suffit donc d'ajouter l'artefact langchain4j-open-ai:

<properties>
  <langchain4j.version>0.34.0</langchain4j.version>
</properties>

<dependencies>
  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
    <version>${langchain4j.version}</version>
  </dependency>
</dependencies>

Pour le reste du code, nous utiliserons soit notre propre clé API, que vous pouvez obtenir en créant un compte auprès d'OpenAI, soit celle fournie par le projet LangChain4j à des fins de démonstration uniquement :

static String getOpenAiApiKey() {
  String apiKey = System.getenv(API_KEY_ENV_NAME);
  if (apiKey == null || apiKey.isEmpty()) {
    Logger.warn("Please provide your own key instead using [{}] env variable", API_KEY_ENV_NAME);
    return "demo";
  }
  return apiKey;
}

Nous pouvons maintenant créer une instance de notre ChatLanguageModel :

ChatLanguageModel model = OpenAiChatModel.withApiKey(getOpenAiApiKey());

Enfin, nous pouvons poser une question simple et obtenir une réponse :

String answer = model.generate("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);

La réponse donnée pourrait être quelque chose comme :

Thomas Pesquet is a French aerospace engineer, pilot, and European Space Agency astronaut.
He was selected as a member of the European Astronaut Corps in 2009 and has since completed 
two space missions to the International Space Station, including serving as a flight engineer 
for Expedition 50/51 in 2016-2017. Pesquet is known for his contributions to scientific 
research and outreach activities during his time in space.

Si vous souhaitez exécuter ce code, consultez la classe Step1AiChatTest.java.

Fournir plus de contexte avec langchain4j

Ajoutons l'artefact langchain4j:

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j</artifactId>
  <version>${langchain4j.version}</version>
</dependency>

Celui-ci fournit un ensemble d'outils qui peuvent nous aider à construire une intégration LLM plus avancée pour construire notre assistant. Ici, nous allons simplement créer une interface Assistant qui fournit la méthode chat qui appellera automatiquement la méthode ChatLanguageModel que nous avons définie plus tôt :

interface Assistant {
  String chat(String userMessage);
}

Il suffit de demander à la classe LangChain4j AiServices de construire une instance pour nous :

Assistant assistant = AiServices.create(Assistant.class, model);

Puis appeler la méthode chat(String):

String answer = assistant.chat("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);

Le comportement est le même qu'auparavant. Pourquoi avons-nous donc modifié le code ? Tout d'abord, c'est plus élégant, mais en plus, vous pouvez maintenant donner des instructions au LLM à l'aide de simples annotations :

interface Assistant {
  @SystemMessage("Please answer in a funny way.")
  String chat(String userMessage);
}

C'est maintenant chose faite :

Ah, Thomas Pesquet is actually a super secret spy disguised as an astronaut! 
He's out there in space fighting aliens and saving the world one spacewalk at a time. 
Or maybe he's just a really cool French astronaut who has been to the International 
Space Station. But my spy theory is much more exciting, don't you think?

Si vous souhaitez exécuter ce code, consultez la classe Step2AssistantTest.java.

Passer à un autre LLM : langchain4j-ollama

Nous pouvons utiliser le grand projet Ollama. Il est utile d'exécuter un LLM localement sur votre machine.

Ajoutons l'artefact langchain4j-ollama:

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-ollama</artifactId>
  <version>${langchain4j.version}</version>
</dependency>

Comme nous exécutons l'exemple de code à l'aide de tests, ajoutons des conteneurs de test à notre projet :

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>ollama</artifactId>
  <version>1.20.1</version>
  <scope>test</scope>
</dependency>

Nous pouvons maintenant démarrer/arrêter les conteneurs Docker :

static String MODEL_NAME = "mistral";
static String DOCKER_IMAGE_NAME = "langchain4j/ollama-" + MODEL_NAME + ":latest";

static OllamaContainer ollama = new OllamaContainer(
  DockerImageName.parse(DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("ollama/ollama"));

@BeforeAll
public static void setup() {
  ollama.start();
}

@AfterAll
public static void teardown() {
  ollama.stop();
}

Nous "devons simplement" modifier l'objet model pour qu'il devienne un OllamaChatModel au lieu du OpenAiChatModel que nous utilisions précédemment :

OllamaChatModel model = OllamaChatModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();

Notez que l'extraction de l'image avec son modèle peut prendre un certain temps, mais au bout d'un moment, vous pouvez obtenir la réponse :

Oh, Thomas Pesquet, the man who single-handedly keeps the French space program running 
while sipping on his crisp rosé and munching on a baguette! He's our beloved astronaut 
with an irresistible accent that makes us all want to learn French just so we can 
understand him better. When he's not floating in space, he's probably practicing his 
best "je ne sais quoi" face for the next family photo. Vive le Thomas Pesquet! 
🚀🌍🇫🇷 #FrenchSpaceHero

Meilleure mémoire

Si nous posons plusieurs questions, par défaut le système ne se souviendra pas des questions et réponses précédentes. Ainsi, si nous posons la question suivante : "Quand est-il né ?", notre application répondra :

Oh, you're asking about this legendary figure from history, huh? Well, let me tell 
you a hilarious tale! He was actually born on Leap Year's Day, but only every 400 
years! So, do the math... if we count backwards from 2020 (which is also a leap year), 
then he was born in... *drumroll please* ...1600! Isn't that a hoot? But remember 
folks, this is just a joke, and historical records may vary.

Ce qui est absurde. Nous devrions plutôt utiliser la mémoire de chat :

ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
  .chatLanguageModel(model)
  .chatMemory(chatMemory)
  .build();

En posant les mêmes questions maintenant, on obtient une réponse significative :

Oh, Thomas Pesquet, the man who was probably born before sliced bread but after dinosaurs! 
You know, around the time when people started putting wheels on suitcases and calling it 
a revolution. So, roughly speaking, he came into this world somewhere in the late 70s or 
early 80s, give or take a year or two - just enough time for him to grow up, become an 
astronaut, and make us all laugh with his space-aged antics! Isn't that a hoot? 
*laughs maniacally*

Conclusion

Dans le prochain article, nous découvrirons comment poser des questions à notre ensemble de données privé en utilisant Elasticsearch comme magasin d'intégration. Cela nous permettra de passer à la vitesse supérieure en matière de recherche de candidatures.

Pour aller plus loin

Tester votre code Java avec des mocks et du vrai Elasticsearch

Piotr Przybyl

LangChain4j avec Elasticsearch comme magasin de plongements

David Pilato

Prêt à créer des expériences de recherche d'exception ?

Une recherche suffisamment avancée ne se fait pas avec les efforts d'une seule personne. Elasticsearch est alimenté par des data scientists, des ML ops, des ingénieurs et bien d'autres qui sont tout aussi passionnés par la recherche que vous. Mettons-nous en relation et travaillons ensemble pour construire l'expérience de recherche magique qui vous permettra d'obtenir les résultats que vous souhaitez.

Jugez-en par vous-même