From 69a11b09f91d23c50cb9405b7e755bddc7b4b406 Mon Sep 17 00:00:00 2001 From: parthiv39731 <70740707+parthiv39731@users.noreply.github.com> Date: Sat, 11 Oct 2025 23:46:07 +0530 Subject: [PATCH 1/3] Issue-06 Spring AI - AzureOpenAi Integration --- springboot-modules/spring-ai/pom.xml | 8 ++- .../azureopenai/ApplicationConfiguration.java | 18 +++++++ .../com/ks/azureopenai/ChatApplication.java | 24 +++++++++ .../java/com/ks/azureopenai/ChatService.java | 18 +++++++ .../main/resources/puml/azureopenai-cld.puml | 49 +++++++++++++++++++ .../SpringAIAzureOpenAILiveTest.java | 28 +++++++++++ 6 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java create mode 100644 springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatApplication.java create mode 100644 springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java create mode 100644 springboot-modules/spring-ai/src/main/resources/puml/azureopenai-cld.puml create mode 100644 springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java diff --git a/springboot-modules/spring-ai/pom.xml b/springboot-modules/spring-ai/pom.xml index 68252bd..351d349 100644 --- a/springboot-modules/spring-ai/pom.xml +++ b/springboot-modules/spring-ai/pom.xml @@ -50,7 +50,7 @@ com.fasterxml.jackson.core jackson-core - 2.17.2 + 2.18.3 org.springframework.boot @@ -62,6 +62,12 @@ hsqldb ${hsqldb.version} + + + org.springframework.ai + spring-ai-starter-model-azure-openai + + diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java new file mode 100644 index 0000000..d91054f --- /dev/null +++ b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java @@ -0,0 +1,18 @@ +package com.ks.azureopenai; + +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("azureopenai") +public class ApplicationConfiguration { + + @Bean + public ChatService chatService(@Autowired AzureOpenAiChatModel azureOpenAiChatModel) { + return new ChatService(azureOpenAiChatModel); + } + +} diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatApplication.java b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatApplication.java new file mode 100644 index 0000000..5a10149 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatApplication.java @@ -0,0 +1,24 @@ +package com.ks.azureopenai; + +import org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(exclude = { OpenAiAudioSpeechAutoConfiguration.class, + OpenAiAudioTranscriptionAutoConfiguration.class, + OpenAiChatAutoConfiguration.class, + OpenAiEmbeddingAutoConfiguration.class, + OpenAiImageAutoConfiguration.class, + OpenAiModerationAutoConfiguration.class +}) +public class ChatApplication { + public static void main(String[] args) { + SpringApplication.run(ChatApplication.class, args); + } + +} diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java new file mode 100644 index 0000000..275ccf1 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java @@ -0,0 +1,18 @@ +package com.ks.azureopenai; + +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; + +public class ChatService { + + AzureOpenAiChatModel chatModel; + + public ChatService(AzureOpenAiChatModel chatModel) { + this.chatModel = chatModel; + } + + public String chat(String message) { + return chatModel.call(message); + } + + +} diff --git a/springboot-modules/spring-ai/src/main/resources/puml/azureopenai-cld.puml b/springboot-modules/spring-ai/src/main/resources/puml/azureopenai-cld.puml new file mode 100644 index 0000000..1965fe8 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/resources/puml/azureopenai-cld.puml @@ -0,0 +1,49 @@ +@startuml +'https://plantuml.com/class-diagram +set namespaceSeparator none +allowmixing +hide empty attributes +'skinparam Handwritten false +skinparam ClassBorderColor black +skinparam BackgroundColor #F0EDDE +skinparam ClassAttributeFontColor #222222 +skinparam ClassFontStyle bold + +skinparam class { +ArrowColor #3C88A3 +ArrowFontColor #3C88A3 +hide empty attributes +skinparam Handwritten false +skinparam ClassBorderColor black +BackgroundColor #FFFFFF +} +together { + class "AzureOpenAIChatModel" as acm { + +call(Prompt prompt): ChatResponse + +call(String prompt): String + +getDefaultOptions(): AzureOpenAiChatOptions + } + class "AzureOpenAiChatModel.Builder" as ab { + +defaultOptions(AzureOpenAiChatOptions options): AzureOpenAiChatModel.Builder + +build(): AzureOpenAIChatModel + } + class "AzureOpenAiChatOptions" as ao { + +getTemperature(): Double + +getMaxTokens(): Integer + +getModel(): String + +builder(): AzureOpenAiChatOptions.Builder + } + +} + +class "AzureOpenAiChatOptions.Builder" as aob { + +temperature(Double temperature): AzureOpenAiChatOptions.Builder + +maxTokens(Integer maxTokens): AzureOpenAiChatOptions.Builder + +model(String model): AzureOpenAiChatOptions.Builder + +build(): AzureOpenAiChatOptions +} +aob -down[hidden]- acm : dummy +acm <-down- ab : static nested +aob -left-> ao : static nested +acm .up.> ao : uses +@enduml \ No newline at end of file diff --git a/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java b/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java new file mode 100644 index 0000000..fe62ee0 --- /dev/null +++ b/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java @@ -0,0 +1,28 @@ +package com.ks.azureopenai; + +import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("azureopenai") +public class SpringAIAzureOpenAILiveTest { + final Logger logger = LoggerFactory.getLogger(SpringAIAzureOpenAILiveTest.class); + @Autowired + private ChatService chatService; + + @Test + void test() { + logger.info("invoked successfully"); + String response = chatService.chat("what is the capital of India?"); + assertThat(response) + .isNotNull() + .containsIgnoringCase("New Delhi"); + logger.info("response: {}", response); + } + +} From fe0504250d9cb70082b305adcbcc4ace30db2d70 Mon Sep 17 00:00:00 2001 From: parthiv39731 <70740707+parthiv39731@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:13:37 +0530 Subject: [PATCH 2/3] Issue-06 Spring AI - AzureOpenAi Integration --- springboot-modules/spring-ai/pom.xml | 4 + .../azureopenai/ApplicationConfiguration.java | 46 ++++++- .../java/com/ks/azureopenai/ChatService.java | 10 +- .../com/ks/azureopenai/VectorDBService.java | 34 ++++++ .../application-azureopenai.properties | 9 ++ .../SpringAIAzureOpenAILiveTest.java | 28 ----- .../SpringAIAzureOpenAiLiveTest.java | 115 ++++++++++++++++++ 7 files changed, 212 insertions(+), 34 deletions(-) create mode 100644 springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/VectorDBService.java create mode 100644 springboot-modules/spring-ai/src/main/resources/application-azureopenai.properties delete mode 100644 springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java create mode 100644 springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java diff --git a/springboot-modules/spring-ai/pom.xml b/springboot-modules/spring-ai/pom.xml index 351d349..e703ffe 100644 --- a/springboot-modules/spring-ai/pom.xml +++ b/springboot-modules/spring-ai/pom.xml @@ -27,6 +27,10 @@ spring-ai-spring-boot-testcontainers test + + org.springframework.ai + spring-ai-vector-store + org.testcontainers junit-jupiter diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java index d91054f..6efc37e 100644 --- a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java +++ b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ApplicationConfiguration.java @@ -1,18 +1,60 @@ package com.ks.azureopenai; import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; +import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; +import org.springframework.ai.vectorstore.SimpleVectorStore; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; + @Configuration @Profile("azureopenai") public class ApplicationConfiguration { + @Value("${spring.ai.azure.openai.api-key}") + private String apiKey; + @Value("${spring.ai.azure.openai.endpoint}") + private String endpoint; @Bean - public ChatService chatService(@Autowired AzureOpenAiChatModel azureOpenAiChatModel) { + public ChatService chatService(AzureOpenAiChatModel azureOpenAiChatModel) { return new ChatService(azureOpenAiChatModel); } + @Bean + public ChatService customChatService() { + OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(getCredential())) + .endpoint(getEndpoint()); + + AzureOpenAiChatOptions openAIChatOptions = AzureOpenAiChatOptions.builder() + .deploymentName("gpt-5-nano") + .temperature(1d) + .build(); + + AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder() + .openAIClientBuilder(openAIClientBuilder) + .defaultOptions(openAIChatOptions) + .build(); + return new ChatService(chatModel); + } + + private String getCredential() { + return apiKey; + } + + private String getEndpoint() { + return endpoint; + } + + @Bean + VectorDBService vectorDBStore(AzureOpenAiEmbeddingModel embeddingModel) { + return new VectorDBService(SimpleVectorStore.builder(embeddingModel) + .build() + ); + } } diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java index 275ccf1..97e3ceb 100644 --- a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java +++ b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/ChatService.java @@ -1,6 +1,7 @@ package com.ks.azureopenai; import org.springframework.ai.azure.openai.AzureOpenAiChatModel; +import org.springframework.ai.chat.prompt.Prompt; public class ChatService { @@ -10,9 +11,10 @@ public ChatService(AzureOpenAiChatModel chatModel) { this.chatModel = chatModel; } - public String chat(String message) { - return chatModel.call(message); + public String chat(Prompt prompt) { + return chatModel.call(prompt) + .getResult() + .getOutput() + .getText(); } - - } diff --git a/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/VectorDBService.java b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/VectorDBService.java new file mode 100644 index 0000000..ee901e3 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/java/com/ks/azureopenai/VectorDBService.java @@ -0,0 +1,34 @@ +package com.ks.azureopenai; + +import java.util.List; + +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.SimpleVectorStore; + +public class VectorDBService { + + private SimpleVectorStore vectorStore; + + public VectorDBService(SimpleVectorStore vectorStore) { + this.vectorStore = vectorStore; + } + + public void saveDocs(List docs) { + this.vectorStore.doAdd(docs); + } + + public String fetchContextFromVectorDB(String query) { + List queryResults = vectorStore.doSimilaritySearch( + SearchRequest.builder() + .query(query).topK(2) + .build() + ); + StringBuilder contextBuilder = new StringBuilder(); + for (Document docs : queryResults) { + contextBuilder.append(docs.getText()).append(" "); + } + return contextBuilder.toString().trim(); + } + +} diff --git a/springboot-modules/spring-ai/src/main/resources/application-azureopenai.properties b/springboot-modules/spring-ai/src/main/resources/application-azureopenai.properties new file mode 100644 index 0000000..81d29f7 --- /dev/null +++ b/springboot-modules/spring-ai/src/main/resources/application-azureopenai.properties @@ -0,0 +1,9 @@ +spring.application.name=spring-ai-azureopenai +spring.ai.azure.openai.chat.options.user=springai-azureopenai-user +spring.ai.azure.openai.api-key=FSPyum-XXXXX +spring.ai.azure.openai.endpoint=https://parth-YYYY-eastus2.openai.azure.com/ +spring.ai.azure.openai.chat.options.deployment-name=gpt-5-nano +spring.ai.azure.openai.chat.options.temperature=1 + +spring.ai.azure.openai.embedding.options.deployment-name=text-embedding-ada-002 +spring.ai.azure.openai.embedding.options.model=text-embedding-ada-002 \ No newline at end of file diff --git a/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java b/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java deleted file mode 100644 index fe62ee0..0000000 --- a/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAILiveTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.ks.azureopenai; - -import static org.assertj.core.api.Assertions.*; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("azureopenai") -public class SpringAIAzureOpenAILiveTest { - final Logger logger = LoggerFactory.getLogger(SpringAIAzureOpenAILiveTest.class); - @Autowired - private ChatService chatService; - - @Test - void test() { - logger.info("invoked successfully"); - String response = chatService.chat("what is the capital of India?"); - assertThat(response) - .isNotNull() - .containsIgnoringCase("New Delhi"); - logger.info("response: {}", response); - } - -} diff --git a/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java b/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java new file mode 100644 index 0000000..91241d6 --- /dev/null +++ b/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java @@ -0,0 +1,115 @@ +package com.ks.azureopenai; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.document.Document; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ActiveProfiles("azureopenai") +public class SpringAIAzureOpenAiLiveTest { + final Logger logger = LoggerFactory.getLogger(SpringAIAzureOpenAiLiveTest.class); + @Autowired + private ChatService chatService; + + @Autowired + private ChatService customChatService; + + @Autowired + private VectorDBService vectorDBStore; + + @BeforeAll + void setup() { + Document doc1 = new Document(""" + TechVerse Solutions provides tailor-made + cloud integration services for finance and retail companies. + """); + Document doc2 = new Document(""" + The company’s AI-powered analytics platform delivers actionable + insights to help customers optimize their business operations. + """); + Document doc3 = new Document(""" + At TechVerse Solutions, dedicated experts support clients with 24/7 + monitoring and rapid troubleshooting. + """); + + List docs = List.of(doc1, doc2, doc3); + vectorDBStore.saveDocs(docs); + logger.info("Documents added to vector store"); + } + + @Test + void givenProgrammaticallyConfiguredClient_whenQueryAzureOpenAiDeployment_thenReturnResponse() { + String query = "TechVerse Solutions provides cloud " + + "integration services for what type of companies?"; + String context = vectorDBStore.fetchContextFromVectorDB(query); + logger.info("context fetched: {}", context); + String prompt = """ + Context: {context} + Question: {question} + Instructions: + Using the provided context, answer the question in a concise manner. + If the context does not contain the answer, respond with "I don't know". + """; + PromptTemplate promptTemplate = PromptTemplate.builder() + .template(prompt) + .variables(Map.of("context", context, "question", query)) + .build(); + Prompt finalPrompt = promptTemplate.create(); + + logger.info("finalPrompt: {}", finalPrompt.getContents()); + + String response = customChatService.chat(finalPrompt); + + logger.info("response: {}", response); + + assertThat(response) + .isNotNull() + .containsIgnoringCase("Finance") + .containsIgnoringCase("Retail"); + } + + @Test + void givenAutoConfiguredClient_whenQueryAzureOpenAiDeployment_thenReturnResponse() { + String query = "TechVerse Solutions provides cloud " + + "integration services for what type of companies?"; + String context = vectorDBStore.fetchContextFromVectorDB(query); + logger.info("context fetched: {}", context); + String prompt = """ + Context: {context} + Question: {question} + Instructions: + Using the provided context, answer the question in a concise manner. + If the context does not contain the answer, respond with "I don't know". + """; + PromptTemplate promptTemplate = PromptTemplate.builder() + .template(prompt) + .variables(Map.of("context", context, "question", query)) + .build(); + Prompt finalPrompt = promptTemplate.create(); + + logger.info("finalPrompt: {}", finalPrompt.getContents()); + + String response = chatService.chat(finalPrompt); + + logger.info("response: {}", response); + + assertThat(response) + .isNotNull() + .containsIgnoringCase("Finance") + .containsIgnoringCase("Retail"); + } +} From 61cbb61d8ddc3f64add9edcf2a11d6fc8a50d398 Mon Sep 17 00:00:00 2001 From: parthiv39731 <70740707+parthiv39731@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:30:34 +0530 Subject: [PATCH 3/3] Issue-06 Spring AI - AzureOpenAi Integration --- .../java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java b/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java index 91241d6..b5f90dd 100644 --- a/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java +++ b/springboot-modules/spring-ai/src/test/java/com/ks/azureopenai/SpringAIAzureOpenAiLiveTest.java @@ -52,7 +52,7 @@ void setup() { } @Test - void givenProgrammaticallyConfiguredClient_whenQueryAzureOpenAiDeployment_thenReturnResponse() { + void givenProgrammaticallyConfiguredClient_whenQueryLLM_thenRespond() { String query = "TechVerse Solutions provides cloud " + "integration services for what type of companies?"; String context = vectorDBStore.fetchContextFromVectorDB(query); @@ -83,7 +83,7 @@ void givenProgrammaticallyConfiguredClient_whenQueryAzureOpenAiDeployment_thenRe } @Test - void givenAutoConfiguredClient_whenQueryAzureOpenAiDeployment_thenReturnResponse() { + void givenAutoConfiguredClient_whenQueryLLM_thenRespond() { String query = "TechVerse Solutions provides cloud " + "integration services for what type of companies?"; String context = vectorDBStore.fetchContextFromVectorDB(query);