diff --git a/astradb-examples/pom.xml b/astradb-examples/pom.xml new file mode 100644 index 00000000..aeec3383 --- /dev/null +++ b/astradb-examples/pom.xml @@ -0,0 +1,63 @@ + + 4.0.0 + + dev.langchain4j + langchain4j-examples + 0.30.0 + + + astradb-examples + jar + astradb-examples + + + 11 + 11 + UTF-8 + 0.29.1 + + + + + + dev.langchain4j + langchain4j + ${langchain.version} + test + + + dev.langchain4j + langchain4j-core + ${langchain.version} + test + + + dev.langchain4j + langchain4j-astradb + ${langchain.version} + test + + + + ch.qos.logback + logback-classic + 1.5.3 + test + + + + dev.langchain4j + langchain4j-open-ai + ${langchain.version} + test + + + org.junit.jupiter + junit-jupiter-engine + 5.10.2 + test + + + + diff --git a/astradb-examples/src/test/java/AstraDbAssistant.java b/astradb-examples/src/test/java/AstraDbAssistant.java new file mode 100644 index 00000000..345c330a --- /dev/null +++ b/astradb-examples/src/test/java/AstraDbAssistant.java @@ -0,0 +1,3 @@ +interface AstraDbAssistant { + String answer(String query); +} diff --git a/astradb-examples/src/test/java/AstraDbNaiveRagTestIT.java b/astradb-examples/src/test/java/AstraDbNaiveRagTestIT.java new file mode 100644 index 00000000..d34a3f82 --- /dev/null +++ b/astradb-examples/src/test/java/AstraDbNaiveRagTestIT.java @@ -0,0 +1,113 @@ +import com.datastax.astra.client.DataAPIClient; +import dev.langchain4j.data.document.parser.TextDocumentParser; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiEmbeddingModel; +import dev.langchain4j.model.openai.OpenAiTokenizer; +import dev.langchain4j.rag.content.retriever.ContentRetriever; +import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; +import dev.langchain4j.service.AiServices; +import dev.langchain4j.store.embedding.EmbeddingStore; +import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; +import dev.langchain4j.store.embedding.astradb.AstraDbEmbeddingStore; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; + +import static com.datastax.astra.client.model.SimilarityMetric.COSINE; +import static com.dtsx.astra.sdk.utils.TestUtils.getAstraToken; +import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument; +import static dev.langchain4j.data.document.splitter.DocumentSplitters.recursive; +import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_3_5_TURBO; +import static dev.langchain4j.model.openai.OpenAiEmbeddingModelName.TEXT_EMBEDDING_ADA_002; +import static java.time.Duration.ofSeconds; + +@Disabled("AstraDB is not available in the CI") +class AstraDbNaiveRagTestIT { + + static final String VAR_OPENAI_API_KEY = "OPENAI_API_KEY"; + static final String VAR_ASTRA_TOKEN = "ASTRA_DB_APPLICATION_TOKEN"; + + @Test + @EnabledIfEnvironmentVariable(named = VAR_ASTRA_TOKEN, matches = "Astra.*") + @EnabledIfEnvironmentVariable(named = VAR_OPENAI_API_KEY, matches = "sk.*") + void shouldNaiveRagWithOpenAiAndAstraDbTest() { + + // Parsing input file + Path textFile = new File(Objects.requireNonNull(getClass() + .getResource("/story-about-happy-carrot.txt")) + .getFile()) + .toPath(); + + // === INGESTION === + + EmbeddingModel embeddingModel = initEmbeddingModelOpenAi(); + EmbeddingStore embeddingStore = initEmbeddingStoreAstraDb(); + EmbeddingStoreIngestor.builder() + .documentSplitter(recursive(100, 10, new OpenAiTokenizer(GPT_3_5_TURBO))) + .embeddingModel(embeddingModel) + .embeddingStore(embeddingStore) + .build() + .ingest(loadDocument(textFile, new TextDocumentParser())); + + // === NAIVE RETRIEVER === + + ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder() + .embeddingStore(embeddingStore) + .embeddingModel(embeddingModel) + .maxResults(2) + .minScore(0.5) + .build(); + + AstraDbAssistant ai = AiServices.builder(AstraDbAssistant.class) + .contentRetriever(contentRetriever) + .chatLanguageModel(initChatLanguageModelOpenAi()) + .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) + .build(); + + String response = ai.answer("What vegetable is Happy?"); + Assertions.assertNotNull(response); + } + + private ChatLanguageModel initChatLanguageModelOpenAi() { + return OpenAiChatModel.builder() + .apiKey(System.getenv(VAR_OPENAI_API_KEY)) + .modelName(GPT_3_5_TURBO) + .temperature(0.7) + .timeout(ofSeconds(15)) + .maxRetries(3) + .logResponses(true) + .logRequests(true) + .build(); + } + + private EmbeddingModel initEmbeddingModelOpenAi() { + return OpenAiEmbeddingModel.builder() + .apiKey(System.getenv(VAR_OPENAI_API_KEY)) + .modelName(TEXT_EMBEDDING_ADA_002) + .build(); + } + + private EmbeddingStore initEmbeddingStoreAstraDb() { + return new AstraDbEmbeddingStore( + // Astra Db Client + new DataAPIClient(getAstraToken()) + // Access the 'admin' part + .getAdmin() + // To create a database if it does not exist + .createDatabase("test_langchain4j") + // Select the created db + .getDatabase() + // And create a collection if it does not exist + .createCollection("story_collection", 1536, COSINE)); + } + +} \ No newline at end of file diff --git a/astradb-examples/src/test/resources/logback-test.xml b/astradb-examples/src/test/resources/logback-test.xml new file mode 100644 index 00000000..b176835f --- /dev/null +++ b/astradb-examples/src/test/resources/logback-test.xml @@ -0,0 +1,17 @@ + + + + + %d{HH:mm:ss.SSS} %magenta(%-5level) %cyan(%-20logger) : %msg%n + + + + + + + + + + + + \ No newline at end of file diff --git a/astradb-examples/src/test/resources/story-about-happy-carrot.txt b/astradb-examples/src/test/resources/story-about-happy-carrot.txt new file mode 100644 index 00000000..66ae976d --- /dev/null +++ b/astradb-examples/src/test/resources/story-about-happy-carrot.txt @@ -0,0 +1,28 @@ +Once upon a time in the town of VeggieVille, there lived a cheerful carrot named Charlie. +Charlie 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. + +Charlie 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 Charlie, 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. +Charlie'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. + +Charlie'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. +Charlie, 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/cassandra-examples/pom.xml b/cassandra-examples/pom.xml new file mode 100644 index 00000000..b2573265 --- /dev/null +++ b/cassandra-examples/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + cassandra-example + + + dev.langchain4j + langchain4j-examples + 0.30.0 + + + + + 11 + 11 + UTF-8 + 0.29.1 + 1.19.7 + + + + + + dev.langchain4j + langchain4j-cassandra + ${langchain.version} + + + + dev.langchain4j + langchain4j + ${langchain.version} + test + + + + dev.langchain4j + langchain4j-core + ${langchain.version} + test + + + + ch.qos.logback + logback-classic + 1.5.3 + test + + + + dev.langchain4j + langchain4j-open-ai + ${langchain.version} + test + + + org.junit.jupiter + junit-jupiter-engine + 5.10.2 + test + + + + org.testcontainers + cassandra + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + + + \ No newline at end of file diff --git a/cassandra-examples/src/test/java/CassandraAssistant.java b/cassandra-examples/src/test/java/CassandraAssistant.java new file mode 100644 index 00000000..b6a7185a --- /dev/null +++ b/cassandra-examples/src/test/java/CassandraAssistant.java @@ -0,0 +1,3 @@ +interface CassandraAssistant { + String answer(String query); +} diff --git a/cassandra-examples/src/test/java/CassandraNaiveRagTestIT.java b/cassandra-examples/src/test/java/CassandraNaiveRagTestIT.java new file mode 100644 index 00000000..280318a8 --- /dev/null +++ b/cassandra-examples/src/test/java/CassandraNaiveRagTestIT.java @@ -0,0 +1,154 @@ + +import com.datastax.oss.driver.api.core.CqlSession; +import dev.langchain4j.data.document.parser.TextDocumentParser; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiEmbeddingModel; +import dev.langchain4j.model.openai.OpenAiTokenizer; +import dev.langchain4j.rag.content.retriever.ContentRetriever; +import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; +import dev.langchain4j.service.AiServices; +import dev.langchain4j.store.cassio.SimilarityMetric; +import dev.langchain4j.store.embedding.EmbeddingStore; +import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; +import dev.langchain4j.store.embedding.cassandra.CassandraCassioEmbeddingStore; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.CassandraContainer; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.io.File; +import java.net.InetSocketAddress; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Objects; + +import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument; +import static dev.langchain4j.data.document.splitter.DocumentSplitters.recursive; +import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_3_5_TURBO; +import static dev.langchain4j.model.openai.OpenAiEmbeddingModelName.TEXT_EMBEDDING_ADA_002; +import static java.time.Duration.ofSeconds; + +@Testcontainers +class CassandraNaiveRagTestIT { + + static final String VAR_OPENAI_API_KEY = "OPENAI_API_KEY"; + + static final String CASSANDRA_IMAGE = "cassandra:5.0"; + static final String DATACENTER = "datacenter1"; + static final String CLUSTER = "langchain4j"; + static final String VECTOR_STORE = "test_langchain4j"; + + static CassandraContainer cassandraContainer; + + /** + * Check Docker is installed and running on host + */ + @BeforeAll + static void ensureDockerIsRunning() { + DockerClientFactory.instance().client(); + if (cassandraContainer == null) { + cassandraContainer = new CassandraContainer<>( + DockerImageName.parse(CASSANDRA_IMAGE)) + .withEnv("CLUSTER_NAME", CLUSTER) + .withEnv("DC", DATACENTER); + cassandraContainer.start(); + + // Part of Database Creation, creating keyspace + final InetSocketAddress contactPoint = cassandraContainer.getContactPoint(); + CqlSession.builder() + .addContactPoint(contactPoint) + .withLocalDatacenter(DATACENTER) + .build().execute( + "CREATE KEYSPACE IF NOT EXISTS " + CLUSTER + + " WITH replication = {'class':'SimpleStrategy', 'replication_factor':'1'};"); + } + } + + /** + * Stop Cassandra Node + */ + @AfterAll + static void afterTests() throws Exception { + cassandraContainer.stop(); + } + + @Test + @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = "sk.*") + void shouldRagWithOpenAiAndAstra() { + // Parsing input file + Path textFile = new File(Objects.requireNonNull(getClass() + .getResource("/story-about-happy-carrot.txt")) + .getFile()) + .toPath(); + + // === INGESTION === + + EmbeddingModel embeddingModel = initEmbeddingModelOpenAi(); + EmbeddingStore embeddingStore = initEmbeddingStoreCassandra(); + EmbeddingStoreIngestor.builder() + .documentSplitter(recursive(100, 10, new OpenAiTokenizer(GPT_3_5_TURBO))) + .embeddingModel(embeddingModel) + .embeddingStore(embeddingStore) + .build() + .ingest(loadDocument(textFile, new TextDocumentParser())); + + // === NAIVE RETRIEVER === + + ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder() + .embeddingStore(embeddingStore) + .embeddingModel(embeddingModel) + .maxResults(2) + .minScore(0.5) + .build(); + + CassandraAssistant ai = AiServices.builder(CassandraAssistant.class) + .contentRetriever(contentRetriever) + .chatLanguageModel(initChatLanguageModelOpenAi()) + .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) + .build(); + + String response = ai.answer("What vegetable is Happy?"); + Assertions.assertNotNull(response); + + } + + private EmbeddingStore initEmbeddingStoreCassandra() { + return CassandraCassioEmbeddingStore.builder() + .contactPoints(Collections.singletonList(cassandraContainer.getContactPoint().getHostName())) + .port(cassandraContainer.getContactPoint().getPort()) + .localDataCenter(DATACENTER) + .keyspace(CLUSTER) + .table(VECTOR_STORE) + .dimension(1536) + .metric(SimilarityMetric.COSINE) + .build(); + } + + private ChatLanguageModel initChatLanguageModelOpenAi() { + return OpenAiChatModel.builder() + .apiKey(System.getenv(VAR_OPENAI_API_KEY)) + .modelName(GPT_3_5_TURBO) + .temperature(0.7) + .timeout(ofSeconds(15)) + .maxRetries(3) + .logResponses(true) + .logRequests(true) + .build(); + } + + private EmbeddingModel initEmbeddingModelOpenAi() { + return OpenAiEmbeddingModel.builder() + .apiKey(System.getenv(VAR_OPENAI_API_KEY)) + .modelName(TEXT_EMBEDDING_ADA_002) + .build(); + } +} \ No newline at end of file diff --git a/cassandra-examples/src/test/resources/logback-test.xml b/cassandra-examples/src/test/resources/logback-test.xml new file mode 100644 index 00000000..73314a62 --- /dev/null +++ b/cassandra-examples/src/test/resources/logback-test.xml @@ -0,0 +1,32 @@ + + + + + %d{HH:mm:ss.SSS} %magenta(%-5level) %cyan(%-47logger) : %msg%n + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cassandra-examples/src/test/resources/story-about-happy-carrot.txt b/cassandra-examples/src/test/resources/story-about-happy-carrot.txt new file mode 100644 index 00000000..66ae976d --- /dev/null +++ b/cassandra-examples/src/test/resources/story-about-happy-carrot.txt @@ -0,0 +1,28 @@ +Once upon a time in the town of VeggieVille, there lived a cheerful carrot named Charlie. +Charlie 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. + +Charlie 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 Charlie, 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. +Charlie'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. + +Charlie'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. +Charlie, 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 869f13a4..c3af367a 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,8 @@ weaviate-example javafx-example quarkus-example + astradb-examples + cassandra-examples \ No newline at end of file