From 85c3aa7af0b912afbf40096dc5ee4fc2453de4ed Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Mon, 12 Feb 2024 17:32:55 +0000 Subject: [PATCH] feat : adds Test cases and CI --- .../llm-rag-with-langchain4j-spring-boot.yml | 39 ++++ ai-opensearch-langchain4j/pom.xml | 2 +- .../docker-compose.yml | 2 - llm-rag-with-langchain4j-spring-boot/pom.xml | 39 ++++ .../com/learning/ai/LLMRagWithSpringBoot.java | 50 ++--- .../java/com/learning/ai/config/AIConfig.java | 211 ++++++++++-------- .../com/learning/ai/config/ChatTools.java | 13 +- .../ai/config/CustomerSupportAgent.java | 50 ++--- .../learning/ai/LLMRagWithSpringBootTest.java | 23 +- .../learning/ai/TestLLMRagWithSpringBoot.java | 17 ++ .../learning/ai/config/ContainersConfig.java | 12 + 11 files changed, 287 insertions(+), 171 deletions(-) create mode 100644 .github/workflows/llm-rag-with-langchain4j-spring-boot.yml create mode 100644 llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/TestLLMRagWithSpringBoot.java create mode 100644 llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/config/ContainersConfig.java diff --git a/.github/workflows/llm-rag-with-langchain4j-spring-boot.yml b/.github/workflows/llm-rag-with-langchain4j-spring-boot.yml new file mode 100644 index 0000000..1e2b6fb --- /dev/null +++ b/.github/workflows/llm-rag-with-langchain4j-spring-boot.yml @@ -0,0 +1,39 @@ +name: llm-rag-with-langchain4j-spring-boot CI Build + +on: + push: + paths: + - "llm-rag-with-langchain4j-spring-boot/**" + branches: [main] + pull_request: + paths: + - "llm-rag-with-langchain4j-spring-boot/**" + types: + - opened + - synchronize + - reopened + +jobs: + build: + name: Run Unit & Integration Tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: llm-rag-with-langchain4j-spring-boot + strategy: + matrix: + distribution: [ 'temurin' ] + java: [ '21' ] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v4.0.0 + with: + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' + - name: Build and analyze + run: ./mvnw clean verify \ No newline at end of file diff --git a/ai-opensearch-langchain4j/pom.xml b/ai-opensearch-langchain4j/pom.xml index 2cf0ba4..68a9b0a 100644 --- a/ai-opensearch-langchain4j/pom.xml +++ b/ai-opensearch-langchain4j/pom.xml @@ -11,7 +11,7 @@ - org.example + org.example.ai ai-opensearch-langchain4j 1.0.0-SNAPSHOT diff --git a/llm-rag-with-langchain4j-spring-boot/docker-compose.yml b/llm-rag-with-langchain4j-spring-boot/docker-compose.yml index 9ac22b4..421692a 100644 --- a/llm-rag-with-langchain4j-spring-boot/docker-compose.yml +++ b/llm-rag-with-langchain4j-spring-boot/docker-compose.yml @@ -25,7 +25,5 @@ services: environment: PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} - volumes: - - ./servers.json:/pgadmin4/servers.json ports: - "${PGADMIN_PORT:-5050}:80" \ No newline at end of file diff --git a/llm-rag-with-langchain4j-spring-boot/pom.xml b/llm-rag-with-langchain4j-spring-boot/pom.xml index c58f939..0511a18 100644 --- a/llm-rag-with-langchain4j-spring-boot/pom.xml +++ b/llm-rag-with-langchain4j-spring-boot/pom.xml @@ -16,6 +16,7 @@ 17 + 2.43.0 @@ -74,6 +75,21 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-testcontainers + test + + + org.testcontainers + postgresql + test + + + org.testcontainers + junit-jupiter + test + @@ -86,6 +102,29 @@ org.springframework.boot spring-boot-maven-plugin + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + 2.39.0 + + + + + + + + + compile + + check + + + + diff --git a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/LLMRagWithSpringBoot.java b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/LLMRagWithSpringBoot.java index c84332c..b3ca12d 100644 --- a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/LLMRagWithSpringBoot.java +++ b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/LLMRagWithSpringBoot.java @@ -1,25 +1,25 @@ -package com.learning.ai; - -import com.learning.ai.config.CustomerSupportAgent; -import org.springframework.boot.ApplicationRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; - -import java.util.Map; - -@SpringBootApplication -public class LLMRagWithSpringBoot { - - public static void main(String[] args) { - SpringApplication.run(LLMRagWithSpringBoot.class, args); - } - - @Bean - ApplicationRunner interactiveChatRunner(CustomerSupportAgent agent) { - return args -> { - var response = agent.chat("what should I know about the transition to consumer direct care network washington?"); - System.out.println(Map.of("response", response)); - }; - } -} +package com.learning.ai; + +import com.learning.ai.config.CustomerSupportAgent; +import java.util.Map; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class LLMRagWithSpringBoot { + + public static void main(String[] args) { + SpringApplication.run(LLMRagWithSpringBoot.class, args); + } + + @Bean + ApplicationRunner interactiveChatRunner(CustomerSupportAgent agent) { + return args -> { + var response = + agent.chat("what should I know about the transition to consumer direct care network washington?"); + System.out.println(Map.of("response", response)); + }; + } +} diff --git a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/AIConfig.java b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/AIConfig.java index 72ed1ee..b544d58 100644 --- a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/AIConfig.java +++ b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/AIConfig.java @@ -1,99 +1,112 @@ -package com.learning.ai.config; - -import com.zaxxer.hikari.HikariDataSource; -import dev.langchain4j.data.document.Document; -import dev.langchain4j.data.document.DocumentSplitter; -import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser; -import dev.langchain4j.data.document.splitter.DocumentSplitters; -import dev.langchain4j.data.segment.TextSegment; -import dev.langchain4j.memory.chat.MessageWindowChatMemory; -import dev.langchain4j.model.chat.ChatLanguageModel; -import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel; -import dev.langchain4j.model.embedding.EmbeddingModel; -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.pgvector.PgVectorEmbeddingStore; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; - -import javax.sql.DataSource; -import java.io.IOException; - -import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument; -import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO; - -@Configuration(proxyBeanMethods = false) -public class AIConfig { - - @Bean - CustomerSupportAgent customerSupportAgent(ChatLanguageModel chatLanguageModel, -// ChatTools bookingTools, - ContentRetriever contentRetriever) { - return AiServices.builder(CustomerSupportAgent.class) - .chatLanguageModel(chatLanguageModel) - .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) -// .tools(bookingTools) - .contentRetriever(contentRetriever) - .build(); - } - - @Bean - ContentRetriever contentRetriever(EmbeddingStore embeddingStore, EmbeddingModel embeddingModel) { - - // You will need to adjust these parameters to find the optimal setting, which will depend on two main factors: - // - The nature of your data - // - The embedding model you are using - int maxResults = 1; - double minScore = 0.6; - - return EmbeddingStoreContentRetriever.builder() - .embeddingStore(embeddingStore) - .embeddingModel(embeddingModel) - .maxResults(maxResults) - .minScore(minScore) - .build(); - } - - @Bean - EmbeddingModel embeddingModel() { - return new AllMiniLmL6V2EmbeddingModel(); - } - - @Bean - EmbeddingStore embeddingStore(EmbeddingModel embeddingModel, ResourceLoader resourceLoader, DataSource dataSource) throws IOException { - - // Normally, you would already have your embedding store filled with your data. - // However, for the purpose of this demonstration, we will: - - HikariDataSource hikariDataSource = (HikariDataSource) dataSource; - // 1. Create an postgres embedding store - // dimension of the embedding is 384 (all-minilm) and 1536 (openai) - EmbeddingStore embeddingStore = PgVectorEmbeddingStore.builder().host("localhost").port(5432) - .user(hikariDataSource.getUsername()).password(hikariDataSource.getPassword()).database("vector_store") - .table("ai_vector_store").dimension(384).build(); - - // 2. Load an example document (medicaid-wa-faqs.pdf) - Resource pdfResource = resourceLoader.getResource("classpath:medicaid-wa-faqs.pdf"); - Document document = loadDocument(pdfResource.getFile().toPath(), new ApachePdfBoxDocumentParser()); - - // 3. Split the document into segments 500 tokens each - // 4. Convert segments into embeddings - // 5. Store embeddings into embedding store - // All this can be done manually, but we will use EmbeddingStoreIngestor to automate this: - DocumentSplitter documentSplitter = DocumentSplitters.recursive(500, 0, new OpenAiTokenizer(GPT_3_5_TURBO)); - EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() - .documentSplitter(documentSplitter) - .embeddingModel(embeddingModel) - .embeddingStore(embeddingStore) - .build(); - ingestor.ingest(document); - - return embeddingStore; - } -} +package com.learning.ai.config; + +import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument; +import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO; + +import com.zaxxer.hikari.HikariDataSource; +import dev.langchain4j.data.document.Document; +import dev.langchain4j.data.document.DocumentSplitter; +import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser; +import dev.langchain4j.data.document.splitter.DocumentSplitters; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel; +import dev.langchain4j.model.embedding.EmbeddingModel; +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.pgvector.PgVectorEmbeddingStore; +import java.io.IOException; +import java.net.URI; +import javax.sql.DataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +@Configuration(proxyBeanMethods = false) +public class AIConfig { + + @Bean + CustomerSupportAgent customerSupportAgent( + ChatLanguageModel chatLanguageModel, + // ChatTools bookingTools, + ContentRetriever contentRetriever) { + return AiServices.builder(CustomerSupportAgent.class) + .chatLanguageModel(chatLanguageModel) + .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) + // .tools(bookingTools) + .contentRetriever(contentRetriever) + .build(); + } + + @Bean + ContentRetriever contentRetriever(EmbeddingStore embeddingStore, EmbeddingModel embeddingModel) { + + // You will need to adjust these parameters to find the optimal setting, which will depend on two main factors: + // - The nature of your data + // - The embedding model you are using + int maxResults = 1; + double minScore = 0.6; + + return EmbeddingStoreContentRetriever.builder() + .embeddingStore(embeddingStore) + .embeddingModel(embeddingModel) + .maxResults(maxResults) + .minScore(minScore) + .build(); + } + + @Bean + EmbeddingModel embeddingModel() { + return new AllMiniLmL6V2EmbeddingModel(); + } + + @Bean + EmbeddingStore embeddingStore( + EmbeddingModel embeddingModel, ResourceLoader resourceLoader, DataSource dataSource) throws IOException { + + // Normally, you would already have your embedding store filled with your data. + // However, for the purpose of this demonstration, we will: + + HikariDataSource hikariDataSource = (HikariDataSource) dataSource; + String jdbcUrl = hikariDataSource.getJdbcUrl(); + URI uri = URI.create(jdbcUrl.substring(5)); + String host = uri.getHost(); + int dbPort = uri.getPort(); + String path = uri.getPath(); + // 1. Create an postgres embedding store + // dimension of the embedding is 384 (all-minilm) and 1536 (openai) + EmbeddingStore embeddingStore = PgVectorEmbeddingStore.builder() + .host(host) + .port(dbPort != -1 ? dbPort : 5432) + .user(hikariDataSource.getUsername()) + .password(hikariDataSource.getPassword()) + .database(path.substring(1)) + .table("ai_vector_store") + .dimension(384) + .build(); + + // 2. Load an example document (medicaid-wa-faqs.pdf) + Resource pdfResource = resourceLoader.getResource("classpath:medicaid-wa-faqs.pdf"); + Document document = loadDocument(pdfResource.getFile().toPath(), new ApachePdfBoxDocumentParser()); + + // 3. Split the document into segments 500 tokens each + // 4. Convert segments into embeddings + // 5. Store embeddings into embedding store + // All this can be done manually, but we will use EmbeddingStoreIngestor to automate this: + DocumentSplitter documentSplitter = DocumentSplitters.recursive(500, 0, new OpenAiTokenizer(GPT_3_5_TURBO)); + EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() + .documentSplitter(documentSplitter) + .embeddingModel(embeddingModel) + .embeddingStore(embeddingStore) + .build(); + ingestor.ingest(document); + + return embeddingStore; + } +} diff --git a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/ChatTools.java b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/ChatTools.java index c07b81a..5ec5ba9 100644 --- a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/ChatTools.java +++ b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/ChatTools.java @@ -1,7 +1,6 @@ -package com.learning.ai.config; - -import org.springframework.stereotype.Component; - -@Component -public class ChatTools { -} +package com.learning.ai.config; + +import org.springframework.stereotype.Component; + +@Component +public class ChatTools {} diff --git a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/CustomerSupportAgent.java b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/CustomerSupportAgent.java index 4cff604..66fdfd6 100644 --- a/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/CustomerSupportAgent.java +++ b/llm-rag-with-langchain4j-spring-boot/src/main/java/com/learning/ai/config/CustomerSupportAgent.java @@ -1,25 +1,25 @@ -package com.learning.ai.config; - -import dev.langchain4j.service.SystemMessage; - -public interface CustomerSupportAgent { - - @SystemMessage({ - """ - - You're assisting with questions about services offered by Carina. - Carina is a two-sided healthcare marketplace focusing on home care aides (caregivers) - and their Medicaid in-home care clients (adults and children with developmental disabilities and low income elderly population). - Carina's mission is to build online tools to bring good jobs to care workers, so care workers can provide the - best possible care for those who need it. - - Use the information from the DOCUMENTS section to provide accurate answers but act as if you knew this information innately. - If unsure, simply state that you don't know. - - DOCUMENTS: - {documents} - - """ - }) - String chat(String documents); -} +package com.learning.ai.config; + +import dev.langchain4j.service.SystemMessage; + +public interface CustomerSupportAgent { + + @SystemMessage({ + """ + + You're assisting with questions about services offered by Carina. + Carina is a two-sided healthcare marketplace focusing on home care aides (caregivers) + and their Medicaid in-home care clients (adults and children with developmental disabilities and low income elderly population). + Carina's mission is to build online tools to bring good jobs to care workers, so care workers can provide the + best possible care for those who need it. + + Use the information from the DOCUMENTS section to provide accurate answers but act as if you knew this information innately. + If unsure, simply state that you don't know. + + DOCUMENTS: + {documents} + + """ + }) + String chat(String documents); +} diff --git a/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/LLMRagWithSpringBootTest.java b/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/LLMRagWithSpringBootTest.java index 8c0a04a..a16d08f 100644 --- a/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/LLMRagWithSpringBootTest.java +++ b/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/LLMRagWithSpringBootTest.java @@ -1,12 +1,11 @@ -package com.learning.ai; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class LLMRagWithSpringBootTest { - - @Test - void contextLoads() { - } -} \ No newline at end of file +package com.learning.ai; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(classes = TestLLMRagWithSpringBoot.class) +class LLMRagWithSpringBootTest { + + @Test + void contextLoads() {} +} diff --git a/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/TestLLMRagWithSpringBoot.java b/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/TestLLMRagWithSpringBoot.java new file mode 100644 index 0000000..faaca28 --- /dev/null +++ b/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/TestLLMRagWithSpringBoot.java @@ -0,0 +1,17 @@ +package com.learning.ai; + +import com.learning.ai.config.ContainersConfig; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; + +@TestConfiguration(proxyBeanMethods = false) +@ImportTestcontainers(ContainersConfig.class) +class TestLLMRagWithSpringBoot { + + public static void main(String[] args) { + SpringApplication.from(LLMRagWithSpringBoot::main) + .with(TestLLMRagWithSpringBoot.class) + .run(args); + } +} diff --git a/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/config/ContainersConfig.java b/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/config/ContainersConfig.java new file mode 100644 index 0000000..28531b4 --- /dev/null +++ b/llm-rag-with-langchain4j-spring-boot/src/test/java/com/learning/ai/config/ContainersConfig.java @@ -0,0 +1,12 @@ +package com.learning.ai.config; + +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; + +public interface ContainersConfig { + + @ServiceConnection + PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer<>( + DockerImageName.parse("ankane/pgvector:v0.5.1").asCompatibleSubstituteFor("postgres")); +}