Skip to content

Commit aaf564a

Browse files
committed
xkcd: create XKCD comic generator
Use RAG with ChatGPT to store all XKCD comics and display the correct one based on the chat history. There is also the possibility to specify your own XKCD if you wish. Signed-off-by: Chris Sdogkos <work@chris-sdogkos.com>
1 parent 59de5ee commit aaf564a

File tree

6 files changed

+553
-1
lines changed

6 files changed

+553
-1
lines changed

application/src/main/java/org/togetherjava/tjbot/features/Features.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import org.togetherjava.tjbot.features.tophelper.TopHelpersPurgeMessagesRoutine;
8282
import org.togetherjava.tjbot.features.tophelper.TopHelpersService;
8383
import org.togetherjava.tjbot.features.voicechat.DynamicVoiceChat;
84+
import org.togetherjava.tjbot.features.xkcd.XkcdCommand;
8485

8586
import java.util.ArrayList;
8687
import java.util.Collection;
@@ -213,6 +214,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
213214
features.add(new JShellCommand(jshellEval));
214215
features.add(new MessageCommand());
215216
features.add(new RewriteCommand(chatGptService));
217+
features.add(new XkcdCommand(chatGptService));
216218

217219
FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
218220
return blacklist.filterStream(features.stream(), Object::getClass).toList();

application/src/main/java/org/togetherjava/tjbot/features/chatgpt/ChatGptService.java

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,27 @@
22

33
import com.openai.client.OpenAIClient;
44
import com.openai.client.okhttp.OpenAIOkHttpClient;
5+
import com.openai.models.files.FileCreateParams;
6+
import com.openai.models.files.FileObject;
7+
import com.openai.models.files.FilePurpose;
58
import com.openai.models.responses.Response;
69
import com.openai.models.responses.ResponseCreateParams;
710
import com.openai.models.responses.ResponseOutputText;
11+
import com.openai.models.responses.Tool;
12+
import com.openai.models.responses.WebSearchTool;
13+
import com.openai.models.vectorstores.VectorStore;
14+
import com.openai.models.vectorstores.VectorStoreCreateParams;
815
import org.slf4j.Logger;
916
import org.slf4j.LoggerFactory;
1017

1118
import org.togetherjava.tjbot.config.Config;
1219

1320
import javax.annotation.Nullable;
1421

22+
import java.io.File;
23+
import java.nio.file.Path;
1524
import java.time.Duration;
25+
import java.util.List;
1626
import java.util.Optional;
1727
import java.util.stream.Collectors;
1828

