Skip to content

Commit ae006d2

Browse files
authored
Chat gpt attempt questions (#873)
* * Refactor folder name * Add ChatGPT command/service code and utils. * * Fix Features.java import statements and add ChatGPTService being passed in to helper. * Add parsing of question to helper. * * Add AI Help message to when user asks question. (Either direction to the command if the service fails or an explanation that the next response is AI generated) * * Add test files and test suite for AIResponseParser.java * Add logger.debug() to catch responses before being parsed in case of failing to parse correctly to generate new tests [AIResponseParser.java]. * * Refactor how AIResponseParserTest parameterizes file names (ints instead of full file names). * Remove ChatGPTServiceTest.java as it was an older way to generate long responses from ChatGPT for testing. * Add another test file and include in parameters for AIResponseParserTest.java
1 parent 03b1112 commit ae006d2

File tree

12 files changed

+566
-32
lines changed

12 files changed

+566
-32
lines changed

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,46 @@
44

55
import org.togetherjava.tjbot.config.Config;
66
import org.togetherjava.tjbot.db.Database;
7-
import org.togetherjava.tjbot.features.basic.*;
8-
import org.togetherjava.tjbot.features.bookmarks.*;
9-
import org.togetherjava.tjbot.features.chaptgpt.ChatGptCommand;
10-
import org.togetherjava.tjbot.features.chaptgpt.ChatGptService;
7+
import org.togetherjava.tjbot.features.basic.PingCommand;
8+
import org.togetherjava.tjbot.features.basic.RoleSelectCommand;
9+
import org.togetherjava.tjbot.features.basic.SlashCommandEducator;
10+
import org.togetherjava.tjbot.features.basic.SuggestionsUpDownVoter;
11+
import org.togetherjava.tjbot.features.bookmarks.BookmarksCommand;
12+
import org.togetherjava.tjbot.features.bookmarks.BookmarksSystem;
13+
import org.togetherjava.tjbot.features.bookmarks.LeftoverBookmarksCleanupRoutine;
14+
import org.togetherjava.tjbot.features.bookmarks.LeftoverBookmarksListener;
15+
import org.togetherjava.tjbot.features.chatgpt.ChatGptCommand;
16+
import org.togetherjava.tjbot.features.chatgpt.ChatGptService;
1117
import org.togetherjava.tjbot.features.code.CodeMessageAutoDetection;
1218
import org.togetherjava.tjbot.features.code.CodeMessageHandler;
1319
import org.togetherjava.tjbot.features.code.CodeMessageManualDetection;
1420
import org.togetherjava.tjbot.features.filesharing.FileSharingMessageListener;
15-
import org.togetherjava.tjbot.features.help.*;
21+
import org.togetherjava.tjbot.features.help.AutoPruneHelperRoutine;
22+
import org.togetherjava.tjbot.features.help.GuildLeaveCloseThreadListener;
23+
import org.togetherjava.tjbot.features.help.HelpSystemHelper;
24+
import org.togetherjava.tjbot.features.help.HelpThreadActivityUpdater;
25+
import org.togetherjava.tjbot.features.help.HelpThreadAutoArchiver;
26+
import org.togetherjava.tjbot.features.help.HelpThreadCommand;
27+
import org.togetherjava.tjbot.features.help.HelpThreadCreatedListener;
28+
import org.togetherjava.tjbot.features.help.HelpThreadMetadataPurger;
1629
import org.togetherjava.tjbot.features.jshell.JShellCommand;
1730
import org.togetherjava.tjbot.features.jshell.JShellEval;
1831
import org.togetherjava.tjbot.features.mathcommands.TeXCommand;
1932
import org.togetherjava.tjbot.features.mathcommands.wolframalpha.WolframAlphaCommand;
2033
import org.togetherjava.tjbot.features.mediaonly.MediaOnlyChannelListener;
21-
import org.togetherjava.tjbot.features.moderation.*;
34+
import org.togetherjava.tjbot.features.moderation.BanCommand;
35+
import org.togetherjava.tjbot.features.moderation.KickCommand;
36+
import org.togetherjava.tjbot.features.moderation.ModerationActionsStore;
37+
import org.togetherjava.tjbot.features.moderation.MuteCommand;
38+
import org.togetherjava.tjbot.features.moderation.NoteCommand;
39+
import org.togetherjava.tjbot.features.moderation.QuarantineCommand;
40+
import org.togetherjava.tjbot.features.moderation.RejoinModerationRoleListener;
2241
import org.togetherjava.tjbot.features.moderation.ReportCommand;
42+
import org.togetherjava.tjbot.features.moderation.UnbanCommand;
43+
import org.togetherjava.tjbot.features.moderation.UnmuteCommand;
44+
import org.togetherjava.tjbot.features.moderation.UnquarantineCommand;
45+
import org.togetherjava.tjbot.features.moderation.WarnCommand;
46+
import org.togetherjava.tjbot.features.moderation.WhoIsCommand;
2347
import org.togetherjava.tjbot.features.moderation.attachment.BlacklistedAttachmentListener;
2448
import org.togetherjava.tjbot.features.moderation.audit.AuditCommand;
2549
import org.togetherjava.tjbot.features.moderation.audit.ModAuditLogRoutine;
@@ -76,9 +100,9 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
76100
ModerationActionsStore actionsStore = new ModerationActionsStore(database);
77101
ModAuditLogWriter modAuditLogWriter = new ModAuditLogWriter(config);
78102
ScamHistoryStore scamHistoryStore = new ScamHistoryStore(database);
79-
HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database);
80103
CodeMessageHandler codeMessageHandler = new CodeMessageHandler(jshellEval);
81104
ChatGptService chatGptService = new ChatGptService(config);
105+
HelpSystemHelper helpSystemHelper = new HelpSystemHelper(config, database, chatGptService);
82106

