Skip to content

Commit 43bd212

Browse files
committed
feature: store and load conversations
1 parent 2d7baac commit 43bd212

File tree

9 files changed

+194
-25
lines changed

9 files changed

+194
-25
lines changed

README.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,27 @@ implementation 'io.github.kezhenxu94:chatgpt-java-sdk:${chatgpt-java.version}'
2626
```java
2727
public class ChatGPTTest {
2828
public static void main(String[] args) throws IOException, InterruptedException {
29-
final var chatGPT = ChatGPT.builder().apiKey("").build(); // Setting API Key via environment variable (CHATGPT_API_KEY) is also supported.
29+
final var chatGPT = ChatGPT
30+
.builder()
31+
.dataPath(Files.createTempDirectory("chatgpt")) // Persist the chat history to a data path
32+
.build();
33+
34+
// Start a new conversation
3035
final var conversation = chatGPT.newConversation();
3136
System.out.println(conversation.ask("What's your name?").content());
3237
// Output: I'm an AI language model developed by OpenAI, and I don't have a name. What can I help you with today?
3338
System.out.println(conversation.ask("What did I ask you?").content());
3439
// Output: You asked for my name.
40+
conversation.save(); // Save the history manually, conversations are saved on shutdown by default.
3541

3642
final var conversation2 = chatGPT.newConversation("You are a software engineer.");
3743
System.out.println(conversation2.ask("What's your job?").content());
38-
// Output: As a software engineer, my job involves designing, developing, testing, and maintaining software systems and applications. It can involve tasks such as writing code, debugging programs, troubleshooting issues, and collaborating with other team members to ensure the overall functionality and efficiency of the software being developed. I may also need to work on improving existing software, conducting research to identify new technologies or methods that could benefit my team, and keeping up with industry trends and best practices to continuously improve my skills and knowledge.
3944
System.out.println(conversation2.ask("What's your day to day work?").content());
40-
// Output: As an AI language model, I do not have a physical day-to-day work environment. However, as a software engineer, a typical day may involve various activities such as:
45+
46+
// Load a conversation by the ID
47+
final var conversation3 = chatGPT.loadConversation(conversation.id());
48+
conversation3.ask("What did I ask you?");
49+
// Should print the same as the first conversation
4150
}
4251
}
4352
```

chatgpt-cli/src/main/java/io/github/kezhenxu94/chatgpt/cli/commands/ConversationCommands.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,26 @@
77

