From 7392b003f1cee2c312f29a17d1f46cb86aa0bcd9 Mon Sep 17 00:00:00 2001 From: CHANDRAKANT Kumar Date: Mon, 23 Oct 2023 14:33:05 +0530 Subject: [PATCH] Adding sourcecode for reference in the article tracked under BAEL-6970. --- libraries-llms/README.md | 6 ++ libraries-llms/pom.xml | 43 ++++++++ libraries-llms/src/main/resources/logback.xml | 17 ++++ .../langchain/ChainWithDocumentLiveTests.java | 98 +++++++++++++++++++ .../langchain/ChatWithDocumentLiveTests.java | 80 +++++++++++++++ .../langchain/ChatWithMemoryLiveTests.java | 45 +++++++++ .../com/baeldung/langchain/Constants.java | 7 ++ .../langchain/PromptTemplatesLiveTests.java | 42 ++++++++ .../langchain/ServiceWithToolsLiveTests.java | 52 ++++++++++ .../example-files/simpson's_adventures.txt | 28 ++++++ pom.xml | 2 + 11 files changed, 420 insertions(+) create mode 100644 libraries-llms/README.md create mode 100644 libraries-llms/pom.xml create mode 100644 libraries-llms/src/main/resources/logback.xml create mode 100644 libraries-llms/src/test/java/com/baeldung/langchain/ChainWithDocumentLiveTests.java create mode 100644 libraries-llms/src/test/java/com/baeldung/langchain/ChatWithDocumentLiveTests.java create mode 100644 libraries-llms/src/test/java/com/baeldung/langchain/ChatWithMemoryLiveTests.java create mode 100644 libraries-llms/src/test/java/com/baeldung/langchain/Constants.java create mode 100644 libraries-llms/src/test/java/com/baeldung/langchain/PromptTemplatesLiveTests.java create mode 100644 libraries-llms/src/test/java/com/baeldung/langchain/ServiceWithToolsLiveTests.java create mode 100644 libraries-llms/src/test/resources/example-files/simpson's_adventures.txt 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