83107
// NOTE The system can add special system relevant commands also by itself,
84108
// hence this list may not necessarily represent the full list of all commands actually
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.togetherjava.tjbot.features.chatgpt;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.stream.Collectors;
9+
10+
/**
11+
* Represents a class to partition long text blocks into smaller blocks which work with Discord's
12+
* API. Initially constructed to partition text from AI text generation APIs.
13+
*/
14+
public class AIResponseParser {
15+
private AIResponseParser() {
16+
throw new UnsupportedOperationException("Utility class, construction not supported");
17+
}
18+
19+
private static final Logger logger = LoggerFactory.getLogger(AIResponseParser.class);
20+
private static final int RESPONSE_LENGTH_LIMIT = 2_000;
21+
22+
/**
23+
* Parses the response generated by AI. If response is longer than
24+
* {@value RESPONSE_LENGTH_LIMIT}, then breaks apart the response into suitable lengths for
25+
* Discords API.
26+
*
27+
* @param response The response from the AI which we want to send over Discord.
28+
* @return An array potentially holding the original response split up into shorter than
29+
* {@value RESPONSE_LENGTH_LIMIT} length pieces.
30+
*/
31+
public static String[] parse(String response) {
32+
String[] partedResponse = new String[] {response};
33+
if (response.length() > RESPONSE_LENGTH_LIMIT) {
34+
logger.debug("Response to parse:\n{}", response);
35+
partedResponse = partitionAiResponse(response);
36+
}
37+
38+
return partedResponse;
39+
}
40+
41+
private static String[] partitionAiResponse(String response) {
42+
List<String> responseChunks = new ArrayList<>();
43+
String[] splitResponseOnMarks = response.split("```");
44+
45+
for (int i = 0; i < splitResponseOnMarks.length; i++) {
46+
String split = splitResponseOnMarks[i];
47+
List<String> chunks = new ArrayList<>();
48+
chunks.add(split);
49+
50+
// Check each chunk for correct length. If over the length, split in two and check
51+
// again.
52+
while (!chunks.stream().allMatch(s -> s.length() < RESPONSE_LENGTH_LIMIT)) {
53+
for (int j = 0; j < chunks.size(); j++) {
54+
String chunk = chunks.get(j);
55+
if (chunk.length() > RESPONSE_LENGTH_LIMIT) {
56+
int midpointNewline = chunk.lastIndexOf("\n", chunk.length() / 2);
57+
chunks.set(j, chunk.substring(0, midpointNewline));
58+
chunks.add(j + 1, chunk.substring(midpointNewline));
59+
}
60+
}
61+
}
62+
63+
// Given the splitting on ```, the odd numbered entries need to have code marks
64+
// restored.
65+
if (i % 2 != 0) {
66+
// We assume that everything after the ``` on the same line is the language
67+
// declaration. Could be empty.
68+
String lang = split.substring(0, split.indexOf(System.lineSeparator()));
69+
chunks = chunks.stream()
70+
.map(s -> ("```" + lang).concat(s).concat("```"))
71+
// Handle case of doubling language declaration
72+
.map(s -> s.replaceFirst("```" + lang + lang, "```" + lang))
73+
.collect(Collectors.toList());
74+
}
75+
76+
List<String> list = chunks.stream().filter(string -> !string.equals("")).toList();
77+
responseChunks.addAll(list);
78+
} // end of for loop.
79+
80+
return responseChunks.toArray(new String[0]);
81+
}
82+
}

