Optimize assistant reuse by properties
This commit implements a new approach to store ChatGPT assistant IDs,
achieving several goals:
- Fixes the issue where existing assistants retain old settings after a
configuration change.
- Addresses a similar issue where dynamically changed settings via the
`/configure` command were not updated in existing assistants.
- Reduces the creation of new assistants by reusing those with identical
properties.
A hash key is now calculated based on the assistant's configurable
properties, and this key is used to store and retrieve the ChatGPT
assistant ID.
Change-Id: I226e28b7c24da12c05b8d8554c04bc0a79d99ae8
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
index 44c1817..cf390e4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandler.java
@@ -55,6 +55,14 @@
}
}
+ public synchronized void destroy() {
+ try {
+ Files.deleteIfExists(configFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to delete the config file: " + configFile, e);
+ }
+ }
+
private void storeProperties() {
try (var output = Files.newOutputStream(configFile)) {
configProperties.store(output, null);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
index b178743..a21c74b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/data/PluginDataHandlerProvider.java
@@ -11,8 +11,11 @@
@Singleton
public class PluginDataHandlerProvider extends PluginDataHandlerBaseProvider implements Provider<PluginDataHandler> {
+ private static final String PATH_ASSISTANTS = ".assistants";
+
private final String projectName;
private final String changeKey;
+ private final String assistantsWorkspace;
@Inject
public PluginDataHandlerProvider(
@@ -22,6 +25,7 @@
super(defaultPluginDataPath);
projectName = sanitizeFilename(change.getProjectName());
changeKey = change.getChangeKey().toString();
+ assistantsWorkspace = projectName + PATH_ASSISTANTS;
}
public PluginDataHandler getGlobalScope() {
@@ -35,4 +39,8 @@
public PluginDataHandler getChangeScope() {
return super.get(changeKey);
}
+
+ public PluginDataHandler getAssistantsWorkspace() {
+ return super.get(assistantsWorkspace);
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTypeChangeMerged.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTypeChangeMerged.java
index 6d033ba..8b82e56 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTypeChangeMerged.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventHandlerTypeChangeMerged.java
@@ -5,8 +5,7 @@
import com.googlesource.gerrit.plugins.chatgpt.interfaces.listener.IEventHandlerType;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
-import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt.ChatGptAssistantBase;
-import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt.ChatGptAssistantReview;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt.ChatGptAssistant;
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
import lombok.extern.slf4j.Slf4j;
@@ -39,7 +38,7 @@
@Override
public void processEvent() {
- ChatGptAssistantBase chatGptAssistant = new ChatGptAssistantBase(
+ ChatGptAssistant chatGptAssistant = new ChatGptAssistant(
config,
changeSetData,
change,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantBase.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistant.java
similarity index 78%
rename from src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantBase.java
rename to src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistant.java
index ba4f1c8..1092a8b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistant.java
@@ -13,12 +13,14 @@
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.prompt.ChatGptPromptStateful;
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.model.api.chatgpt.*;
-import lombok.Getter;
+import com.googlesource.gerrit.plugins.chatgpt.utils.HashUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import java.net.URI;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import static com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt.ChatGptVectorStore.KEY_VECTOR_STORE_ID;
import static com.googlesource.gerrit.plugins.chatgpt.utils.FileUtils.createTempFileWithContent;
@@ -26,20 +28,20 @@
import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
@Slf4j
-public class ChatGptAssistantBase extends ClientBase {
- protected static final String KEY_REVIEW_ASSISTANT_ID = "reviewAssistantId";
- protected static final String KEY_REQUESTS_ASSISTANT_ID = "requestsAssistantId";
-
- @Getter
- protected String keyAssistantId;
-
+public class ChatGptAssistant extends ClientBase {
private final ChatGptHttpClient httpClient = new ChatGptHttpClient();
private final ChangeSetData changeSetData;
private final GerritChange change;
private final GitRepoFiles gitRepoFiles;
private final PluginDataHandler projectDataHandler;
+ private final PluginDataHandler assistantsDataHandler;
- public ChatGptAssistantBase(
+ private String description;
+ private String instructions;
+ private String model;
+ private Double temperature;
+
+ public ChatGptAssistant(
Configuration config,
ChangeSetData changeSetData,
GerritChange change,
@@ -51,20 +53,25 @@
this.change = change;
this.gitRepoFiles = gitRepoFiles;
this.projectDataHandler = pluginDataHandlerProvider.getProjectScope();
+ this.assistantsDataHandler = pluginDataHandlerProvider.getAssistantsWorkspace();
}
- public void setupAssistant() {
- String assistantId = projectDataHandler.getValue(keyAssistantId);
+ public String setupAssistant() {
+ setupAssistantParameters();
+ String assistantIdHashKey = calculateAssistantIdHashKey();
+ log.info("Calculated assistant id hash key: {}", assistantIdHashKey);
+ String assistantId = assistantsDataHandler.getValue(assistantIdHashKey);
if (assistantId == null || config.getForceCreateAssistant()) {
log.debug("Setup Assistant for project {}", change.getProjectNameKey());
String vectorStoreId = createVectorStore();
assistantId = createAssistant(vectorStoreId);
- projectDataHandler.setValue(keyAssistantId, assistantId);
+ assistantsDataHandler.setValue(assistantIdHashKey, assistantId);
log.info("Project assistant created with ID: {}", assistantId);
}
else {
log.info("Project assistant found for the project. Assistant ID: {}", assistantId);
}
+ return assistantId;
}
public String createVectorStore() {
@@ -85,8 +92,7 @@
public void flushAssistantIds() {
projectDataHandler.removeValue(KEY_VECTOR_STORE_ID);
- projectDataHandler.removeValue(KEY_REVIEW_ASSISTANT_ID);
- projectDataHandler.removeValue(KEY_REQUESTS_ASSISTANT_ID);
+ assistantsDataHandler.destroy();
}
private String uploadRepoFiles() {
@@ -111,8 +117,6 @@
private Request createRequest(String vectorStoreId) {
URI uri = URI.create(config.getGptDomain() + UriResourceLocatorStateful.assistantCreateUri());
log.debug("ChatGPT Create Assistant request URI: {}", uri);
- ChatGptPromptStateful chatGptPromptStateful = new ChatGptPromptStateful(config, changeSetData, change);
- ChatGptParameters chatGptParameters = new ChatGptParameters(config, change.getIsCommentEvent());
ChatGptTool[] tools = new ChatGptTool[] {
new ChatGptTool("file_search"),
ChatGptTools.retrieveFormatRepliesTool()
@@ -124,10 +128,10 @@
);
ChatGptCreateAssistantRequestBody requestBody = ChatGptCreateAssistantRequestBody.builder()
.name(ChatGptPromptStateful.DEFAULT_GPT_ASSISTANT_NAME)
- .description(chatGptPromptStateful.getDefaultGptAssistantDescription())
- .instructions(chatGptPromptStateful.getDefaultGptAssistantInstructions())
- .model(config.getGptModel())
- .temperature(chatGptParameters.getGptTemperature())
+ .description(description)
+ .instructions(instructions)
+ .model(model)
+ .temperature(temperature)
.tools(tools)
.toolResources(toolResources)
.build();
@@ -135,4 +139,23 @@
return httpClient.createRequestFromJson(uri.toString(), config.getGptToken(), requestBody);
}
+
+ private void setupAssistantParameters() {
+ ChatGptPromptStateful chatGptPromptStateful = new ChatGptPromptStateful(config, changeSetData, change);
+ ChatGptParameters chatGptParameters = new ChatGptParameters(config, change.getIsCommentEvent());
+
+ description = chatGptPromptStateful.getDefaultGptAssistantDescription();
+ instructions = chatGptPromptStateful.getDefaultGptAssistantInstructions();
+ model = config.getGptModel();
+ temperature = chatGptParameters.getGptTemperature();
+ }
+
+ private String calculateAssistantIdHashKey() {
+ return HashUtils.hashData(new ArrayList<>(List.of(
+ description,
+ instructions,
+ model,
+ temperature.toString()
+ )));
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantRequests.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantRequests.java
deleted file mode 100644
index f2f0b5a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantRequests.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt;
-
-import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
-import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class ChatGptAssistantRequests extends ChatGptAssistantBase {
- public ChatGptAssistantRequests(
- Configuration config,
- ChangeSetData changeSetData,
- GerritChange change,
- GitRepoFiles gitRepoFiles,
- PluginDataHandlerProvider pluginDataHandlerProvider
- ) {
- super(config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider);
- keyAssistantId = KEY_REQUESTS_ASSISTANT_ID;
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantReview.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantReview.java
deleted file mode 100644
index abc8ac9..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptAssistantReview.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt;
-
-import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
-import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class ChatGptAssistantReview extends ChatGptAssistantBase {
- public ChatGptAssistantReview(
- Configuration config,
- ChangeSetData changeSetData,
- GerritChange change,
- GitRepoFiles gitRepoFiles,
- PluginDataHandlerProvider pluginDataHandlerProvider
- ) {
- super(config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider);
- keyAssistantId = KEY_REVIEW_ASSISTANT_ID;
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
index eee376b..e38af1c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptClientStateful.java
@@ -62,8 +62,7 @@
changeSetData,
change,
gitRepoFiles,
- pluginDataHandlerProvider,
- isCommentEvent
+ pluginDataHandlerProvider
);
chatGptRun.createRun();
chatGptRun.pollRunStep();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java
index 7e5ed34..02c9b99 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptRun.java
@@ -1,7 +1,6 @@
package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
-import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandler;
import com.googlesource.gerrit.plugins.chatgpt.data.PluginDataHandlerProvider;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.ClientBase;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
@@ -39,11 +38,10 @@
private final String threadId;
private final GitRepoFiles gitRepoFiles;
private final PluginDataHandlerProvider pluginDataHandlerProvider;
- private final boolean isCommentEvent;
private ChatGptResponse runResponse;
private ChatGptListResponse stepResponse;
- private String keyAssistantId;
+ private String assistantId;
public ChatGptRun(
String threadId,
@@ -51,8 +49,7 @@
ChangeSetData changeSetData,
GerritChange change,
GitRepoFiles gitRepoFiles,
- PluginDataHandlerProvider pluginDataHandlerProvider,
- boolean isCommentEvent
+ PluginDataHandlerProvider pluginDataHandlerProvider
) {
super(config);
this.changeSetData = changeSetData;
@@ -60,16 +57,17 @@
this.threadId = threadId;
this.gitRepoFiles = gitRepoFiles;
this.pluginDataHandlerProvider = pluginDataHandlerProvider;
- this.isCommentEvent = isCommentEvent;
}
public void createRun() {
- ChatGptAssistantBase chatGptAssistant = isCommentEvent ?
- new ChatGptAssistantRequests(config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider) :
- new ChatGptAssistantReview(config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider);
-
- chatGptAssistant.setupAssistant();
- keyAssistantId = chatGptAssistant.getKeyAssistantId();
+ ChatGptAssistant chatGptAssistant = new ChatGptAssistant(
+ config,
+ changeSetData,
+ change,
+ gitRepoFiles,
+ pluginDataHandlerProvider
+ );
+ assistantId = chatGptAssistant.setupAssistant();
Request request = runCreateRequest();
log.info("ChatGPT Create Run request: {}", request);
@@ -145,10 +143,8 @@
private Request runCreateRequest() {
URI uri = URI.create(config.getGptDomain() + UriResourceLocatorStateful.runsUri(threadId));
log.debug("ChatGPT Create Run request URI: {}", uri);
-
- PluginDataHandler projectDataHandler = pluginDataHandlerProvider.getProjectScope();
ChatGptCreateRunRequest requestBody = ChatGptCreateRunRequest.builder()
- .assistantId(projectDataHandler.getValue(keyAssistantId))
+ .assistantId(assistantId)
.build();
return httpClient.createRequestFromJson(uri.toString(), config.getGptToken(), requestBody);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/HashUtils.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/HashUtils.java
new file mode 100644
index 0000000..7e9840c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/HashUtils.java
@@ -0,0 +1,39 @@
+package com.googlesource.gerrit.plugins.chatgpt.utils;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+public class HashUtils {
+
+ public static String hashData(List<String> dataItems) {
+ StringBuilder concatenatedData = new StringBuilder();
+ for (String item : dataItems) {
+ concatenatedData.append(item);
+ }
+ return sha1(concatenatedData.toString());
+ }
+
+ private static String sha1(String data) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ byte[] hashBytes = digest.digest(data.getBytes(StandardCharsets.UTF_8));
+ return bytesToHex(hashBytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("SHA-1 algorithm not found", e);
+ }
+ }
+
+ private static String bytesToHex(byte[] hashBytes) {
+ StringBuilder hexString = new StringBuilder(2 * hashBytes.length);
+ for (byte b : hashBytes) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
index 239f84b..6c74d1d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
@@ -69,6 +69,8 @@
projectHandler = provider.getProjectScope();
// Mock the pluginDataHandlerProvider to return the mocked project pluginDataHandler
when(pluginDataHandlerProvider.getProjectScope()).thenReturn(projectHandler);
+ // Mock the pluginDataHandlerProvider to return the mocked assistant pluginDataHandler
+ when(pluginDataHandlerProvider.getAssistantsWorkspace()).thenReturn(projectHandler);
}
protected void initTest() {