Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.togetherjava.tjbot.features.tophelper.TopHelpersPurgeMessagesRoutine;
import org.togetherjava.tjbot.features.tophelper.TopHelpersService;
import org.togetherjava.tjbot.features.voicechat.DynamicVoiceChat;
import org.togetherjava.tjbot.features.xkcd.XkcdCommand;

import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -213,6 +214,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new JShellCommand(jshellEval));
features.add(new MessageCommand());
features.add(new RewriteCommand(chatGptService));
features.add(new XkcdCommand(chatGptService));

FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
return blacklist.filterStream(features.stream(), Object::getClass).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@

import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.files.FileCreateParams;
import com.openai.models.files.FileObject;
import com.openai.models.files.FilePurpose;
import com.openai.models.responses.Response;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseOutputText;
import com.openai.models.responses.Tool;
import com.openai.models.vectorstores.VectorStore;
import com.openai.models.vectorstores.VectorStoreCreateParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.togetherjava.tjbot.config.Config;

import javax.annotation.Nullable;

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -95,7 +104,111 @@ public Optional<String> askRaw(String inputPrompt, ChatGptModel chatModel) {
* @param chatModel The AI model to use for this request.
* @return response from ChatGPT as a String.
*/
private Optional<String> sendPrompt(String prompt, ChatGptModel chatModel) {
public Optional<String> sendPrompt(String prompt, ChatGptModel chatModel) {
return sendPrompt(prompt, chatModel, List.of());
}

/**
* Lists all files uploaded to OpenAI and returns the ID of the first file matching the given
* filename (case-insensitive).
*
* @param filePath The filename to search for among uploaded files.
* @return An Optional containing the file ID if found, or empty if no matching file exists.
*/
public Optional<String> getUploadedFileId(String filePath) {
return openAIClient.files()
.list()
.items()
.stream()
.filter(fileObj -> fileObj.filename().equalsIgnoreCase(filePath))
.map(FileObject::id)
.findFirst();
}

/**
* Uploads the specified file to OpenAI if it exists locally and hasn't been uploaded before.
*
* @param filePath The local path to the file to upload.
* @param purpose The OpenAI file purpose (e.g., {@link FilePurpose#ASSISTANTS})
* @return an Optional containing the uploaded file ID, or empty if:
* <ul>
* <li>service is disabled</li>
* <li>file doesn't exist locally</li>
* <li>file with matching name already uploaded</li>
* </ul>
*/
public Optional<String> uploadFileIfNotExists(Path filePath, FilePurpose purpose) {
if (isDisabled) {
logger.warn("ChatGPT file upload attempted but service is disabled");
return Optional.empty();
}

if (!Files.notExists(filePath)) {
logger.warn("Could not find file '{}' to upload to ChatGPT", filePath);
return Optional.empty();
}

if (getUploadedFileId(filePath.toString()).isPresent()) {
logger.warn("File '{}' already exists.", filePath);
return Optional.empty();
}

FileCreateParams fileCreateParams =
FileCreateParams.builder().file(filePath).purpose(purpose).build();

FileObject fileObj = openAIClient.files().create(fileCreateParams);
String id = fileObj.id();

logger.info("Uploaded file to ChatGPT with ID {}", id);
return Optional.of(id);
}

/**
* Creates a new vector store with the given file ID if none exists or returns the ID of the
* existing vector store with that name.
* <p>
* You can use this for RAG purposes, it is an effective way to give ChatGPT extra information
* from what it has been trained.
Comment on lines +167 to +171
Copy link
Member

@Zabuzard Zabuzard Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is RAG, what is a vector store? these terms arent generally known to people. maybe drop a sentence or two somewhere to elaborate

Copy link
Member Author

@christolis christolis Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I expect people to google terms that they do not understand, I am happy to add a concise explanation for these two terms in the JavaDoc.

*
* @param fileId The ID of the file to include in the new vector store.
* @return The vector store ID (existing or newly created).
*/
public String createOrGetVectorStore(String fileId, String vectorStoreName) {
List<VectorStore> vectorStores = openAIClient.vectorStores()
.list()
.items()
.stream()
.filter(vectorStore -> vectorStore.name().equalsIgnoreCase(vectorStoreName))
.toList();
Optional<VectorStore> vectorStore = vectorStores.stream().findFirst();

if (vectorStore.isPresent()) {
String vectorStoreId = vectorStore.get().id();
logger.debug("Got vector store {}", vectorStoreId);
return vectorStoreId;
}

VectorStoreCreateParams params = VectorStoreCreateParams.builder()
.name(vectorStoreName)
.fileIds(List.of(fileId))
.build();

VectorStore newVectorStore = openAIClient.vectorStores().create(params);
String vectorStoreId = newVectorStore.id();

logger.debug("Created vector store {}", vectorStoreId);
return vectorStoreId;
}

/**
* Sends a prompt to the ChatGPT API and returns the response.
*
* @param prompt The prompt to send to ChatGPT.
* @param chatModel The AI model to use for this request.
* @param tools The list of OpenAPI tools to enhance the prompt's answers.
* @return response from ChatGPT as a String.
*/
public Optional<String> sendPrompt(String prompt, ChatGptModel chatModel, List<Tool> tools) {
if (isDisabled) {
logger.warn("ChatGPT request attempted but service is disabled");
return Optional.empty();
Expand All @@ -107,6 +220,7 @@ private Optional<String> sendPrompt(String prompt, ChatGptModel chatModel) {
ResponseCreateParams params = ResponseCreateParams.builder()
.model(chatModel.toChatModel())
.input(prompt)
.tools(tools)
.maxOutputTokens(MAX_TOKENS)
.build();

Expand Down
Loading
Loading