Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions application/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'

implementation "com.theokanning.openai-gpt3-java:api:$chatGPTVersion"
implementation "com.theokanning.openai-gpt3-java:service:$chatGPTVersion"
}

application {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public final class Config {
private final String mediaOnlyChannelPattern;
private final String logInfoChannelWebhook;
private final String logErrorChannelWebhook;
private final String openaiToken;

@SuppressWarnings("ConstructorWithTooManyParameters")
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
Expand Down Expand Up @@ -70,7 +71,8 @@ private Config(@JsonProperty(value = "token", required = true) String token,
@JsonProperty(value = "logInfoChannelWebhook",
required = true) String logInfoChannelWebhook,
@JsonProperty(value = "logErrorChannelWebhook",
required = true) String logErrorChannelWebhook) {
required = true) String logErrorChannelWebhook,
@JsonProperty(value = "openaiToken", required = true) String openaiToken) {
this.token = Objects.requireNonNull(token);
this.gistApiKey = Objects.requireNonNull(gistApiKey);
this.databasePath = Objects.requireNonNull(databasePath);
Expand All @@ -93,6 +95,7 @@ private Config(@JsonProperty(value = "token", required = true) String token,
this.blacklistedFileExtension = Objects.requireNonNull(blacklistedFileExtension);
this.logInfoChannelWebhook = Objects.requireNonNull(logInfoChannelWebhook);
this.logErrorChannelWebhook = Objects.requireNonNull(logErrorChannelWebhook);
this.openaiToken = openaiToken;
}

/**
Expand Down Expand Up @@ -304,4 +307,13 @@ public String getLogInfoChannelWebhook() {
public String getLogErrorChannelWebhook() {
return logErrorChannelWebhook;
}

/**
* The OpenAI token needed for communicating with OpenAI ChatGPT.
*
* @return the OpenAI API Token
*/
public String getOpenaiToken() {
return openaiToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.togetherjava.tjbot.db.Database;
import org.togetherjava.tjbot.features.basic.*;
import org.togetherjava.tjbot.features.bookmarks.*;
import org.togetherjava.tjbot.features.chaptgpt.ChatGPTCommand;
import org.togetherjava.tjbot.features.code.CodeMessageAutoDetection;
import org.togetherjava.tjbot.features.code.CodeMessageHandler;
import org.togetherjava.tjbot.features.code.CodeMessageManualDetection;
Expand Down Expand Up @@ -139,7 +140,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new HelpThreadCommand(config, helpSystemHelper));
features.add(new ReportCommand(config));
features.add(new BookmarksCommand(bookmarksSystem));

features.add(new ChatGPTCommand(config));
return features;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.togetherjava.tjbot.features.chaptgpt;

import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;

import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.features.CommandVisibility;
import org.togetherjava.tjbot.features.SlashCommandAdapter;

/**
* The implemented command is {@code /chatgpt}, upon which the bot will respond with a response
* generated by ChatGPT.
*/
public final class ChatGPTCommand extends SlashCommandAdapter {
private static final String PROMPT = "prompt";
private final ChatGPTService chatGPTService;

/**
* Creates an instance of the chatgpt command.
*
* @param config Config - Required to get token needed for ChatGPT API
*/
public ChatGPTCommand(Config config) {
super("chatgpt", "User enters a prompt and then gets a response generated by ChatGPT",
CommandVisibility.GUILD);

chatGPTService = new ChatGPTService(config);

getData().addOption(OptionType.STRING, PROMPT, "Prompt sent to ChatGPT", true);
}

@Override
public void onSlashCommand(SlashCommandInteractionEvent event) {
event.deferReply().queue();
String response = chatGPTService.ask(event.getOption(PROMPT).getAsString());
event.getHook().sendMessage(response).queue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.togetherjava.tjbot.features.chaptgpt;

import com.theokanning.openai.OpenAiHttpException;
import com.theokanning.openai.completion.chat.ChatCompletionRequest;
import com.theokanning.openai.completion.chat.ChatMessage;
import com.theokanning.openai.completion.chat.ChatMessageRole;
import com.theokanning.openai.service.OpenAiService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.togetherjava.tjbot.config.Config;

import java.time.Duration;
import java.util.List;
import java.util.Objects;

/**
* Service used to communicate to OpenAI API to generate responses.
*/
public class ChatGPTService {
private static final Logger logger = LoggerFactory.getLogger(ChatGPTService.class);
private static final long TIMEOUT_DURATION = 10000L; // In milliseconds
private static final int MAX_TOKENS = 3000;
private final OpenAiService openAiService;

/**
* Creates instance of ChatGPTService
*
* @param config Config - needed for token to OpenAI API.
*/
public ChatGPTService(Config config) {
String token = config.getOpenaiToken();
openAiService = new OpenAiService(token, Duration.ofMillis(TIMEOUT_DURATION));
}

/**
* Prompt ChatGPT with a question and receive a response.
*
* @param question String - The question being asked of ChatGPT. Max is 4000 tokens.
* @see <a href="https://platform.openai.com/docs/guides/chat/managing-tokens">ChatGPT
* Tokens</a>.
* @return response from ChatGPT as a String.
* @throws OpenAiHttpException - Thrown when an error occurs with the API such as a timeout or
* token error such as it being expired or revoked.
*/
public String ask(String question) {
try {
ChatMessage chatMessage =
new ChatMessage(ChatMessageRole.USER.value(), Objects.requireNonNull(question));
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model("gpt-3.5-turbo")
.messages(List.of(chatMessage))
.frequencyPenalty(0.5)
.temperature(0.7)
.maxTokens(MAX_TOKENS)
.n(1)
.build();
return openAiService.createChatCompletion(chatCompletionRequest)
.getChoices()
.get(0)
.getMessage()
.getContent();
} catch (OpenAiHttpException openAiHttpException) {
logger.error(String.format(
"There was an error using the OpenAI API: %s Code: %s Type: %s Status Code:%s",
openAiHttpException.getMessage(), openAiHttpException.code,
openAiHttpException.type, openAiHttpException.statusCode));
}
return "An error has occurred while trying to communication with ChatGPT. Please try again later";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* This package contains the functionality to connect with ChatGPT via API calls.
*/
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package org.togetherjava.tjbot.features.chaptgpt;

import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ version '1.0-SNAPSHOT'
ext {
jooqVersion = '3.18.0'
jacksonVersion = '2.14.0'
chatGPTVersion = '0.11.1'
}

// Skips sonarlint during the build, useful for testing purposes.
Expand Down