diff --git a/libraries-llms/README.md b/libraries-llms/README.md
new file mode 100644
index 0000000000..2e250d42d6
--- /dev/null
+++ b/libraries-llms/README.md
@@ -0,0 +1,6 @@
+## Language Model Integration Libraries
+
+This module contains articles about libraries for language model integration in Java.
+
+### Relevant articles
+- [Introduction to LangChain](https://www.baeldung.com/langchain)
\ No newline at end of file
diff --git a/libraries-llms/pom.xml b/libraries-llms/pom.xml
new file mode 100644
index 0000000000..3d5ed6830e
--- /dev/null
+++ b/libraries-llms/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+ libraries-llms
+ libraries-llms
+
+
+ com.baeldung
+ parent-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+ dev.langchain4j
+ langchain4j
+ ${langchain4j.version}
+
+
+ dev.langchain4j
+ langchain4j-embeddings
+ ${langchain4j.version}
+
+
+ dev.langchain4j
+ langchain4j-open-ai
+ ${langchain4j.version}
+
+
+ dev.langchain4j
+ langchain4j-embeddings-all-minilm-l6-v2
+ ${langchain4j.version}
+
+
+
+
+ 0.23.0
+
+
+
\ No newline at end of file
diff --git a/libraries-llms/src/main/resources/logback.xml b/libraries-llms/src/main/resources/logback.xml
new file mode 100644
index 0000000000..23c5605a05
--- /dev/null
+++ b/libraries-llms/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -
+ %msg%n
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libraries-llms/src/test/java/com/baeldung/langchain/ChainWithDocumentLiveTests.java b/libraries-llms/src/test/java/com/baeldung/langchain/ChainWithDocumentLiveTests.java
new file mode 100644
index 0000000000..4ab5eaa68d
--- /dev/null
+++ b/libraries-llms/src/test/java/com/baeldung/langchain/ChainWithDocumentLiveTests.java
@@ -0,0 +1,98 @@
+package com.baeldung.langchain;
+
+import static dev.langchain4j.data.document.FileSystemDocumentLoader.loadDocument;
+import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
+import static java.time.Duration.ofSeconds;
+import static java.util.stream.Collectors.joining;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import dev.langchain4j.data.document.Document;
+import dev.langchain4j.data.document.DocumentSplitter;
+import dev.langchain4j.data.document.splitter.DocumentSplitters;
+import dev.langchain4j.data.embedding.Embedding;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.input.Prompt;
+import dev.langchain4j.model.input.PromptTemplate;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.openai.OpenAiTokenizer;
+import dev.langchain4j.store.embedding.EmbeddingMatch;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
+
+public class ChainWithDocumentLiveTests {
+
+ @Test
+ public void givenChainWithDocument_whenPrompted_thenValidResponse() {
+
+ Document document = loadDocument(toPath("src/test/resources/example-files/simpson's_adventures.txt"));
+ DocumentSplitter splitter = DocumentSplitters.recursive(100, 0, new OpenAiTokenizer(GPT_3_5_TURBO));
+ List segments = splitter.split(document);
+
+ EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();
+ List embeddings = embeddingModel.embedAll(segments)
+ .content();
+ EmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>();
+ embeddingStore.addAll(embeddings, segments);
+
+ String question = "Who is Simpson?";
+ Embedding questionEmbedding = embeddingModel.embed(question)
+ .content();
+ int maxResults = 3;
+ double minScore = 0.7;
+ List> relevantEmbeddings = embeddingStore.findRelevant(questionEmbedding, maxResults, minScore);
+
+ PromptTemplate promptTemplate = PromptTemplate.from("Answer the following question to the best of your ability:\n" + "\n" + "Question:\n" + "{{question}}\n" + "\n" + "Base your answer on the following information:\n" + "{{information}}");
+
+ String information = relevantEmbeddings.stream()
+ .map(match -> match.embedded()
+ .text())
+ .collect(joining("\n\n"));
+
+ Map variables = new HashMap<>();
+ variables.put("question", question);
+ variables.put("information", information);
+
+ Prompt prompt = promptTemplate.apply(variables);
+ ChatLanguageModel chatModel = OpenAiChatModel.builder()
+ .apiKey(Constants.OPEN_API_KEY)
+ .timeout(ofSeconds(60))
+ .build();
+ AiMessage aiMessage = chatModel.generate(prompt.toUserMessage())
+ .content();
+
+ Logger.getGlobal()
+ .info(aiMessage.text());
+ Assert.assertNotNull(aiMessage.text());
+
+ }
+
+ private static Path toPath(String fileName) {
+ try {
+ URL fileUrl = new File(fileName).toURI()
+ .toURL();
+ System.out.println(new File(fileName).toURI()
+ .toURL());
+ return Paths.get(fileUrl.toURI());
+ } catch (URISyntaxException | MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/libraries-llms/src/test/java/com/baeldung/langchain/ChatWithDocumentLiveTests.java b/libraries-llms/src/test/java/com/baeldung/langchain/ChatWithDocumentLiveTests.java
new file mode 100644
index 0000000000..e75465e5b7
--- /dev/null
+++ b/libraries-llms/src/test/java/com/baeldung/langchain/ChatWithDocumentLiveTests.java
@@ -0,0 +1,80 @@
+package com.baeldung.langchain;
+
+import static dev.langchain4j.data.document.FileSystemDocumentLoader.loadDocument;
+import static java.time.Duration.ofSeconds;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.logging.Logger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import dev.langchain4j.chain.ConversationalRetrievalChain;
+import dev.langchain4j.data.document.Document;
+import dev.langchain4j.data.document.splitter.DocumentSplitters;
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.retriever.EmbeddingStoreRetriever;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
+import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
+
+public class ChatWithDocumentLiveTests {
+
+ @Test
+ public void givenDocument_whenPrompted_thenValidResponse() {
+
+ EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();
+
+ EmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>();
+
+ EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
+ .documentSplitter(DocumentSplitters.recursive(500, 0))
+ .embeddingModel(embeddingModel)
+ .embeddingStore(embeddingStore)
+ .build();
+
+ Document document = loadDocument(toPath("src/test/resources/example-files/simpson's_adventures.txt"));
+ ingestor.ingest(document);
+
+ ChatLanguageModel chatModel = OpenAiChatModel.builder()
+ .apiKey(Constants.OPEN_API_KEY)
+ .timeout(ofSeconds(60))
+ .build();
+
+ ConversationalRetrievalChain chain = ConversationalRetrievalChain.builder()
+ .chatLanguageModel(chatModel)
+ .retriever(EmbeddingStoreRetriever.from(embeddingStore, embeddingModel))
+ // .chatMemory() // you can override default chat memory
+ // .promptTemplate() // you can override default prompt template
+ .build();
+
+ String answer = chain.execute("Who is Simpson?");
+
+ Logger.getGlobal()
+ .info(answer);
+ Assert.assertNotNull(answer);
+
+ }
+
+ private static Path toPath(String fileName) {
+ try {
+ URL fileUrl = new File(fileName).toURI()
+ .toURL();
+ System.out.println(new File(fileName).toURI()
+ .toURL());
+ return Paths.get(fileUrl.toURI());
+ } catch (URISyntaxException | MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/libraries-llms/src/test/java/com/baeldung/langchain/ChatWithMemoryLiveTests.java b/libraries-llms/src/test/java/com/baeldung/langchain/ChatWithMemoryLiveTests.java
new file mode 100644
index 0000000000..4395c52274
--- /dev/null
+++ b/libraries-llms/src/test/java/com/baeldung/langchain/ChatWithMemoryLiveTests.java
@@ -0,0 +1,45 @@
+package com.baeldung.langchain;
+
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.memory.ChatMemory;
+import dev.langchain4j.memory.chat.TokenWindowChatMemory;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.openai.OpenAiTokenizer;
+
+import static dev.langchain4j.data.message.UserMessage.userMessage;
+import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
+
+import java.util.logging.Logger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ChatWithMemoryLiveTests {
+
+ @Test
+ public void givenMemory_whenPrompted_thenValidResponse() {
+
+ ChatLanguageModel model = OpenAiChatModel.withApiKey(Constants.OPEN_API_KEY);
+ ChatMemory chatMemory = TokenWindowChatMemory.withMaxTokens(300, new OpenAiTokenizer(GPT_3_5_TURBO));
+
+ chatMemory.add(userMessage("Hello, my name is Kumar"));
+ AiMessage answer = model.generate(chatMemory.messages())
+ .content();
+ Logger.getGlobal()
+ .info(answer.text());
+ Assert.assertNotNull(answer.text());
+ chatMemory.add(answer);
+
+ chatMemory.add(userMessage("What is my name?"));
+ AiMessage answerWithName = model.generate(chatMemory.messages())
+ .content();
+ Logger.getGlobal()
+ .info(answerWithName.text());
+ Assert.assertTrue(answerWithName.text()
+ .contains("Kumar"));
+ chatMemory.add(answerWithName);
+
+ }
+
+}
diff --git a/libraries-llms/src/test/java/com/baeldung/langchain/Constants.java b/libraries-llms/src/test/java/com/baeldung/langchain/Constants.java
new file mode 100644
index 0000000000..15645ce68e
--- /dev/null
+++ b/libraries-llms/src/test/java/com/baeldung/langchain/Constants.java
@@ -0,0 +1,7 @@
+package com.baeldung.langchain;
+
+public class Constants {
+
+ public static String OPEN_API_KEY = "demo";
+
+}
diff --git a/libraries-llms/src/test/java/com/baeldung/langchain/PromptTemplatesLiveTests.java b/libraries-llms/src/test/java/com/baeldung/langchain/PromptTemplatesLiveTests.java
new file mode 100644
index 0000000000..c485b9cdf6
--- /dev/null
+++ b/libraries-llms/src/test/java/com/baeldung/langchain/PromptTemplatesLiveTests.java
@@ -0,0 +1,42 @@
+package com.baeldung.langchain;
+
+import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import org.junit.Test;
+
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.input.Prompt;
+import dev.langchain4j.model.input.PromptTemplate;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+
+import org.junit.Assert;
+
+public class PromptTemplatesLiveTests {
+
+ @Test
+ public void givenPromptTemplate_whenSuppliedInput_thenValidResponse() {
+
+ PromptTemplate promptTemplate = PromptTemplate.from("Tell me a {{adjective}} joke about {{content}}..");
+ Map variables = new HashMap<>();
+ variables.put("adjective", "funny");
+ variables.put("content", "humans");
+ Prompt prompt = promptTemplate.apply(variables);
+
+ ChatLanguageModel model = OpenAiChatModel.builder()
+ .apiKey(Constants.OPEN_API_KEY)
+ .modelName(GPT_3_5_TURBO)
+ .temperature(0.3)
+ .build();
+
+ String response = model.generate(prompt.text());
+ Logger.getGlobal()
+ .info(response);
+ Assert.assertNotNull(response);
+
+ }
+
+}
diff --git a/libraries-llms/src/test/java/com/baeldung/langchain/ServiceWithToolsLiveTests.java b/libraries-llms/src/test/java/com/baeldung/langchain/ServiceWithToolsLiveTests.java
new file mode 100644
index 0000000000..1e4b356334
--- /dev/null
+++ b/libraries-llms/src/test/java/com/baeldung/langchain/ServiceWithToolsLiveTests.java
@@ -0,0 +1,52 @@
+package com.baeldung.langchain;
+
+import java.util.logging.Logger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import dev.langchain4j.agent.tool.Tool;
+import dev.langchain4j.memory.chat.MessageWindowChatMemory;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.service.AiServices;
+
+public class ServiceWithToolsLiveTests {
+
+ static class Calculator {
+
+ @Tool("Calculates the length of a string")
+ int stringLength(String s) {
+ return s.length();
+ }
+
+ @Tool("Calculates the sum of two numbers")
+ int add(int a, int b) {
+ return a + b;
+ }
+
+ }
+
+ interface Assistant {
+
+ String chat(String userMessage);
+ }
+
+ @Test
+ public void givenServiceWithTools_whenPrompted_thenValidResponse() {
+
+ Assistant assistant = AiServices.builder(Assistant.class)
+ .chatLanguageModel(OpenAiChatModel.withApiKey(Constants.OPEN_API_KEY))
+ .tools(new Calculator())
+ .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
+ .build();
+
+ String question = "What is the sum of the numbers of letters in the words \"language\" and \"model\"?";
+ String answer = assistant.chat(question);
+
+ Logger.getGlobal()
+ .info(answer);
+ Assert.assertNotNull(answer);
+
+ }
+
+}
diff --git a/libraries-llms/src/test/resources/example-files/simpson's_adventures.txt b/libraries-llms/src/test/resources/example-files/simpson's_adventures.txt
new file mode 100644
index 0000000000..ae6de80be0
--- /dev/null
+++ b/libraries-llms/src/test/resources/example-files/simpson's_adventures.txt
@@ -0,0 +1,28 @@
+Once upon a time in the town of VeggieVille, there lived a cheerful carrot named Simpson.
+Simpson was a radiant carrot, always beaming with joy and positivity.
+His vibrant orange skin and lush green top were a sight to behold, but it was his infectious laughter and warm personality that really set him apart.
+
+Simpson had a diverse group of friends, each a vegetable with their own unique characteristics.
+There was Bella the blushing beetroot, always ready with a riddle or two; Timmy the timid tomato, a gentle soul with a heart of gold; and Percy the prankster potato, whose jokes always brought a smile to everyone's faces.
+Despite their differences, they shared a close bond, their friendship as robust as their natural goodness.
+
+Their lives were filled with delightful adventures, from playing hide-and-seek amidst the leafy lettuce to swimming in the dewy droplets that pooled on the cabbage leaves.
+Their favorite place, though, was the sunlit corner of the vegetable patch, where they would bask in the warmth of the sun, share stories, and have hearty laughs.
+
+One day, a bunch of pesky caterpillars invaded VeggieVille.
+The vegetables were terrified, fearing they would be nibbled to nothingness.
+But Simpson, with his usual sunny disposition, had an idea.
+He proposed they host a grand feast for the caterpillars, with the juiciest leaves from the outskirts of the town.
+Simpson's optimism was contagious, and his friends eagerly joined in to prepare the feast.
+
+When the caterpillars arrived, they were pleasantly surprised.
+They enjoyed the feast and were so impressed with the vegetables' hospitality that they promised not to trouble VeggieVille again.
+In return, they agreed to help pollinate the flowers, contributing to a more lush and vibrant VeggieVille.
+
+Simpson's idea had saved the day, but he humbly attributed the success to their teamwork and friendship.
+They celebrated their victory with a grand party, filled with laughter, dance, and merry games.
+That night, under the twinkling stars, they made a pact to always stand by each other, come what may.
+
+From then on, the story of the happy carrot and his friends spread far and wide, a tale of friendship, unity, and positivity.
+Simpson, Bella, Timmy, and Percy continued to live their joyful lives, their laughter echoing through VeggieVille.
+And so, the tale of the happy carrot and his friends serves as a reminder that no matter the challenge, with optimism, teamwork, and a bit of creativity, anything is possible.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3f4da4bdb0..a391a1c774 100644
--- a/pom.xml
+++ b/pom.xml
@@ -936,6 +936,7 @@
spring-di-4
spring-kafka-2
+ libraries-llms
@@ -1223,6 +1224,7 @@
spring-di-4
spring-kafka-2
+ libraries-llms