Switch from `code_interpreter` to `file_search`

The `code_interpreter` tool (hosted by OpenAI), exhibited a high failure
rate during code lookups and has been replaced with file_search. This
change enables ChatGPT to more reliably perform codebase lookups to
provide context for its reviews.
The codebase is uploaded to ChatGPT and transformed into Vector Stores.
These stores are assigned permanent IDs for each project, allowing them
to be reused whenever a new assistant is created.

Change-Id: I333ffb8e0e42f681b57ca021591c2a30ccb4fbf1
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java
index 9480d76..a0265a2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/UriResourceLocatorStateful.java
@@ -42,4 +42,8 @@
     public static String runCancelUri(String threadId, String runId) {
         return runRetrieveUri(threadId, runId) + "/cancel";
     }
+
+    public static String vectorStoreCreateUri() {
+        return VERSION_URI + "/vector_stores";
+    }
 }
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/ChatGptAssistantBase.java
index cc11000..435efc9 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/ChatGptAssistantBase.java
@@ -20,7 +20,7 @@
 import java.net.URI;
 import java.nio.file.Path;
 
-import static com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.chatgpt.ChatGptFiles.KEY_FILE_ID;
+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;
 import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
 
@@ -54,8 +54,8 @@
         if (assistantId == null || config.getForceCreateAssistant()) {
             log.debug("Setup Assistant for project {}", change.getProjectNameKey());
             String fileId = uploadRepoFiles();
-            projectDataHandler.setValue(KEY_FILE_ID, fileId);
-            assistantId = createAssistant(fileId);
+            String vectorStoreId = createVectorStore(fileId);
+            assistantId = createAssistant(vectorStoreId);
             projectDataHandler.setValue(keyAssistantId, assistantId);
             log.info("Project assistant created with ID: {}", assistantId);
         }
@@ -73,8 +73,23 @@
         return chatGptFilesResponse.getId();
     }
 
-    private String createAssistant(String fileId) {
-        Request request = createRequest(fileId);
+    private String createVectorStore(String fileId) {
+        String vectorStoreId = projectDataHandler.getValue(KEY_VECTOR_STORE_ID);
+        if (vectorStoreId == null) {
+            ChatGptVectorStore vectorStore = new ChatGptVectorStore(fileId, config, change);
+            ChatGptResponse createVectorStoreResponse = vectorStore.createVectorStore();
+            vectorStoreId = createVectorStoreResponse.getId();
+            projectDataHandler.setValue(KEY_VECTOR_STORE_ID, vectorStoreId);
+            log.info("Vector store created with ID: {}", vectorStoreId);
+        }
+        else {
+            log.info("Vector store found for the project. Assistant ID: {}", vectorStoreId);
+        }
+        return vectorStoreId;
+    }
+
+    private String createAssistant(String vectorStoreId) {
+        Request request = createRequest(vectorStoreId);
         log.debug("ChatGPT Create Assistant request: {}", request);
 
         ChatGptResponse assistantResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class);
@@ -83,15 +98,20 @@
         return assistantResponse.getId();
     }
 
-    private Request createRequest(String fileId) {
+    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()
         };