application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java renamed to application/src/main/java/org/togetherjava/tjbot/features/chatgpt/ChatGptCommand.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.togetherjava.tjbot.features.chaptgpt;
1+
package org.togetherjava.tjbot.features.chatgpt;
22

33
import com.github.benmanes.caffeine.cache.Cache;
44
import com.github.benmanes.caffeine.cache.Caffeine;
@@ -22,6 +22,7 @@
2222
* which it will respond with an AI generated answer.
2323
*/
2424
public final class ChatGptCommand extends SlashCommandAdapter {
25+
public static final String COMMAND_NAME = "chatgpt";
2526
private static final String QUESTION_INPUT = "question";
2627
private static final int MAX_MESSAGE_INPUT_LENGTH = 200;
2728
private static final int MIN_MESSAGE_INPUT_LENGTH = 4;
@@ -37,7 +38,7 @@ public final class ChatGptCommand extends SlashCommandAdapter {
3738
* @param chatGptService ChatGptService - Needed to make calls to ChatGPT API
3839
*/
3940
public ChatGptCommand(ChatGptService chatGptService) {
40-
super("chatgpt", "Ask the ChatGPT AI a question!", CommandVisibility.GUILD);
41+
super(COMMAND_NAME, "Ask the ChatGPT AI a question!", CommandVisibility.GUILD);
4142

4243
this.chatGptService = chatGptService;
4344
}
@@ -73,14 +74,20 @@ public void onSlashCommand(SlashCommandInteractionEvent event) {
7374
public void onModalSubmitted(ModalInteractionEvent event, List<String> args) {
7475
event.deferReply().queue();
7576

76-
Optional<String> optional =
77+
Optional<String[]> optional =
7778
chatGptService.ask(event.getValue(QUESTION_INPUT).getAsString());
7879
if (optional.isPresent()) {
7980
userIdToAskedAtCache.put(event.getMember().getId(), Instant.now());
8081
}
8182

82-
String response = optional.orElse(
83-
"An error has occurred while trying to communicate with ChatGPT. Please try again later");
84-
event.getHook().sendMessage(response).queue();
83+
String[] errorResponse = {"""
84+
An error has occurred while trying to communicate with ChatGPT.
85+
Please try again later.
86+
"""};
87+
88+
String[] response = optional.orElse(errorResponse);
89+
for (String message : response) {
90+
event.getHook().sendMessage(message).queue();
91+
}
8592
}
8693
}

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

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.togetherjava.tjbot.features.chaptgpt;
1+
package org.togetherjava.tjbot.features.chatgpt;
22

33
import com.theokanning.openai.OpenAiHttpException;
44
import com.theokanning.openai.completion.chat.ChatCompletionRequest;
@@ -22,12 +22,13 @@ public class ChatGptService {
2222
private static final Logger logger = LoggerFactory.getLogger(ChatGptService.class);
2323
private static final Duration TIMEOUT = Duration.ofSeconds(90);
2424
private static final int MAX_TOKENS = 3_000;
25+
private static final String AI_MODEL = "gpt-3.5-turbo";
2526
private boolean isDisabled = false;
2627
private final OpenAiService openAiService;
2728

2829
/**
2930
* Creates instance of ChatGPTService
30-
*
31+
*
3132
* @param config needed for token to OpenAI API.
3233
*/
3334
public ChatGptService(Config config) {
@@ -37,17 +38,34 @@ public ChatGptService(Config config) {
3738
}
3839

3940
openAiService = new OpenAiService(apiKey, TIMEOUT);
41+
42+
ChatMessage setupMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(),
43+
"""
44+
Please answer questions in 1500 characters or less. Remember to count spaces in the
45+
character limit. For code supplied for review, refer to the old code supplied rather than
46+
rewriting the code. Don't supply a corrected version of the code.\s""");
47+
ChatCompletionRequest systemSetupRequest = ChatCompletionRequest.builder()
48+
.model(AI_MODEL)
49+
.messages(List.of(setupMessage))
50+
.frequencyPenalty(0.5)
51+
.temperature(0.3)
52+
.maxTokens(50)
53+
.n(1)
54+
.build();
55+
56+
// Sending the system setup message to ChatGPT.
57+
openAiService.createChatCompletion(systemSetupRequest);
4058
}
4159

4260
/**
4361
* Prompt ChatGPT with a question and receive a response.
44-
*
62+
*
4563
* @param question The question being asked of ChatGPT. Max is {@value MAX_TOKENS} tokens.
64+
* @return partitioned response from ChatGPT as a String array.
4665
* @see <a href="https://platform.openai.com/docs/guides/chat/managing-tokens">ChatGPT
4766
* Tokens</a>.
48-
* @return response from ChatGPT as a String.
4967
*/
50-
public Optional<String> ask(String question) {
68+
public Optional<String[]> ask(String question) {
5169
if (isDisabled) {
5270
return Optional.empty();
5371
}
@@ -56,18 +74,25 @@ public Optional<String> ask(String question) {
5674
ChatMessage chatMessage =
5775
new ChatMessage(ChatMessageRole.USER.value(), Objects.requireNonNull(question));
5876
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
59-
.model("gpt-3.5-turbo")
77+
.model(AI_MODEL)
6078
.messages(List.of(chatMessage))
6179
.frequencyPenalty(0.5)
62-
.temperature(0.7)
80+
.temperature(0.3)
6381
.maxTokens(MAX_TOKENS)
6482
.n(1)
6583
.build();
66-
return Optional.ofNullable(openAiService.createChatCompletion(chatCompletionRequest)
84+
85+
String response = openAiService.createChatCompletion(chatCompletionRequest)
6786
.getChoices()
6887
.get(0)
6988
.getMessage()
70-
.getContent());
89+
.getContent();
90+
91+
if (response == null) {
92+
return Optional.empty();
93+
}
94+
95+
return Optional.of(AIResponseParser.parse(response));
7196
} catch (OpenAiHttpException openAiHttpException) {
7297
logger.warn(
7398
"There was an error using the OpenAI API: {} Code: {} Type: {} Status Code: {}",

application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/package-info.java renamed to application/src/main/java/org/togetherjava/tjbot/features/chatgpt/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44
@MethodsReturnNonnullByDefault
55
@ParametersAreNonnullByDefault
6-
package org.togetherjava.tjbot.features.chaptgpt;
6+
package org.togetherjava.tjbot.features.chatgpt;
77

88
import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault;
99

0 commit comments

Comments
 (0)