Skip to content

Commit 39b9b09

Browse files
committed
Refactor code to avoid duplication
1 parent e48cff3 commit 39b9b09

File tree

9 files changed

+105
-168
lines changed

9 files changed

+105
-168
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,90 @@
11
package com.microsoft.openai.samples.rag.approaches;
22

3+
import com.azure.ai.openai.models.CompletionsOptions;
4+
import com.azure.core.util.Context;
5+
import com.azure.search.documents.SearchDocument;
6+
import com.azure.search.documents.models.*;
7+
import com.azure.search.documents.util.SearchPagedIterable;
8+
import com.microsoft.openai.samples.rag.proxy.CognitiveSearchProxy;
9+
10+
import java.util.ArrayList;
11+
import java.util.HashMap;
12+
import java.util.List;
13+
import java.util.Optional;
14+
315
public interface RAGApproach<I, O> {
416

517
O run(I questionOrConversation, RAGOptions options);
618

19+
CognitiveSearchProxy getCognitiveSearchProxy();
20+
21+
default CompletionsOptions fillCommonCompletionsOptions(CompletionsOptions completionsOptions) {
22+
completionsOptions.setMaxTokens(1024);
23+
completionsOptions.setTemperature(0.3);
24+
completionsOptions.setLogitBias(new HashMap<>());
25+
completionsOptions.setEcho(false);
26+
completionsOptions.setN(1);
27+
completionsOptions.setStream(false);
28+
completionsOptions.setUser("search-openai-demo-java");
29+
completionsOptions.setPresencePenalty(0.0);
30+
completionsOptions.setFrequencyPenalty(0.0);
31+
completionsOptions.setBestOf(1);
32+
return completionsOptions;
33+
}
34+
35+
default SearchOptions buildSearchOptions(RAGOptions options) {
36+
var searchOptions = new SearchOptions();
37+
38+
Optional.ofNullable(options.getTop()).ifPresentOrElse(
39+
searchOptions::setTop,
40+
() -> searchOptions.setTop(3));
41+
Optional.ofNullable(options.getExcludeCategory())
42+
.ifPresentOrElse(
43+
value -> searchOptions.setFilter("category ne '%s'".formatted(value.replace("'", "''"))),
44+
() -> searchOptions.setFilter(null));
45+
46+
Optional.ofNullable(options.isSemanticRanker()).ifPresent(isSemanticRanker -> {
47+
if (isSemanticRanker) {
48+
searchOptions.setQueryType(QueryType.SEMANTIC);
49+
searchOptions.setQueryLanguage(QueryLanguage.EN_US);
50+
searchOptions.setSpeller(QuerySpellerType.LEXICON);
51+
searchOptions.setSemanticConfigurationName("default");
52+
searchOptions.setQueryCaption(QueryCaptionType.EXTRACTIVE);
53+
searchOptions.setQueryCaptionHighlightEnabled(false);
54+
}
55+
});
56+
57+
return searchOptions;
58+
}
59+
60+
default SearchPagedIterable getCognitiveSearchResults(String question, RAGOptions options) {
61+
return getCognitiveSearchProxy().search(question, buildSearchOptions(options), Context.NONE);
62+
}
63+
64+
default List<ContentSource> buildSourcesFromSearchResults(RAGOptions options, SearchPagedIterable searchResults) {
65+
List<ContentSource> sources = new ArrayList<>();
66+
67+
searchResults.iterator().forEachRemaining(result ->
68+
{
69+
var searchDocument = result.getDocument(SearchDocument.class);
70+
71+
/*
72+
If captions is enabled the content source is taken from the captions generated by the semantic ranker.
73+
Captions are appended sequentially and separated by a dot.
74+
*/
75+
if(options.isSemanticCaptions()) {
76+
StringBuilder sourcesContentBuffer = new StringBuilder();
77+
78+
result.getCaptions().forEach(caption -> sourcesContentBuffer.append(caption.getText()).append("."));
79+
80+
sources.add(new ContentSource((String)searchDocument.get("sourcepage"), sourcesContentBuffer.toString()));
81+
} else {
82+
//If captions is disabled the content source is taken from the cognitive search index field "content"
83+
sources.add(new ContentSource((String) searchDocument.get("sourcepage"), (String) searchDocument.get("content")));
84+
}
85+
});
86+
87+
return sources;
88+
}
89+
790
}

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

