Skip to content

Commit ae612e9

Browse files
authored
Merge pull request #47 from milderhc/add-sk-chat
Add SK chat support
2 parents 7537fdd + 659b508 commit ae612e9

File tree

15 files changed

+716
-129
lines changed

15 files changed

+716
-129
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Large diffs are not rendered by default.

app/backend/src/main/java/com/microsoft/openai/samples/rag/Application.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import org.slf4j.LoggerFactory;
66
import org.springframework.boot.SpringApplication;
77
import org.springframework.boot.autoconfigure.SpringBootApplication;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.web.servlet.config.annotation.CorsRegistry;
10+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
811

912
@SpringBootApplication
1013
public class Application {
Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
// Copyright (c) Microsoft. All rights reserved.
21
package com.microsoft.openai.samples.rag.approaches;
32

43
import com.microsoft.openai.samples.rag.ask.approaches.PlainJavaAskApproach;
54
import com.microsoft.openai.samples.rag.ask.approaches.semantickernel.JavaSemanticKernelChainsApproach;
65
import com.microsoft.openai.samples.rag.ask.approaches.semantickernel.JavaSemanticKernelPlannerApproach;
76
import com.microsoft.openai.samples.rag.ask.approaches.semantickernel.JavaSemanticKernelWithMemoryApproach;
87
import com.microsoft.openai.samples.rag.chat.approaches.PlainJavaChatApproach;
8+
import com.microsoft.openai.samples.rag.chat.approaches.semantickernel.JavaSemanticKernelChainsChatApproach;
9+
import com.microsoft.openai.samples.rag.chat.approaches.semantickernel.JavaSemanticKernelWithMemoryChatApproach;
910
import org.springframework.context.ApplicationContext;
1011
import org.springframework.context.ApplicationContextAware;
1112
import org.springframework.stereotype.Component;
1213

1314
@Component
14-
public class RAGApproachFactorySpringBootImpl
15-
implements RAGApproachFactory, ApplicationContextAware {
15+
public class RAGApproachFactorySpringBootImpl implements RAGApproachFactory, ApplicationContextAware {
1616

1717
private static final String JAVA_OPENAI_SDK = "jos";
1818
private static final String JAVA_SEMANTIC_KERNEL = "jsk";
@@ -29,31 +29,34 @@ public class RAGApproachFactorySpringBootImpl
2929
@Override
3030
public RAGApproach createApproach(String approachName, RAGType ragType, RAGOptions ragOptions) {
3131

32-
if (ragType.equals(RAGType.CHAT) && JAVA_OPENAI_SDK.equals(approachName)) {
33-
return applicationContext.getBean(PlainJavaChatApproach.class);
34-
32+
if (ragType.equals(RAGType.CHAT)) {
33+
if (JAVA_OPENAI_SDK.equals(approachName)) {
34+
return applicationContext.getBean(PlainJavaChatApproach.class);
35+
} else if (JAVA_SEMANTIC_KERNEL.equals(approachName)) {
36+
return applicationContext.getBean(JavaSemanticKernelWithMemoryChatApproach.class);
37+
} else if (
38+
JAVA_SEMANTIC_KERNEL_PLANNER.equals(approachName) &&
39+
ragOptions != null &&
40+
ragOptions.getSemantickKernelMode() != null &&
41+
ragOptions.getSemantickKernelMode() == SemanticKernelMode.chains) {
42+
return applicationContext.getBean(JavaSemanticKernelChainsChatApproach.class);
43+
}
3544
} else if (ragType.equals(RAGType.ASK)) {
3645
if (JAVA_OPENAI_SDK.equals(approachName))
3746
return applicationContext.getBean(PlainJavaAskApproach.class);
3847
else if (JAVA_SEMANTIC_KERNEL.equals(approachName))
3948
return applicationContext.getBean(JavaSemanticKernelWithMemoryApproach.class);
40-
else if (JAVA_SEMANTIC_KERNEL_PLANNER.equals(approachName)
41-
&& ragOptions.getSemantickKernelMode() != null
42-
&& ragOptions.getSemantickKernelMode() == SemanticKernelMode.planner)
49+
else if (JAVA_SEMANTIC_KERNEL_PLANNER.equals(approachName) && ragOptions.getSemantickKernelMode() != null && ragOptions.getSemantickKernelMode() == SemanticKernelMode.planner)
4350
return applicationContext.getBean(JavaSemanticKernelPlannerApproach.class);
44-
else if (JAVA_SEMANTIC_KERNEL_PLANNER.equals(approachName)
45-
&& ragOptions != null
46-
&& ragOptions.getSemantickKernelMode() != null
47-
&& ragOptions.getSemantickKernelMode() == SemanticKernelMode.chains)
51+
else if (JAVA_SEMANTIC_KERNEL_PLANNER.equals(approachName) && ragOptions != null && ragOptions.getSemantickKernelMode() != null && ragOptions.getSemantickKernelMode() == SemanticKernelMode.chains)
4852
return applicationContext.getBean(JavaSemanticKernelChainsApproach.class);
4953
}
50-
// if this point is reached then the combination of approach and rag type is not supported
51-
throw new IllegalArgumentException(
52-
"Invalid combination for approach[%s] and rag type[%s]: "
53-
.formatted(approachName, ragType));
54+
//if this point is reached then the combination of approach and rag type is not supported
55+
throw new IllegalArgumentException("Invalid combination for approach[%s] and rag type[%s]: ".formatted(approachName, ragType));
5456
}
5557

5658
public void setApplicationContext(ApplicationContext applicationContext) {
5759
this.applicationContext = applicationContext;
5860
}
61+
5962
}

app/backend/src/main/java/com/microsoft/openai/samples/rag/ask/approaches/semantickernel/JavaSemanticKernelChainsApproach.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.microsoft.openai.samples.rag.approaches.RAGApproach;
77
import com.microsoft.openai.samples.rag.approaches.RAGOptions;
88
import com.microsoft.openai.samples.rag.approaches.RAGResponse;
9+
import com.microsoft.openai.samples.rag.retrieval.semantickernel.CognitiveSearchPlugin;
910
import com.microsoft.openai.samples.rag.proxy.CognitiveSearchProxy;
1011
import com.microsoft.openai.samples.rag.proxy.OpenAIProxy;
1112
import com.microsoft.semantickernel.Kernel;
@@ -25,7 +26,7 @@
2526
/**
2627
* Use Java Semantic Kernel framework with semantic and native functions chaining. It uses an
2728
* imperative style for AI orchestration through semantic kernel functions chaining.
28-
* InformationFinder.Search native function and RAG.AnswerQuestion semantic function are called
29+
* InformationFinder.SearchFromQuestion native function and RAG.AnswerQuestion semantic function are called
2930
* sequentially. Several cognitive search retrieval options are available: Text, Vector, Hybrid.
3031
*/
3132
@Component
@@ -74,7 +75,7 @@ public RAGResponse run(String question, RAGOptions options) {
7475
question,
7576
semanticKernel
7677
.getSkill("InformationFinder")
77-
.getFunction("Search", null))
78+
.getFunction("SearchFromQuestion", null))
7879
.block();
7980

8081
var sources = formSourcesList(searchContext.getResult());
@@ -135,9 +136,9 @@ private List<ContentSource> formSourcesList(String result) {
135136

136137
/**
137138
* Build semantic kernel context with AnswerQuestion semantic function and
138-
* InformationFinder.Search native function. AnswerQuestion is imported from
139-
* src/main/resources/semantickernel/Plugins. InformationFinder.Search is implemented in a
140-
* traditional Java class method: CognitiveSearchPlugin.search
139+
* InformationFinder.SearchFromQuestion native function. AnswerQuestion is imported from
140+
* src/main/resources/semantickernel/Plugins. InformationFinder.SearchFromQuestion is implemented in a
141+
* traditional Java class method: CognitiveSearchPlugin.searchFromConversation
141142
*
142143
* @param options
143144
* @return
@@ -155,7 +156,6 @@ private Kernel buildSemanticKernel(RAGOptions options) {
155156
kernel.importSkill(
156157
new CognitiveSearchPlugin(this.cognitiveSearchProxy, this.openAIProxy, options),
157158
"InformationFinder");
158-
159159
kernel.importSkillFromResources("semantickernel/Plugins", "RAG", "AnswerQuestion", null);
160160

161161
return kernel;

app/backend/src/main/java/com/microsoft/openai/samples/rag/ask/approaches/semantickernel/JavaSemanticKernelPlannerApproach.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.microsoft.openai.samples.rag.approaches.RAGApproach;
66
import com.microsoft.openai.samples.rag.approaches.RAGOptions;
77
import com.microsoft.openai.samples.rag.approaches.RAGResponse;
8+
import com.microsoft.openai.samples.rag.retrieval.semantickernel.CognitiveSearchPlugin;
89
import com.microsoft.openai.samples.rag.proxy.CognitiveSearchProxy;
910
import com.microsoft.openai.samples.rag.proxy.OpenAIProxy;
1011
import com.microsoft.semantickernel.Kernel;
@@ -97,8 +98,8 @@ public void runStreaming(
9798
/**
9899
* Build semantic kernel context with AnswerQuestion semantic function and
99100
* InformationFinder.Search native function. AnswerQuestion is imported from
100-
* src/main/resources/semantickernel/Plugins. InformationFinder.Search is implemented in a
101-
* traditional Java class method: CognitiveSearchPlugin.search
101+
* src/main/resources/semantickernel/Plugins. InformationFinder.SearchFromQuestion is implemented in a
102+
* traditional Java class method: CognitiveSearchPlugin.searchFromQuestion
102103
*
103104
* @param options
104105
* @return
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.microsoft.openai.samples.rag.chat.approaches.semantickernel;
2+
3+
import com.azure.ai.openai.OpenAIAsyncClient;
4+
import com.microsoft.openai.samples.rag.approaches.ContentSource;
5+
import com.microsoft.openai.samples.rag.approaches.RAGApproach;
6+
import com.microsoft.openai.samples.rag.approaches.RAGOptions;
7+
import com.microsoft.openai.samples.rag.approaches.RAGResponse;
8+
import com.microsoft.openai.samples.rag.retrieval.semantickernel.CognitiveSearchPlugin;
9+
import com.microsoft.openai.samples.rag.common.ChatGPTConversation;
10+
import com.microsoft.openai.samples.rag.common.ChatGPTUtils;
11+
import com.microsoft.openai.samples.rag.proxy.CognitiveSearchProxy;
12+
import com.microsoft.openai.samples.rag.proxy.OpenAIProxy;
13+
import com.microsoft.semantickernel.Kernel;
14+
import com.microsoft.semantickernel.SKBuilders;
15+
import com.microsoft.semantickernel.orchestration.ContextVariables;
16+
import com.microsoft.semantickernel.orchestration.SKContext;
17+
import org.springframework.beans.factory.annotation.Value;
18+
import org.springframework.stereotype.Component;
19+
20+
import java.io.OutputStream;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Objects;
25+
import java.util.stream.Collectors;
26+
27+
/**
28+
* Use Java Semantic Kernel framework with semantic and native functions chaining. It uses an
29+
* imperative style for AI orchestration through semantic kernel functions chaining.
30+
* InformationFinder.SearchFromConversation native function and RAG.AnswerConversation semantic function are called
31+
* sequentially. Several cognitive search retrieval options are available: Text, Vector, Hybrid.
32+
*/
33+
@Component
34+
public class JavaSemanticKernelChainsChatApproach implements RAGApproach<ChatGPTConversation, RAGResponse> {
35+
private final CognitiveSearchProxy cognitiveSearchProxy;
36+
37+
private final OpenAIProxy openAIProxy;
38+
39+
private final OpenAIAsyncClient openAIAsyncClient;
40+
41+
@Value("${openai.chatgpt.deployment}")
42+
private String gptChatDeploymentModelId;
43+
44+
public JavaSemanticKernelChainsChatApproach(CognitiveSearchProxy cognitiveSearchProxy, OpenAIAsyncClient openAIAsyncClient, OpenAIProxy openAIProxy) {
45+
this.cognitiveSearchProxy = cognitiveSearchProxy;
46+
this.openAIAsyncClient = openAIAsyncClient;
47+
this.openAIProxy = openAIProxy;
48+
}
49+
50+
/**
51+
* @param questionOrConversation
52+
* @param options
53+
* @return
54+
*/
55+
@Override
56+
public RAGResponse run(ChatGPTConversation questionOrConversation, RAGOptions options) {
57+
String question = ChatGPTUtils.getLastUserQuestion(questionOrConversation.getMessages());
58+
String conversation = ChatGPTUtils.formatAsChatML(questionOrConversation.toOpenAIChatMessages());
59+
60+
Kernel semanticKernel = buildSemanticKernel(options);
61+
62+
// STEP 1: Retrieve relevant documents using the current conversation. It reuses the
63+
// CognitiveSearchRetriever appraoch through the CognitiveSearchPlugin native function.
64+
SKContext searchContext =
65+
semanticKernel.runAsync(
66+
conversation,
67+
semanticKernel.getSkill("InformationFinder").getFunction("SearchFromConversation", null)).block();
68+
69+
// STEP 2: Build a SK context with the sources retrieved from the memory store and conversation
70+
ContextVariables variables = SKBuilders.variables()
71+
.withVariable("sources", searchContext.getResult())
72+
.withVariable("conversation", conversation)
73+
.withVariable("suggestions", String.valueOf(options.isSuggestFollowupQuestions()))
74+
.withVariable("input", question)
75+
.build();
76+
77+
/**
78+
* STEP 3: Get a reference of the semantic function [AnswerConversation] of the [RAG] plugin
79+
* (a.k.a. skill) from the SK skills registry and provide it with the pre-built context.
80+
* Triggering Open AI to get a reply.
81+
*/
82+
SKContext reply = semanticKernel.runAsync(variables,
83+
semanticKernel.getSkill("RAG").getFunction("AnswerConversation", null)).block();
84+
85+
return new RAGResponse.Builder()
86+
.prompt("Prompt is managed by Semantic Kernel")
87+
.answer(reply.getResult())
88+
.sources(formSourcesList(searchContext.getResult()))
89+
.sourcesAsText(searchContext.getResult())
90+
.question(question)
91+
.build();
92+
}
93+
94+
@Override
95+
public void runStreaming(
96+
ChatGPTConversation questionOrConversation,
97+
RAGOptions options,
98+
OutputStream outputStream) {
99+
throw new IllegalStateException("Streaming not supported for this approach");
100+
}
101+
102+
private List<ContentSource> formSourcesList(String result) {
103+
if (result == null) {
104+
return Collections.emptyList();
105+
}
106+
return Arrays.stream(result
107+
.split("\n"))
108+
.map(source -> {
109+
String[] split = source.split(":", 2);
110+
if (split.length >= 2) {
111+
var sourceName = split[0].trim();
112+
var sourceContent = split[1].trim();
113+
return new ContentSource(sourceName, sourceContent);
114+
} else {
115+
return null;
116+
}
117+
})
118+
.filter(Objects::nonNull)
119+
.collect(Collectors.toList());
120+
}
121+
122+
/**
123+
* Build semantic kernel context with AnswerConversation semantic function and
124+
* InformationFinder.SearchFromConversation native function. AnswerConversation is imported from
125+
* src/main/resources/semantickernel/Plugins. InformationFinder.SearchFromConversation is implemented in a
126+
* traditional Java class method: CognitiveSearchPlugin.searchFromConversation
127+
*
128+
* @param options
129+
* @return
130+
*/
131+
private Kernel buildSemanticKernel(RAGOptions options) {
132+
Kernel kernel = SKBuilders.kernel()
133+
.withDefaultAIService(SKBuilders.chatCompletion()
134+
.withModelId(gptChatDeploymentModelId)
135+
.withOpenAIClient(this.openAIAsyncClient)
136+
.build())
137+
.build();
138+
139+
kernel.importSkill(
140+
new CognitiveSearchPlugin(this.cognitiveSearchProxy, this.openAIProxy, options),
141+
"InformationFinder");
142+
kernel.importSkillFromResources("semantickernel/Plugins", "RAG", "AnswerConversation", null);
143+
144+
return kernel;
145+
}
146+
147+
}

0 commit comments

Comments
 (0)