Skip to content

Commit 87ce6c8

Browse files
authored
Issue 06 (#36)
* Issue-06 Spring AI - AzureOpenAi Integration * Issue-06 Spring AI - AzureOpenAi Integration * Issue-06 Spring AI - AzureOpenAi Integration
1 parent 7c1bd2b commit 87ce6c8

8 files changed

Lines changed: 322 additions & 1 deletion

File tree

springboot-modules/spring-ai/pom.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
<artifactId>spring-ai-spring-boot-testcontainers</artifactId>
2828
<scope>test</scope>
2929
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.ai</groupId>
32+
<artifactId>spring-ai-vector-store</artifactId>
33+
</dependency>
3034
<dependency>
3135
<groupId>org.testcontainers</groupId>
3236
<artifactId>junit-jupiter</artifactId>
@@ -50,7 +54,7 @@
5054
<dependency>
5155
<groupId>com.fasterxml.jackson.core</groupId>
5256
<artifactId>jackson-core</artifactId>
53-
<version>2.17.2</version>
57+
<version>2.18.3</version>
5458
</dependency>
5559
<dependency>
5660
<groupId>org.springframework.boot</groupId>
@@ -62,6 +66,12 @@
6266
<artifactId>hsqldb</artifactId>
6367
<version>${hsqldb.version}</version>
6468
</dependency>
69+
70+
<dependency>
71+
<groupId>org.springframework.ai</groupId>
72+
<artifactId>spring-ai-starter-model-azure-openai</artifactId>
73+
</dependency>
74+
6575
</dependencies>
6676

6777
<dependencyManagement>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.ks.azureopenai;
2+
3+
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
4+
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
5+
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
6+
import org.springframework.ai.vectorstore.SimpleVectorStore;
7+
import org.springframework.beans.factory.annotation.Value;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.context.annotation.Profile;
11+
12+
import com.azure.ai.openai.OpenAIClientBuilder;
13+
import com.azure.core.credential.AzureKeyCredential;
14+
15+
@Configuration
16+
@Profile("azureopenai")
17+
public class ApplicationConfiguration {
18+
@Value("${spring.ai.azure.openai.api-key}")
19+
private String apiKey;
20+
@Value("${spring.ai.azure.openai.endpoint}")
21+
private String endpoint;
22+
23+
@Bean
24+
public ChatService chatService(AzureOpenAiChatModel azureOpenAiChatModel) {
25+
return new ChatService(azureOpenAiChatModel);
26+
}
27+
28+
@Bean
29+
public ChatService customChatService() {
30+
OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
31+
.credential(new AzureKeyCredential(getCredential()))
32+
.endpoint(getEndpoint());
33+
34+
AzureOpenAiChatOptions openAIChatOptions = AzureOpenAiChatOptions.builder()
35+
.deploymentName("gpt-5-nano")
36+
.temperature(1d)
37+
.build();
38+
39+
AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder()
40+
.openAIClientBuilder(openAIClientBuilder)
41+
.defaultOptions(openAIChatOptions)
42+
.build();
43+
return new ChatService(chatModel);
44+
}
45+
46+
private String getCredential() {
47+
return apiKey;
48+
}
49+
50+
private String getEndpoint() {
51+
return endpoint;
52+
}
53+
54+
@Bean
55+
VectorDBService vectorDBStore(AzureOpenAiEmbeddingModel embeddingModel) {
56+
return new VectorDBService(SimpleVectorStore.builder(embeddingModel)
57+
.build()
58+
);
59+
}
60+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.ks.azureopenai;
2+
3+
import org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration;
4+
import org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration;
5+
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
6+
import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
7+
import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration;
8+
import org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration;
9+
import org.springframework.boot.SpringApplication;
10+
import org.springframework.boot.autoconfigure.SpringBootApplication;
11+
12+
@SpringBootApplication(exclude = { OpenAiAudioSpeechAutoConfiguration.class,
13+
OpenAiAudioTranscriptionAutoConfiguration.class,
14+
OpenAiChatAutoConfiguration.class,
15+
OpenAiEmbeddingAutoConfiguration.class,
16+
OpenAiImageAutoConfiguration.class,
17+
OpenAiModerationAutoConfiguration.class
18+
})
19+
public class ChatApplication {
20+
public static void main(String[] args) {
21+
SpringApplication.run(ChatApplication.class, args);
22+
}
23+
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.ks.azureopenai;
2+
3+
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
4+
import org.springframework.ai.chat.prompt.Prompt;
5+
6+
public class ChatService {
7+
8+
AzureOpenAiChatModel chatModel;
9+
10+
public ChatService(AzureOpenAiChatModel chatModel) {
11+
this.chatModel = chatModel;
12+
}
13+
14+
public String chat(Prompt prompt) {
15+
return chatModel.call(prompt)
16+
.getResult()
17+
.getOutput()
18+
.getText();
19+
}
20+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.ks.azureopenai;
2+
3+
import java.util.List;
4+
5+
import org.springframework.ai.document.Document;
6+
import org.springframework.ai.vectorstore.SearchRequest;
7+
import org.springframework.ai.vectorstore.SimpleVectorStore;
8+
9+
public class VectorDBService {
10+
11+
private SimpleVectorStore vectorStore;
12+
13+
public VectorDBService(SimpleVectorStore vectorStore) {
14+
this.vectorStore = vectorStore;
15+
}
16+
17+
public void saveDocs(List<Document> docs) {
18+
this.vectorStore.doAdd(docs);
19+
}
20+
21+
public String fetchContextFromVectorDB(String query) {
22+
List<Document> queryResults = vectorStore.doSimilaritySearch(
23+
SearchRequest.builder()
24+
.query(query).topK(2)
25+
.build()
26+
);
27+
StringBuilder contextBuilder = new StringBuilder();
28+
for (Document docs : queryResults) {
29+
contextBuilder.append(docs.getText()).append(" ");
30+
}
31+
return contextBuilder.toString().trim();
32+
}
33+
34+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
spring.application.name=spring-ai-azureopenai
2+
spring.ai.azure.openai.chat.options.user=springai-azureopenai-user
3+
spring.ai.azure.openai.api-key=FSPyum-XXXXX
4+
spring.ai.azure.openai.endpoint=https://parth-YYYY-eastus2.openai.azure.com/
5+
spring.ai.azure.openai.chat.options.deployment-name=gpt-5-nano
6+
spring.ai.azure.openai.chat.options.temperature=1
7+
8+
spring.ai.azure.openai.embedding.options.deployment-name=text-embedding-ada-002
9+
spring.ai.azure.openai.embedding.options.model=text-embedding-ada-002
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
@startuml
2+
'https://plantuml.com/class-diagram
3+
set namespaceSeparator none
4+
allowmixing
5+
hide empty attributes
6+
'skinparam Handwritten false
7+
skinparam ClassBorderColor black
8+
skinparam BackgroundColor #F0EDDE
9+
skinparam ClassAttributeFontColor #222222
10+
skinparam ClassFontStyle bold
11+
12+
skinparam class {
13+
ArrowColor #3C88A3
14+
ArrowFontColor #3C88A3
15+
hide empty attributes
16+
skinparam Handwritten false
17+
skinparam ClassBorderColor black
18+
BackgroundColor #FFFFFF
19+
}
20+
together {
21+
class "AzureOpenAIChatModel" as acm {
22+
+call(Prompt prompt): ChatResponse
23+
+call(String prompt): String
24+
+getDefaultOptions(): AzureOpenAiChatOptions
25+
}
26+
class "AzureOpenAiChatModel.Builder" as ab {
27+
+defaultOptions(AzureOpenAiChatOptions options): AzureOpenAiChatModel.Builder
28+
+build(): AzureOpenAIChatModel
29+
}
30+
class "AzureOpenAiChatOptions" as ao {
31+
+getTemperature(): Double
32+
+getMaxTokens(): Integer
33+
+getModel(): String
34+
+builder(): AzureOpenAiChatOptions.Builder
35+
}
36+
37+
}
38+
39+
class "AzureOpenAiChatOptions.Builder" as aob {
40+
+temperature(Double temperature): AzureOpenAiChatOptions.Builder
41+
+maxTokens(Integer maxTokens): AzureOpenAiChatOptions.Builder
42+
+model(String model): AzureOpenAiChatOptions.Builder
43+
+build(): AzureOpenAiChatOptions
44+
}
45+
aob -down[hidden]- acm : dummy
46+
acm <-down- ab : static nested
47+
aob -left-> ao : static nested
48+
acm .up.> ao : uses
49+
@enduml
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.ks.azureopenai;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
import org.junit.jupiter.api.BeforeAll;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.TestInstance;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
import org.springframework.ai.chat.prompt.Prompt;
14+
import org.springframework.ai.chat.prompt.PromptTemplate;
15+
import org.springframework.ai.document.Document;
16+
import org.springframework.beans.factory.annotation.Autowired;
17+
import org.springframework.boot.test.context.SpringBootTest;
18+
import org.springframework.test.context.ActiveProfiles;
19+
20+
@SpringBootTest
21+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
22+
@ActiveProfiles("azureopenai")
23+
public class SpringAIAzureOpenAiLiveTest {
24+
final Logger logger = LoggerFactory.getLogger(SpringAIAzureOpenAiLiveTest.class);
25+
@Autowired
26+
private ChatService chatService;
27+
28+
@Autowired
29+
private ChatService customChatService;
30+
31+
@Autowired
32+
private VectorDBService vectorDBStore;
33+
34+
@BeforeAll
35+
void setup() {
36+
Document doc1 = new Document("""
37+
TechVerse Solutions provides tailor-made
38+
cloud integration services for finance and retail companies.
39+
""");
40+
Document doc2 = new Document("""
41+
The company’s AI-powered analytics platform delivers actionable
42+
insights to help customers optimize their business operations.
43+
""");
44+
Document doc3 = new Document("""
45+
At TechVerse Solutions, dedicated experts support clients with 24/7
46+
monitoring and rapid troubleshooting.
47+
""");
48+
49+
List<Document> docs = List.of(doc1, doc2, doc3);
50+
vectorDBStore.saveDocs(docs);
51+
logger.info("Documents added to vector store");
52+
}
53+
54+
@Test
55+
void givenProgrammaticallyConfiguredClient_whenQueryLLM_thenRespond() {
56+
String query = "TechVerse Solutions provides cloud " +
57+
"integration services for what type of companies?";
58+
String context = vectorDBStore.fetchContextFromVectorDB(query);
59+
logger.info("context fetched: {}", context);
60+
String prompt = """
61+
Context: {context}
62+
Question: {question}
63+
Instructions:
64+
Using the provided context, answer the question in a concise manner.
65+
If the context does not contain the answer, respond with "I don't know".
66+
""";
67+
PromptTemplate promptTemplate = PromptTemplate.builder()
68+
.template(prompt)
69+
.variables(Map.of("context", context, "question", query))
70+
.build();
71+
Prompt finalPrompt = promptTemplate.create();
72+
73+
logger.info("finalPrompt: {}", finalPrompt.getContents());
74+
75+
String response = customChatService.chat(finalPrompt);
76+
77+
logger.info("response: {}", response);
78+
79+
assertThat(response)
80+
.isNotNull()
81+
.containsIgnoringCase("Finance")
82+
.containsIgnoringCase("Retail");
83+
}
84+
85+
@Test
86+
void givenAutoConfiguredClient_whenQueryLLM_thenRespond() {
87+
String query = "TechVerse Solutions provides cloud " +
88+
"integration services for what type of companies?";
89+
String context = vectorDBStore.fetchContextFromVectorDB(query);
90+
logger.info("context fetched: {}", context);
91+
String prompt = """
92+
Context: {context}
93+
Question: {question}
94+
Instructions:
95+
Using the provided context, answer the question in a concise manner.
96+
If the context does not contain the answer, respond with "I don't know".
97+
""";
98+
PromptTemplate promptTemplate = PromptTemplate.builder()
99+
.template(prompt)
100+
.variables(Map.of("context", context, "question", query))
101+
.build();
102+
Prompt finalPrompt = promptTemplate.create();
103+
104+
logger.info("finalPrompt: {}", finalPrompt.getContents());
105+
106+
String response = chatService.chat(finalPrompt);
107+
108+
logger.info("response: {}", response);
109+
110+
assertThat(response)
111+
.isNotNull()
112+
.containsIgnoringCase("Finance")
113+
.containsIgnoringCase("Retail");
114+
}
115+
}

0 commit comments

Comments
 (0)