88
@ShellComponent
99
public class ConversationCommands {
10-
private final Conversation conversation;
10+
private final ChatGPT chatGPT;
11+
12+
private Conversation conversation;
1113

1214
public ConversationCommands(ChatGPT chatGPT) {
13-
conversation = chatGPT.newConversation();
15+
this.chatGPT = chatGPT;
16+
}
17+
18+
@ShellMethod("Load a conversation by ID")
19+
public String load(String id) throws Exception {
20+
conversation = chatGPT.loadConversation(id);
21+
return "You are now at conversation: " + id;
1422
}
1523

16-
@ShellMethod(value = "Ask a question")
24+
@ShellMethod("Ask a question")
1725
public String ask(String question) throws Exception {
26+
if (conversation == null) {
27+
conversation = chatGPT.newConversation();
28+
}
29+
1830
final var answer = conversation.ask(question);
1931
return answer.content().trim();
2032
}

chatgpt-java-sdk/src/main/java/io/github/kezhenxu94/chatgpt/ChatGPT.java

+15
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
package io.github.kezhenxu94.chatgpt;
22

3+
import java.io.IOException;
4+
import java.nio.file.Path;
5+
36
public interface ChatGPT {
7+
public static final String API_URL = "https://api.openai.com/v1/chat/completions";
8+
49
Conversation newConversation();
510

611
Conversation newConversation(String system);
712

13+
Conversation newConversationWithID(String id);
14+
15+
Conversation loadConversation(String id) throws IOException;
16+
817
String apiKey();
918

1019
int conversationSize();
1120

21+
Path dataPath();
22+
1223
static Builder builder() {
1324
return new ChatGPTBuilder();
1425
}
@@ -18,6 +29,10 @@ interface Builder {
1829

1930
Builder apiKey(String apiKey);
2031

32+
Builder dataPath(String persistentPath);
33+
34+
Builder dataPath(Path persistentPath);
35+
2136
ChatGPT build();
2237
}
2338
}

chatgpt-java-sdk/src/main/java/io/github/kezhenxu94/chatgpt/ChatGPTBuilder.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package io.github.kezhenxu94.chatgpt;
22

3+
import java.nio.file.Path;
4+
import java.nio.file.Paths;
5+
36
class ChatGPTBuilder implements ChatGPT.Builder {
47
private String apiKey;
58
private int conversationSize = Integer.MAX_VALUE;
9+
private Path dataPath = Paths.get(System.getProperty("user.home"), ".chatgpt", "data");
610

711
@Override
812
public ChatGPT build() {
@@ -14,7 +18,7 @@ public ChatGPT build() {
1418
throw new IllegalArgumentException("API key is required");
1519
}
1620

17-
return new ChatGPTImpl(apiKey, conversationSize);
21+
return new ChatGPTImpl(apiKey, conversationSize, dataPath);
1822
}
1923

2024
public ChatGPTBuilder conversationSize(int conversationSize) {
@@ -27,4 +31,16 @@ public ChatGPT.Builder apiKey(String apiKey) {
2731
this.apiKey = apiKey;
2832
return this;
2933
}
34+
35+
@Override
36+
public ChatGPT.Builder dataPath(String dataPath) {
37+
this.dataPath = Paths.get(dataPath);
38+
return this;
39+
}
40+
41+
@Override
42+
public ChatGPT.Builder dataPath(Path dataPath) {
43+
this.dataPath = dataPath;
44+
return this;
45+
}
3046
}

chatgpt-java-sdk/src/main/java/io/github/kezhenxu94/chatgpt/ChatGPTImpl.java

+57-3
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,65 @@
11
package io.github.kezhenxu94.chatgpt;
22

3+
import io.github.kezhenxu94.chatgpt.message.Message;
4+
import java.io.IOException;
5+
import java.nio.file.Path;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
39
final class ChatGPTImpl implements ChatGPT {
10+
private final List<Conversation> conversations;
11+
412
private final String apiKey;
513
private final int conversationSize;
14+
private final Path dataPath;
615

7-
public ChatGPTImpl(String apiKey, int conversationSize) {
16+
public ChatGPTImpl(String apiKey, int conversationSize, Path dataPath) {
817
this.apiKey = apiKey;
918
this.conversationSize = conversationSize;
19+
this.dataPath = dataPath;
20+
21+
this.conversations = new ArrayList<>();
22+
23+
Runtime.getRuntime()
24+
.addShutdownHook(
25+
new Thread(
26+
() -> {
27+
try {
28+
save();
29+
} catch (IOException e) {
30+
throw new RuntimeException(e);
31+
}
32+
}));
1033
}
1134

1235
@Override
1336
public Conversation newConversation() {
14-
return new ConversationImpl(this);
37+
final var newConversation = new ConversationImpl(this);
38+
conversations.add(newConversation);
39+
return newConversation;
1540
}
1641

1742
@Override
1843
public Conversation newConversation(String system) {
19-
return new ConversationImpl(this, system);
44+
final var newConversation = new ConversationImpl(this, Message.ofSystem(system));
45+
conversations.add(newConversation);
46+
return newConversation;
47+
}
48+
49+
@Override
50+
public Conversation newConversationWithID(String id) {
51+
final var conversation = new ConversationImpl(this, id);
52+
conversations.add(conversation);
53+
return conversation;
54+
}
55+
56+
@Override
57+
public Conversation loadConversation(String id) throws IOException {
58+
final var conversation = new ConversationImpl(this, id);
59+
conversations.add(conversation);
60+
61+
conversation.load();
62+
return conversation;
2063
}
2164

2265
@Override
@@ -28,4 +71,15 @@ public String apiKey() {
2871
public int conversationSize() {
2972
return conversationSize;
3073
}
74+
75+
@Override
76+
public Path dataPath() {
77+
return dataPath;
78+
}
79+
80+
void save() throws IOException {
81+
for (final var conversation : conversations) {
82+
conversation.save();
83+
}
84+
}
3185
}

chatgpt-java-sdk/src/main/java/io/github/kezhenxu94/chatgpt/Conversation.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22

33
import io.github.kezhenxu94.chatgpt.message.Message;
44

5-
import java.io.IOException;
5+
import java.io.IOException;import java.util.List;
66

77
public interface Conversation {
88
Message ask(String question) throws IOException, InterruptedException;
9+
10+
String id();
11+
12+
List<Message> messages();
13+
14+
void save() throws IOException;
15+
16+
void load() throws IOException;
917
}

chatgpt-java-sdk/src/main/java/io/github/kezhenxu94/chatgpt/ConversationImpl.java

+52-8
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,43 @@
11
package io.github.kezhenxu94.chatgpt;
22

3+
import com.fasterxml.jackson.core.type.TypeReference;
34
import com.fasterxml.jackson.databind.ObjectMapper;
45
import io.github.kezhenxu94.chatgpt.internal.ChatGPTHttpRequest;
56
import io.github.kezhenxu94.chatgpt.internal.ChatGPTHttpResponse;
67
import io.github.kezhenxu94.chatgpt.internal.JsonBodyHandler;
78
import io.github.kezhenxu94.chatgpt.message.Message;
8-
9+
import io.github.kezhenxu94.chatgpt.message.SystemMessage;
910
import java.io.IOException;
1011
import java.net.URI;
1112
import java.net.http.HttpClient;
1213
import java.net.http.HttpRequest;
1314
import java.time.Duration;
1415
import java.util.LinkedList;
1516
import java.util.List;
17+
import java.util.UUID;
1618

1719
final class ConversationImpl implements Conversation {
18-
private static final String API_URL = "https://api.openai.com/v1/chat/completions";
19-
2020
private final ChatGPT chatGPT;
21+
private final String uuid;
2122
private final List<Message> messages;
2223
private final HttpClient httpClient;
2324
private final ObjectMapper objectMapper;
2425

25-
ConversationImpl(ChatGPT chatGPT) {
26+
ConversationImpl(ChatGPT chatGPT, String uuid) {
2627
this.chatGPT = chatGPT;
28+
this.uuid = uuid;
2729
this.messages = new LinkedList<>();
28-
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
30+
this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
2931
this.objectMapper = new ObjectMapper();
3032
}
3133

32-
ConversationImpl(ChatGPT chatGPT, String system) {
34+
ConversationImpl(ChatGPT chatGPT) {
35+
this(chatGPT, UUID.randomUUID().toString());
36+
}
37+
38+
ConversationImpl(ChatGPT chatGPT, SystemMessage system) {
3339
this(chatGPT);
34-
messages.add(Message.ofSystem(system));
40+
messages.add(system);
3541
}
3642

3743
@Override
@@ -47,7 +53,7 @@ public Message ask(String question) throws IOException, InterruptedException {
4753
final var response =
4854
httpClient.send(
4955
HttpRequest.newBuilder()
50-
.uri(URI.create(API_URL))
56+
.uri(URI.create(ChatGPT.API_URL))
5157
.header("Authorization", "Bearer " + chatGPT.apiKey())
5258
.header("Content-Type", "application/json")
5359
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(request)))
@@ -60,4 +66,42 @@ public Message ask(String question) throws IOException, InterruptedException {
6066

6167
return choice.message();
6268
}
69+
70+
@Override
71+
public String id() {
72+
return uuid;
73+
}
74+
75+
@Override
76+
public List<Message> messages() {
77+
return messages;
78+
}
79+
80+
@Override
81+
public void save() throws IOException {
82+
final var dataPath = chatGPT.dataPath();
83+
if (!dataPath.toFile().exists()) {
84+
//noinspection ResultOfMethodCallIgnored
85+
dataPath.toFile().mkdirs();
86+
}
87+
88+
final var conversationFile = dataPath.resolve(id() + ".json").toFile();
89+
objectMapper.writeValue(conversationFile, messages());
90+
}
91+
92+
@Override
93+
public void load() throws IOException {
94+
final var dataPath = chatGPT.dataPath();
95+
if (!dataPath.toFile().exists()) {
96+
return;
97+
}
98+
final var conversationFile = dataPath.resolve(id() + ".json").toFile();
99+
if (!conversationFile.exists()) {
100+
return;
101+
}
102+
103+
final var messages =
104+
objectMapper.readValue(conversationFile, new TypeReference<List<Message>>() {});
105+
this.messages.addAll(messages);
106+
}
63107
}

chatgpt-java-sdk/src/main/java/io/github/kezhenxu94/chatgpt/message/Message.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ public interface Message {
1717
@JsonProperty
1818
String content();
1919

20-
static Message ofUser(String content) {
20+
static UserMessage ofUser(String content) {
2121
return new UserMessage(content);
2222
}
2323

24-
static Message ofSystem(String content) {
24+
static SystemMessage ofSystem(String content) {
2525
return new SystemMessage(content);
2626
}
2727
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
package io.github.kezhenxu94.chatgpt;
22

3-
import java.io.IOException;
3+
import java.io.IOException;import java.nio.file.Files;
44

55
public class ChatGPTTest {
66
public static void main(String[] args) throws IOException, InterruptedException {
7-
final var chatGPT = ChatGPT.builder().conversationSize(2).build();
7+
final var chatGPT = ChatGPT
8+
.builder()
9+
.dataPath(Files.createTempDirectory("chatgpt")) // Persist the chat history to a data path
10+
.build();
11+
12+
// Start a new conversation
813
final var conversation = chatGPT.newConversation();
914
System.out.println(conversation.ask("What's your name?").content());
10-
System.out.println(conversation.ask("What's your hobbit?").content());
11-
System.out.println(conversation.ask("What's your favorite song?").content());
15+
// Output: I'm an AI language model developed by OpenAI, and I don't have a name. What can I help you with today?
1216
System.out.println(conversation.ask("What did I ask you?").content());
17+
// Output: You asked for my name.
18+
conversation.save(); // Save the history manually, conversations are saved on shutdown by default.
1319

1420
final var conversation2 = chatGPT.newConversation("You are a software engineer.");
1521
System.out.println(conversation2.ask("What's your job?").content());
1622
System.out.println(conversation2.ask("What's your day to day work?").content());
23+
24+
// Load a conversation by the ID
25+
final var conversation3 = chatGPT.loadConversation(conversation.id());
26+
conversation3.ask("What did I ask you?");
27+
// Should print the same as the first conversation
1728
}
1829
}

0 commit comments

Comments
 (0)