-        ChatGptToolResources toolResources = new ChatGptToolResources(new ChatGptFileIds(new String[] {fileId}));
+        ChatGptToolResources toolResources = new ChatGptToolResources(
+                new ChatGptToolResources.VectorStoreIds(
+                        new String[] {vectorStoreId}
+                )
+        );
         ChatGptCreateAssistantRequestBody requestBody = ChatGptCreateAssistantRequestBody.builder()
                 .name(ChatGptPromptStateful.DEFAULT_GPT_ASSISTANT_NAME)
                 .description(chatGptPromptStateful.getDefaultGptAssistantDescription())
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptFiles.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptFiles.java
index 3ac6e3e..4923503 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptFiles.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptFiles.java
@@ -16,8 +16,6 @@
 
 @Slf4j
 public class ChatGptFiles extends ClientBase {
-    public static final String KEY_FILE_ID = "fileId";
-
     private final HttpClient httpClient = new HttpClient();
 
     public ChatGptFiles(Configuration config) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptVectorStore.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptVectorStore.java
new file mode 100644
index 0000000..5f9d51d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/chatgpt/ChatGptVectorStore.java
@@ -0,0 +1,51 @@
+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.mode.common.client.ClientBase;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.UriResourceLocatorStateful;
+import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.model.api.chatgpt.*;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Request;
+
+import java.net.URI;
+
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
+
+@Slf4j
+public class ChatGptVectorStore extends ClientBase {
+    public static final String KEY_VECTOR_STORE_ID = "vectorStoreId";
+
+    private final ChatGptHttpClient httpClient = new ChatGptHttpClient();
+    private final String fileId;
+    private final GerritChange change;
+
+    public ChatGptVectorStore(String fileId, Configuration config, GerritChange change) {
+        super(config);
+        this.fileId = fileId;
+        this.change = change;
+    }
+
+    public ChatGptResponse createVectorStore() {
+        Request request = vectorStoreCreateRequest();
+        log.debug("ChatGPT Create Vector Store request: {}", request);
+
+        ChatGptResponse createVectorStoreResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class);
+        log.info("Vector Store created: {}", createVectorStoreResponse);
+
+        return createVectorStoreResponse;
+    }
+
+    private Request vectorStoreCreateRequest() {
+        URI uri = URI.create(config.getGptDomain() + UriResourceLocatorStateful.vectorStoreCreateUri());
+        log.debug("ChatGPT Create Vector Store request URI: {}", uri);
+
+        ChatGptCreateVectorStoreRequest requestBody = ChatGptCreateVectorStoreRequest.builder()
+                .name(change.getProjectName())
+                .fileIds(new String[] { fileId })
+                .build();
+
+        log.debug("ChatGPT Create Vector Store request body: {}", requestBody);
+        return httpClient.createRequestFromJson(uri.toString(), config.getGptToken(), requestBody);
+    }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptFileIds.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptCreateVectorStoreRequest.java
similarity index 68%
rename from src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptFileIds.java
rename to src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptCreateVectorStoreRequest.java
index 2fa6fe5..aae613f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptFileIds.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptCreateVectorStoreRequest.java
@@ -1,12 +1,13 @@
 package com.googlesource.gerrit.plugins.chatgpt.mode.stateful.model.api.chatgpt;
 
 import com.google.gson.annotations.SerializedName;
-import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 
 @Data
-@AllArgsConstructor
-public class ChatGptFileIds {
+@Builder
+public class ChatGptCreateVectorStoreRequest {
+    private String name;
     @SerializedName("file_ids")
     private String[] fileIds;
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptToolResources.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptToolResources.java
index 8752d1a..66bd3db 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptToolResources.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/model/api/chatgpt/ChatGptToolResources.java
@@ -7,6 +7,13 @@
 @Data
 @AllArgsConstructor
 public class ChatGptToolResources {
-    @SerializedName("code_interpreter")
-    private ChatGptFileIds codeInterpreter;
-}
+    @SerializedName("file_search")
+    private VectorStoreIds fileSearch;
+
+    @Data
+    @AllArgsConstructor
+    public static class VectorStoreIds {
+        @SerializedName("vector_store_ids")
+        private String[] vectorStoreIds;
+    }
+}
\ No newline at end of file
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 5b07882..0dbaafe 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
@@ -37,6 +37,7 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ChatGptReviewStatefulTest extends ChatGptReviewTestBase {
     private static final String CHAT_GPT_FILE_ID = "file-TEST_FILE_ID";
+    private static final String CHAT_GPT_VECTOR_ID = "file-TEST_VECTOR_ID";
     private static final String CHAT_GPT_ASSISTANT_ID = "asst_TEST_ASSISTANT_ID";
     private static final String CHAT_GPT_THREAD_ID = "thread_TEST_THREAD_ID";
     private static final String CHAT_GPT_MESSAGE_ID = "msg_TEST_MESSAGE_ID";
@@ -83,6 +84,14 @@
                         .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
                         .withBody("{\"id\": " + CHAT_GPT_FILE_ID + "}")));
 
+        // Mock the behavior of the ChatGPT create-vector-store request
+        WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+                        + UriResourceLocatorStateful.vectorStoreCreateUri()).getPath()))
+                .willReturn(WireMock.aResponse()
+                        .withStatus(HTTP_OK)
+                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+                        .withBody("{\"id\": " + CHAT_GPT_VECTOR_ID + "}")));
+
         // Mock the behavior of the ChatGPT create-assistant request
         WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
                         + UriResourceLocatorStateful.assistantCreateUri()).getPath()))