Handle "message_creation" responses in JSON format
Occasionally, ChatGPT's "message_creation" response may include a
JSON-structured output embedded within a plain text message. This update
ensures these cases are properly managed.
Change-Id: I16ef37448cd65bbf10ba779636b74754ba2317f8
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
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 f0eb5f8..3d7b0b7 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
@@ -14,6 +14,10 @@
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.model.api.chatgpt.ChatGptThreadMessageResponse;
import lombok.extern.slf4j.Slf4j;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.JsonTextUtils.isJsonString;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.JsonTextUtils.unwrapJsonCode;
+
@Slf4j
@Singleton
public class ChatGptClientStateful extends ChatGptClient implements IChatGptClient {
@@ -87,6 +91,17 @@
ChatGptThreadMessageResponse threadMessageResponse = chatGptThreadMessage.retrieveMessage(
chatGptRun.getFirstStepDetails().getMessageCreation().getMessageId()
);
- return new ChatGptResponseContent(threadMessageResponse.getContent().get(0).getText().getValue());
+ String responseText = threadMessageResponse.getContent().get(0).getText().getValue();
+ if (responseText == null) {
+ throw new RuntimeException("ChatGPT thread message response content is null");
+ }
+ if (isJsonString(responseText)) {
+ return extractResponseContent(responseText);
+ }
+ return new ChatGptResponseContent(responseText);
+ }
+
+ private ChatGptResponseContent extractResponseContent(String responseText) {
+ return getGson().fromJson(unwrapJsonCode(responseText), ChatGptResponseContent.class);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/JsonTextUtils.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/JsonTextUtils.java
new file mode 100644
index 0000000..e03ca7b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/utils/JsonTextUtils.java
@@ -0,0 +1,20 @@
+package com.googlesource.gerrit.plugins.chatgpt.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.regex.Pattern;
+
+@Slf4j
+public class JsonTextUtils extends TextUtils {
+ private static final Pattern JSON_DELIMITED = Pattern.compile("^.*?" + CODE_DELIMITER + "json\\s*(.*)\\s*" +
+ CODE_DELIMITER + ".*$", Pattern.DOTALL);
+ private static final Pattern JSON_OBJECT = Pattern.compile("^\\{.*\\}$", Pattern.DOTALL);
+
+ public static String unwrapJsonCode(String text) {
+ return JSON_DELIMITED.matcher(text).replaceAll("$1");
+ }
+
+ public static boolean isJsonString(String text) {
+ return JSON_OBJECT.matcher(text).matches() || JSON_DELIMITED.matcher(text).matches();
+ }
+}
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 75be9c3..5b07882 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
@@ -204,7 +204,7 @@
}
@Test
- public void gptMentionedInCommentMessageResponse() throws RestApiException {
+ public void gptMentionedInCommentMessageResponseText() throws RestApiException {
String reviewMessageCommitMessage = getReviewMessage("__files/chatGptResponseRequestStateful.json", 0);
chatGptPromptStateful.setCommentEvent(true);
@@ -220,7 +220,33 @@
.willReturn(WireMock.aResponse()
.withStatus(HTTP_OK)
.withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
- .withBodyFile("chatGptResponseThreadMessage.json")));
+ .withBodyFile("chatGptResponseThreadMessageText.json")));
+
+ handleEventBasedOnType(true);
+
+ ArgumentCaptor<ReviewInput> captor = testRequestSent();
+ Assert.assertEquals(promptTagComments, requestContent);
+ Assert.assertEquals(reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME));
+ }
+
+ @Test
+ public void gptMentionedInCommentMessageResponseJson() throws RestApiException {
+ String reviewMessageCommitMessage = getReviewMessage("__files/chatGptResponseRequestStateful.json", 0);
+
+ chatGptPromptStateful.setCommentEvent(true);
+ // Mock the behavior of the ChatGPT retrieve-run-steps request
+ WireMock.stubFor(WireMock.get(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+ + UriResourceLocatorStateful.runStepsUri(CHAT_GPT_THREAD_ID, CHAT_GPT_RUN_ID)).getPath()))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBodyFile("chatGptResponseRequestMessageStateful.json")));
+ WireMock.stubFor(WireMock.get(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+ + UriResourceLocatorStateful.threadMessageRetrieveUri(CHAT_GPT_THREAD_ID, CHAT_GPT_MESSAGE_ID)).getPath()))
+ .willReturn(WireMock.aResponse()
+ .withStatus(HTTP_OK)
+ .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+ .withBodyFile("chatGptResponseThreadMessageJson.json")));
handleEventBasedOnType(true);
diff --git a/src/test/resources/__files/chatGptResponseThreadMessageJson.json b/src/test/resources/__files/chatGptResponseThreadMessageJson.json
new file mode 100644
index 0000000..0cabd66
--- /dev/null
+++ b/src/test/resources/__files/chatGptResponseThreadMessageJson.json
@@ -0,0 +1,12 @@
+{
+ "object": "thread.message",
+ "role": "assistant",
+ "content": [
+ {
+ "type": "text",
+ "text": {
+ "value": "```json\n{\n \"replies\": [\n {\n \"id\": 0,\n \"reply\": \"The commit message 'Corrected Indentation in Module-Class Retrieval Line' accurately represents the change made in the code.\"\n }\n ]\n}\n```"
+ }
+ }
+ ]
+}
diff --git a/src/test/resources/__files/chatGptResponseThreadMessage.json b/src/test/resources/__files/chatGptResponseThreadMessageText.json
similarity index 100%
rename from src/test/resources/__files/chatGptResponseThreadMessage.json
rename to src/test/resources/__files/chatGptResponseThreadMessageText.json