@@ -23,6 +33,8 @@ public class ChatGptService {
2333
private static final Logger logger = LoggerFactory.getLogger(ChatGptService.class);
2434
private static final Duration TIMEOUT = Duration.ofSeconds(90);
2535

36+
private static final String VECTOR_STORE_XKCD = "xkcd-comics";
37+
2638
/** The maximum number of tokens allowed for the generated answer. */
2739
private static final int MAX_TOKENS = 1000;
2840

@@ -88,14 +100,103 @@ public Optional<String> askRaw(String inputPrompt, ChatGptModel chatModel) {
88100
return sendPrompt(inputPrompt, chatModel);
89101
}
90102

103+
/**
104+
* Sends a prompt to the ChatGPT API with web capabilities and returns the response.
105+
*
106+
* @param prompt The prompt to send to ChatGPT.
107+
* @param chatModel The AI model to use for this request.
108+
* @return response from ChatGPT as a String.
109+
*/
110+
public Optional<String> sendWebPrompt(String prompt, ChatGptModel chatModel) {
111+
Tool webSearchTool = Tool
112+
.ofWebSearch(WebSearchTool.builder().type(WebSearchTool.Type.WEB_SEARCH).build());
113+
114+
return sendPrompt(prompt, chatModel, List.of(webSearchTool));
115+
}
116+
117+
/**
118+
* Sends a prompt to the ChatGPT API and returns the response.
119+
*
120+
* @param prompt The prompt to send to ChatGPT.
121+
* @param chatModel The AI model to use for this request.
122+
* @return response from ChatGPT as a String.
123+
*/
124+
public Optional<String> sendPrompt(String prompt, ChatGptModel chatModel) {
125+
return sendPrompt(prompt, chatModel, List.of());
126+
}
127+
128+
public Optional<String> getUploadedFileId(String filePath) {
129+
return openAIClient.files()
130+
.list()
131+
.items()
132+
.stream()
133+
.filter(fileObj -> fileObj.filename().equalsIgnoreCase(filePath))
134+
.map(FileObject::id)
135+
.findFirst();
136+
}
137+
138+
public Optional<String> uploadFileIfNotExists(Path filePath, FilePurpose purpose) {
139+
if (isDisabled) {
140+
logger.warn("ChatGPT file upload attempted but service is disabled");
141+
return Optional.empty();
142+
}
143+
144+
File file = filePath.toFile();
145+
if (!file.exists()) {
146+
logger.warn("Could not find file '{}' to upload to ChatGPT", filePath);
147+
return Optional.empty();
148+
}
149+
150+
if (getUploadedFileId(filePath.toString()).isPresent()) {
151+
logger.warn("File '{}' already exists.", filePath);
152+
return Optional.empty();
153+
}
154+
155+
FileCreateParams fileCreateParams =
156+
FileCreateParams.builder().file(filePath).purpose(purpose).build();
157+
158+
FileObject fileObj = openAIClient.files().create(fileCreateParams);
159+
String id = fileObj.id();
160+
161+
logger.info("Uploaded file to ChatGPT with ID {}", id);
162+
return Optional.of(id);
163+
}
164+
165+
public String createOrGetXkcdVectorStore(String fileId) {
166+
List<VectorStore> vectorStores = openAIClient.vectorStores()
167+
.list()
168+
.items()
169+
.stream()
170+
.filter(vectorStore -> vectorStore.name().equalsIgnoreCase(VECTOR_STORE_XKCD))
171+
.toList();
172+
Optional<VectorStore> vectorStore = vectorStores.stream().findFirst();
173+
174+
if (vectorStore.isPresent()) {
175+
return vectorStore.get().id();
176+
}
177+
178+
VectorStoreCreateParams params = VectorStoreCreateParams.builder()
179+
.name(VECTOR_STORE_XKCD)
180+
.fileIds(List.of(fileId))
181+
.build();
182+
183+
VectorStore newVectorStore = openAIClient.vectorStores().create(params);
184+
String vectorStoreId = newVectorStore.id();
185+
186+
logger.info("Created vector store {} with XKCD data", vectorStoreId);
187+
188+
return vectorStoreId;
189+
}
190+
91191
/**
92192
* Sends a prompt to the ChatGPT API and returns the response.
93193
*
94194
* @param prompt The prompt to send to ChatGPT.
95195
* @param chatModel The AI model to use for this request.
196+
* @param tools The list of OpenAPI tools to enhance the prompt's answers.
96197
* @return response from ChatGPT as a String.
97198
*/
98-
private Optional<String> sendPrompt(String prompt, ChatGptModel chatModel) {
199+
public Optional<String> sendPrompt(String prompt, ChatGptModel chatModel, List<Tool> tools) {
99200
if (isDisabled) {
100201
logger.warn("ChatGPT request attempted but service is disabled");
101202
return Optional.empty();
@@ -107,6 +208,7 @@ private Optional<String> sendPrompt(String prompt, ChatGptModel chatModel) {
107208
ResponseCreateParams params = ResponseCreateParams.builder()
108209
.model(chatModel.toChatModel())
109210
.input(prompt)
211+
.tools(tools)
110212
.maxOutputTokens(MAX_TOKENS)
111213
.build();
112214

0 commit comments

Comments
 (0)