Lines changed: 7 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
import com.azure.ai.openai.models.Completions;
44
import com.azure.ai.openai.models.CompletionsOptions;
5-
import com.azure.core.util.Context;
6-
import com.azure.search.documents.SearchDocument;
7-
import com.azure.search.documents.models.*;
85
import com.azure.search.documents.util.SearchPagedIterable;
96
import com.microsoft.openai.samples.rag.approaches.ContentSource;
107
import com.microsoft.openai.samples.rag.approaches.RAGApproach;
@@ -16,7 +13,9 @@
1613
import org.slf4j.LoggerFactory;
1714
import org.springframework.stereotype.Component;
1815

19-
import java.util.*;
16+
import java.util.ArrayList;
17+
import java.util.Collections;
18+
import java.util.List;
2019

2120
/**
2221
* Simple retrieve-then-read implementation, using the Cognitive Search and OpenAI APIs directly. It first retrieves
@@ -70,72 +69,14 @@ public RAGResponse run(String questionOrConversation, RAGOptions options) {
7069

7170
private CompletionsOptions buildCompletionsOptions(SemanticSearchAskPrompt retrieveThenReadPrompt) {
7271
CompletionsOptions completionsOptions = new CompletionsOptions(new ArrayList<>(Collections.singletonList(retrieveThenReadPrompt.getFormattedPrompt())));
73-
7472
// Due to a potential bug when using JVM 17 and java openai SDK 1.0.0-beta.2, we need to provide default for all properties to avoid 404 bad Request on the server
75-
completionsOptions.setMaxTokens(1024);
76-
completionsOptions.setTemperature(0.3);
7773
completionsOptions.setStop(List.of("\n"));
78-
completionsOptions.setLogitBias(new HashMap<>());
79-
completionsOptions.setEcho(false);
80-
completionsOptions.setN(1);
81-
completionsOptions.setStream(false);
82-
completionsOptions.setUser( "search-openai-demo-java");
83-
completionsOptions.setPresencePenalty(0.0);
84-
completionsOptions.setFrequencyPenalty(0.0);
85-
completionsOptions.setBestOf(1);
86-
87-
88-
return completionsOptions;
74+
return fillCommonCompletionsOptions(completionsOptions);
8975
}
9076

91-
private SearchPagedIterable getCognitiveSearchResults(String question, RAGOptions options) {
92-
var searchOptions = new SearchOptions();
93-
94-
Optional.ofNullable(options.getTop()).ifPresentOrElse(
95-
searchOptions::setTop,
96-
() -> searchOptions.setTop(3));
97-
Optional.ofNullable(options.getExcludeCategory())
98-
.ifPresentOrElse(
99-
value -> searchOptions.setFilter("category ne '%s'".formatted(value.replace("'", "''"))),
100-
() -> searchOptions.setFilter(null));
101-
102-
Optional.ofNullable(options.isSemanticRanker()).ifPresent(isSemanticRanker -> {
103-
if(isSemanticRanker) {
104-
searchOptions.setQueryType(QueryType.SEMANTIC);
105-
searchOptions.setQueryLanguage(QueryLanguage.EN_US);
106-
searchOptions.setSpeller(QuerySpellerType.LEXICON);
107-
searchOptions.setSemanticConfigurationName("default");
108-
searchOptions.setQueryCaption(QueryCaptionType.EXTRACTIVE);
109-
searchOptions.setQueryCaptionHighlightEnabled(false);
110-
}
111-
});
112-
113-
return this.cognitiveSearchProxy.search(question, searchOptions, Context.NONE);
114-
}
115-
116-
private List<ContentSource> buildSourcesFromSearchResults(RAGOptions options, SearchPagedIterable searchResults) {
117-
List<ContentSource> sources = new ArrayList<>();
118-
119-
searchResults.iterator().forEachRemaining(result ->
120-
{
121-
var searchDocument = result.getDocument(SearchDocument.class);
122-
123-
/*
124-
If captions is enabled the content source is taken from the captions generated by the semantic ranker.
125-
Captions are appended sequentially and separated by a dot.
126-
*/
127-
if(options.isSemanticCaptions()) {
128-
StringBuilder sourcesContentBuffer = new StringBuilder();
129-
130-
result.getCaptions().forEach(caption -> sourcesContentBuffer.append(caption.getText()).append("."));
131-
132-
sources.add(new ContentSource((String)searchDocument.get("sourcepage"), sourcesContentBuffer.toString()));
133-
} else {
134-
//If captions is disabled the content source is taken from the cognitive search index field "content"
135-
sources.add(new ContentSource((String) searchDocument.get("sourcepage"), (String) searchDocument.get("content")));
136-
}
137-
});
138-
return sources;
77+
@Override
78+
public CognitiveSearchProxy getCognitiveSearchProxy() {
79+
return this.cognitiveSearchProxy;
13980
}
14081

