Skip to content
Merged
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
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
1 change: 1 addition & 0 deletions application/config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,5 @@
],
"logInfoChannelWebhook": "<put_your_webhook_here>",
"logErrorChannelWebhook": "<put_your_webhook_here>"
"openaiApiKey": "<check pins in #tjbot_discussion for the key>"
}
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 openaiApiKey;

@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 = "openaiApiKey", required = true) String openaiApiKey) {
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.openaiApiKey = Objects.requireNonNull(openaiApiKey);
}

/**
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 getOpenaiApiKey() {
return openaiApiKey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
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.chaptgpt.ChatGptService;
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 @@ -72,6 +74,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
ScamHistoryStore scamHistoryStore = new ScamHistoryStore(database);
HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database);
CodeMessageHandler codeMessageHandler = new CodeMessageHandler();
ChatGptService chatGptService = new ChatGptService(config);

// NOTE The system can add special system relevant commands also by itself,
// hence this list may not necessarily represent the full list of all commands actually
Expand Down Expand Up @@ -139,7 +142,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(chatGptService));
return features;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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.features.CommandVisibility;
import org.togetherjava.tjbot.features.SlashCommandAdapter;

/**
* The implemented command is {@code /chatgpt}, which allows users to ask ChatGPT a question, upon
* which it will respond with an AI generated answer.
*/
public final class ChatGptCommand extends SlashCommandAdapter {
private static final String QUESTION_OPTION = "question";
private final ChatGptService chatGptService;

/**
* Creates an instance of the chatgpt command.
*
* @param chatGptService ChatGptService - Needed to make calls to ChatGPT API
*/
public ChatGptCommand(ChatGptService chatGptService) {
super("chatgpt", "Ask the ChatGPT AI a question!", CommandVisibility.GUILD);

this.chatGptService = chatGptService;

getData().addOption(OptionType.STRING, QUESTION_OPTION, "What do you want to ask?", true);
}

@Override
public void onSlashCommand(SlashCommandInteractionEvent event) {
event.deferReply().queue();
String response = chatGptService.ask(event.getOption(QUESTION_OPTION).getAsString())
.orElse("An error has occurred while trying to communication with ChatGPT. Please try again later");
event.getHook().sendMessage(response).queue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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;
import java.util.Optional;

/**
* 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 Duration TIMEOUT = Duration.ofSeconds(10);
private static final int MAX_TOKENS = 3_000;
private boolean isDisabled = false;
private final OpenAiService openAiService;

/**
* Creates instance of ChatGPTService
*
* @param config needed for token to OpenAI API.
*/
public ChatGptService(Config config) {
String apiKey = config.getOpenaiApiKey();
if (apiKey.isBlank()) {
isDisabled = true;
}

openAiService = new OpenAiService(apiKey, TIMEOUT);
}

/**
* Prompt ChatGPT with a question and receive a response.
*
* @param question The question being asked of ChatGPT. Max is {@value MAX_TOKENS} tokens.
* @see <a href="https://platform.openai.com/docs/guides/chat/managing-tokens">ChatGPT
* Tokens</a>.
* @return response from ChatGPT as a String.
*/
public Optional<String> ask(String question) {
if (isDisabled) {
return Optional.empty();
}

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 Optional.ofNullable(openAiService.createChatCompletion(chatCompletionRequest)
.getChoices()
.get(0)
.getMessage()
.getContent());
} catch (OpenAiHttpException openAiHttpException) {
logger.warn(
"There was an error using the OpenAI API: {} Code: {} Type: {} Status Code: {}",
openAiHttpException.getMessage(), openAiHttpException.code,
openAiHttpException.type, openAiHttpException.statusCode);
}
return Optional.empty();
}
}
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