Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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": "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"
}
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