14182
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public String getFormattedPrompt() {
5757
if (this.question == null || this.question.isEmpty())
5858
throw new IllegalStateException("question cannot be null or empty. Please use setQuestion() before calling getFormattedPrompt()");
5959

60-
StringBuffer sourcesText = new StringBuffer();
60+
StringBuilder sourcesText = new StringBuilder();
6161
sources.iterator().forEachRemaining(source -> sourcesText.append(source).append("\n"));
6262

6363
return PROMPT_TEMPLATE.formatted(question, sourcesText.toString());

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

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.microsoft.openai.samples.rag.ask.approaches.semantickernel;
22

33
import com.azure.ai.openai.OpenAIAsyncClient;
4-
import com.azure.search.documents.models.*;
54
import com.microsoft.openai.samples.rag.approaches.RAGApproach;
65
import com.microsoft.openai.samples.rag.approaches.RAGOptions;
76
import com.microsoft.openai.samples.rag.approaches.RAGResponse;
@@ -20,7 +19,6 @@
2019
import java.io.InputStream;
2120
import java.nio.charset.StandardCharsets;
2221
import java.util.Objects;
23-
import java.util.Optional;
2422
import java.util.Set;
2523

2624
/**
@@ -34,12 +32,11 @@ public class ReadRetrieveReadApproach implements RAGApproach<String, RAGResponse
3432
Take the input as a question and answer it finding any information needed
3533
""";
3634
private final CognitiveSearchProxy cognitiveSearchProxy;
35+
private final OpenAIAsyncClient openAIAsyncClient;
3736
// This will be injected as prototype bean
3837
@Value("${openai.gpt.deployment}")
3938
private String gptDeploymentModelId;
4039

41-
OpenAIAsyncClient openAIAsyncClient;
42-
4340
public ReadRetrieveReadApproach(CognitiveSearchProxy cognitiveSearchProxy, OpenAIAsyncClient openAIAsyncClient) {
4441
this.cognitiveSearchProxy = cognitiveSearchProxy;
4542
this.openAIAsyncClient = openAIAsyncClient;
@@ -106,28 +103,9 @@ private Kernel buildSemanticKernel( RAGOptions options) {
106103
return kernel;
107104
}
108105

109-
private SearchOptions buildSearchOptions(RAGOptions options){
110-
var searchOptions = new SearchOptions();
111-
112-
Optional.ofNullable(options.getTop()).ifPresentOrElse(
113-
searchOptions::setTop,
114-
() -> searchOptions.setTop(3));
115-
Optional.ofNullable(options.getExcludeCategory())
116-
.ifPresentOrElse(
117-
value -> searchOptions.setFilter("category ne '%s'".formatted(value.replace("'", "''"))),
118-
() -> searchOptions.setFilter(null));
119-
120-
Optional.ofNullable(options.isSemanticRanker()).ifPresent(isSemanticRanker -> {
121-
if(isSemanticRanker) {
122-
searchOptions.setQueryType(QueryType.SEMANTIC);
123-
searchOptions.setQueryLanguage(QueryLanguage.EN_US);
124-
searchOptions.setSpeller(QuerySpellerType.LEXICON);
125-
searchOptions.setSemanticConfigurationName("default");
126-
searchOptions.setQueryCaption(QueryCaptionType.EXTRACTIVE);
127-
searchOptions.setQueryCaptionHighlightEnabled(false);
128-
}
129-
});
130-
return searchOptions;
106+
@Override
107+
public CognitiveSearchProxy getCognitiveSearchProxy() {
108+
return this.cognitiveSearchProxy;
131109
}
132110

133111
}

app/backend/src/main/java/com/microsoft/openai/samples/rag/chat/approaches/ChatGPTConversation.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.azure.ai.openai.models.ChatMessage;
44

55
import java.util.List;
6-
import java.util.stream.Collectors;
76

87
public class ChatGPTConversation {
98

@@ -22,7 +21,7 @@ public List<ChatMessage> toOpenAIChatMessages() {
2221
chatMessage.setContent(message.content());
2322
return chatMessage;
2423
})
25-
.collect(Collectors.toList());
24+
.toList();
2625
}
2726

2827
public List<ChatGPTMessage> getMessages() {

app/backend/src/main/java/com/microsoft/openai/samples/rag/chat/approaches/ChatReadRetrieveReadApproach.java

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package com.microsoft.openai.samples.rag.chat.approaches;
22

33
import com.azure.ai.openai.models.*;
4-
import com.azure.core.util.Context;
5-
import com.azure.search.documents.SearchDocument;
6-
import com.azure.search.documents.models.*;
74
import com.azure.search.documents.util.SearchPagedIterable;
85
import com.microsoft.openai.samples.rag.approaches.ContentSource;
96
import com.microsoft.openai.samples.rag.approaches.RAGApproach;
@@ -88,21 +85,9 @@ private String formatChat(SemanticSearchChat semanticSearchChat) {
8885

8986
private CompletionsOptions buildCompletionsOptions(CognitiveSearchQueryPrompt retrieveThenReadPrompt) {
9087
CompletionsOptions completionsOptions = new CompletionsOptions(new ArrayList<>(Collections.singletonList(retrieveThenReadPrompt.getFormattedPrompt())));
91-
9288
// Due to a potential bug in using JVM 17 and java open SDK 1.0.0-beta.2, we need to provide default for all properties to avoid 404 bad Request on the server
93-
completionsOptions.setMaxTokens(1024);
94-
completionsOptions.setTemperature(0.3);
9589
completionsOptions.setStop(new ArrayList<>( Arrays.asList("<|im_end|>","<|im_start|>")));
96-
completionsOptions.setLogitBias(new HashMap<>());
97-
completionsOptions.setEcho(false);
98-
completionsOptions.setN(1);
99-
completionsOptions.setStream(false);
100-
completionsOptions.setUser( "search-openai-demo-java");
101-
completionsOptions.setPresencePenalty(0.0);
102-
completionsOptions.setFrequencyPenalty(0.0);
103-
completionsOptions.setBestOf(1);
104-
105-
return completionsOptions;
90+
return fillCommonCompletionsOptions(completionsOptions);
10691
}
10792

10893
private ChatCompletionsOptions buildChatCompletionsOptions(SemanticSearchChat semanticSearchChat) {
@@ -115,61 +100,16 @@ private ChatCompletionsOptions buildChatCompletionsOptions(SemanticSearchChat se
115100
completionsOptions.setLogitBias(new HashMap<>());
116101
completionsOptions.setN(1);
117102
completionsOptions.setStream(false);
118-
completionsOptions.setUser( "search-openai-demo-java");
103+
completionsOptions.setUser("search-openai-demo-java");
119104
completionsOptions.setPresencePenalty(0.0);
120105
completionsOptions.setFrequencyPenalty(0.0);
121106

122-
123107
return completionsOptions;
124108
}
125109

126-
private SearchPagedIterable getCognitiveSearchResults(String question, RAGOptions options) {
127-
var searchOptions = new SearchOptions();
128-
129-
Optional.ofNullable(options.getTop()).ifPresentOrElse(
130-
searchOptions::setTop,
131-
() -> searchOptions.setTop(3));
132-
Optional.ofNullable(options.getExcludeCategory())
133-
.ifPresentOrElse(
134-
value -> searchOptions.setFilter("category ne '%s'".formatted(value.replace("'", "''"))),
135-
() -> searchOptions.setFilter(null));
136-
137-
Optional.ofNullable(options.isSemanticRanker()).ifPresent(isSemanticRanker -> {
138-
if(isSemanticRanker) {
139-
searchOptions.setQueryType(QueryType.SEMANTIC);
140-
searchOptions.setQueryLanguage(QueryLanguage.EN_US);
141-
searchOptions.setSpeller(QuerySpellerType.LEXICON);
142-
searchOptions.setSemanticConfigurationName("default");
143-
searchOptions.setQueryCaption(QueryCaptionType.EXTRACTIVE);
144-
searchOptions.setQueryCaptionHighlightEnabled(false);
145-
}
146-
});
147-
148-
return this.cognitiveSearchProxy.search(question, searchOptions, Context.NONE);
110+
@Override
111+
public CognitiveSearchProxy getCognitiveSearchProxy() {
112+
return this.cognitiveSearchProxy;
149113
}
150114

151-
private List<ContentSource> buildSourcesFromSearchResults(RAGOptions options, SearchPagedIterable searchResults) {
152-
List<ContentSource> sources = new ArrayList<>();
153-
154-
searchResults.iterator().forEachRemaining(result ->
155-
{
156-
var searchDocument = result.getDocument(SearchDocument.class);
157-
158-
/*
159-
If captions is enabled the content source is taken from the captions generated by the semantic ranker.
160-
Captions are appended sequentially and separated by a dot.
161-
*/
162-
if(options.isSemanticCaptions()) {
163-
StringBuilder sourcesContentBuffer = new StringBuilder();
164-
165-
result.getCaptions().forEach(caption -> sourcesContentBuffer.append(caption.getText()).append("."));
166-
167-
sources.add(new ContentSource((String)searchDocument.get("sourcepage"), sourcesContentBuffer.toString()));
168-
} else {
169-
//If captions is disabled the content source is taken from the cognitive search index field "content"
170-
sources.add(new ContentSource((String) searchDocument.get("sourcepage"), (String) searchDocument.get("content")));
171-
}
172-
});
173-
return sources;
174-
}
175115
}

app/backend/src/main/java/com/microsoft/openai/samples/rag/chat/approaches/SemanticSearchChat.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class SemanticSearchChat {
1717
private Boolean replacePrompt = false;
1818

1919
private static final String FOLLOW_UP_QUESTIONS_TEMPLATE = """
20-
Generate three very brief follow-up questions that the user would likely ask next about their healthcare plan and employee handbook.
20+
Generate three very brief follow-up questions that the user would likely ask next about their healthcare plan and employee handbook.
2121
Use double angle brackets to reference the questions, e.g. <<Are there exclusions for prescriptions?>>.
2222
Try not to repeat questions that have already been asked.
2323
Only generate questions and do not generate any text before or after the questions, such as 'Next Questions'

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@
2424
@Component
2525
public class OpenAIProxy {
2626

27-
private OpenAIClient client;
28-
27+
private final OpenAIClient client;
2928
@Value("${openai.gpt.deployment}")
3029
private String gptDeploymentModelId;
31-
3230
@Value("${openai.chatgpt.deployment}")
3331
private String gptChatDeploymentModelId;
3432

0 commit comments

Comments
 (0)