From 44208305716435bab5e834a3601009f0f1f8c978 Mon Sep 17 00:00:00 2001 From: Andrei Costescu Date: Sun, 22 Oct 2023 17:36:03 +0300 Subject: [PATCH 01/30] Implemented missing API for issue #366: No Retrieve file content api (#367) --- .../java/com/theokanning/openai/client/OpenAiApi.java | 4 ++++ .../com/theokanning/openai/service/OpenAiService.java | 4 ++++ .../java/com/theokanning/openai/service/FileTest.java | 10 ++++++++++ 3 files changed, 18 insertions(+) diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index 60ffdbf6..497dacd5 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -89,6 +89,10 @@ public interface OpenAiApi { @GET("/v1/files/{file_id}") Single retrieveFile(@Path("file_id") String fileId); + @Streaming + @GET("/v1/files/{file_id}/content") + Single retrieveFileContent(@Path("file_id") String fileId); + @POST("/v1/fine_tuning/jobs") Single createFineTuningJob(@Body FineTuningJobRequest request); diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index 7114531b..0296c15c 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -173,6 +173,10 @@ public File retrieveFile(String fileId) { return execute(api.retrieveFile(fileId)); } + public ResponseBody retrieveFileContent(String fileId) { + return execute(api.retrieveFileContent(fileId)); + } + public FineTuningJob createFineTuningJob(FineTuningJobRequest request) { return execute(api.createFineTuningJob(request)); } diff --git a/service/src/test/java/com/theokanning/openai/service/FileTest.java b/service/src/test/java/com/theokanning/openai/service/FileTest.java index 2b51aee3..74fe2084 100644 --- a/service/src/test/java/com/theokanning/openai/service/FileTest.java +++ b/service/src/test/java/com/theokanning/openai/service/FileTest.java @@ -7,6 +7,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.concurrent.TimeUnit; @@ -52,6 +55,13 @@ void retrieveFile() { @Test @Order(4) + void retrieveFileContent() throws IOException { + String fileBytesToString = service.retrieveFileContent(fileId).string(); + assertEquals(Files.readString(Path.of(filePath)), fileBytesToString); + } + + @Test + @Order(5) void deleteFile() { DeleteResult result = service.deleteFile(fileId); assertTrue(result.isDeleted()); From a4f2df805d30e98cd6a74ed1a1bf43ef73df88cb Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 22 Oct 2023 15:41:32 -0500 Subject: [PATCH 02/30] Fix java 8 compilation (#378) --- .../java/com/theokanning/openai/service/FileTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/service/src/test/java/com/theokanning/openai/service/FileTest.java b/service/src/test/java/com/theokanning/openai/service/FileTest.java index 74fe2084..51dd9407 100644 --- a/service/src/test/java/com/theokanning/openai/service/FileTest.java +++ b/service/src/test/java/com/theokanning/openai/service/FileTest.java @@ -8,8 +8,9 @@ import org.junit.jupiter.api.TestMethodOrder; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.concurrent.TimeUnit; @@ -21,7 +22,7 @@ public class FileTest { static String filePath = "src/test/resources/fine-tuning-data.jsonl"; String token = System.getenv("OPENAI_TOKEN"); - com.theokanning.openai.service.OpenAiService service = new OpenAiService(token); + OpenAiService service = new OpenAiService(token); static String fileId; @Test @@ -57,7 +58,8 @@ void retrieveFile() { @Order(4) void retrieveFileContent() throws IOException { String fileBytesToString = service.retrieveFileContent(fileId).string(); - assertEquals(Files.readString(Path.of(filePath)), fileBytesToString); + String contents = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); + assertEquals(contents, fileBytesToString); } @Test From 44cba2e92757e7281ba4a3c45a91b89e56876683 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 22 Oct 2023 16:02:15 -0500 Subject: [PATCH 03/30] Add fine-tuning api models to json test (#379) --- .../openai/fine_tuning/FineTuningEvent.java | 5 +++++ .../java/com/theokanning/openai/JsonTest.java | 8 ++++++-- .../resources/fixtures/FineTuningEvent.json | 8 ++++++++ .../resources/fixtures/FineTuningJob.json | 19 +++++++++++++++++++ .../fixtures/FineTuningJobRequest.json | 9 +++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 api/src/test/resources/fixtures/FineTuningEvent.json create mode 100644 api/src/test/resources/fixtures/FineTuningJob.json create mode 100644 api/src/test/resources/fixtures/FineTuningJobRequest.json diff --git a/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningEvent.java b/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningEvent.java index 097bf7ce..c653c048 100644 --- a/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningEvent.java +++ b/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningEvent.java @@ -15,6 +15,11 @@ public class FineTuningEvent { */ String object; + /** + * The ID of the fine-tuning event. + */ + String id; + /** * The creation time in epoch seconds. */ diff --git a/api/src/test/java/com/theokanning/openai/JsonTest.java b/api/src/test/java/com/theokanning/openai/JsonTest.java index 09d89f5b..68b98155 100644 --- a/api/src/test/java/com/theokanning/openai/JsonTest.java +++ b/api/src/test/java/com/theokanning/openai/JsonTest.java @@ -12,10 +12,11 @@ import com.theokanning.openai.embedding.EmbeddingResult; import com.theokanning.openai.engine.Engine; import com.theokanning.openai.file.File; +import com.theokanning.openai.fine_tuning.FineTuningEvent; +import com.theokanning.openai.fine_tuning.FineTuningJob; +import com.theokanning.openai.fine_tuning.FineTuningJobRequest; import com.theokanning.openai.finetune.FineTuneEvent; import com.theokanning.openai.finetune.FineTuneResult; -import com.theokanning.openai.image.CreateImageEditRequest; -import com.theokanning.openai.image.CreateImageRequest; import com.theokanning.openai.image.ImageResult; import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; @@ -43,6 +44,9 @@ public class JsonTest { File.class, FineTuneEvent.class, FineTuneResult.class, + FineTuningEvent.class, + FineTuningJob.class, + FineTuningJobRequest.class, ImageResult.class, TranscriptionResult.class, TranslationResult.class, diff --git a/api/src/test/resources/fixtures/FineTuningEvent.json b/api/src/test/resources/fixtures/FineTuningEvent.json new file mode 100644 index 00000000..cd07faa5 --- /dev/null +++ b/api/src/test/resources/fixtures/FineTuningEvent.json @@ -0,0 +1,8 @@ +{ + "object": "fine_tuning.job.event", + "id": "ft-event-ddTJfwuMVpfLXseO0Am0Gqjm", + "created_at": 1692407401, + "level": "info", + "message": "Fine tuning job successfully completed", + "type": "message" +} diff --git a/api/src/test/resources/fixtures/FineTuningJob.json b/api/src/test/resources/fixtures/FineTuningJob.json new file mode 100644 index 00000000..51eb64c1 --- /dev/null +++ b/api/src/test/resources/fixtures/FineTuningJob.json @@ -0,0 +1,19 @@ +{ + "id": "ftjob-abc123", + "object": "fine_tuning.job", + "model": "davinci-002", + "status": "succeeded", + "hyperparameters": { + "n_epochs": 4 + }, + "created_at": 1692661014, + "finished_at": 1692661190, + "fine_tuned_model": "ft:davinci-002:my-org:custom_suffix:7q8mpxmy", + "organization_id": "org-123", + "training_file": "file-abc123", + "result_files": [ + "file-abc123" + ], + "validation_file": "validation-file", + "trained_tokens": 5768 +} diff --git a/api/src/test/resources/fixtures/FineTuningJobRequest.json b/api/src/test/resources/fixtures/FineTuningJobRequest.json new file mode 100644 index 00000000..45e79f73 --- /dev/null +++ b/api/src/test/resources/fixtures/FineTuningJobRequest.json @@ -0,0 +1,9 @@ +{ + "model": "davinci-002", + "validation_file": "file-abc123", + "training_file": "file-abc123", + "hyperparameters": { + "n_epochs": 4 + }, + "suffix": "test" +} From fe4295837a1bdbe68a8e2a13b7c72878956a02b4 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 22 Oct 2023 16:06:56 -0500 Subject: [PATCH 04/30] Fix fine-tuning test (#380) The first status changed from created to validating_files Since this is working either way, just check that it has an id instead --- .../java/com/theokanning/openai/service/FineTuningTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java b/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java index 288b1f41..8bc3cd57 100644 --- a/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java +++ b/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java @@ -4,7 +4,6 @@ import com.theokanning.openai.fine_tuning.FineTuningJob; import com.theokanning.openai.fine_tuning.FineTuningJobRequest; import com.theokanning.openai.fine_tuning.Hyperparameters; -import com.theokanning.openai.finetune.FineTuneResult; import org.junit.jupiter.api.*; import java.util.List; @@ -49,7 +48,7 @@ void createFineTuningJob() { FineTuningJob fineTuningJob = service.createFineTuningJob(request); fineTuningJobId = fineTuningJob.getId(); - assertEquals("created", fineTuningJob.getStatus()); + assertNotNull(fineTuningJob); } @Test From 761350a89ff819828bb07852cbe13e7c077be0af Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 22 Oct 2023 16:24:29 -0500 Subject: [PATCH 05/30] Add File status fields (#381) Fixes https://github.com/TheoKanning/openai-java/issues/355 --- .../main/java/com/theokanning/openai/file/File.java | 12 ++++++++++++ api/src/test/resources/fixtures/File.json | 6 ++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/theokanning/openai/file/File.java b/api/src/main/java/com/theokanning/openai/file/File.java index 36107e73..07708f8e 100644 --- a/api/src/main/java/com/theokanning/openai/file/File.java +++ b/api/src/main/java/com/theokanning/openai/file/File.java @@ -41,4 +41,16 @@ public class File { * Description of the file's purpose. */ String purpose; + + /** + * The current status of the file, which can be either uploaded, processed, pending, error, deleting or deleted. + */ + String status; + + /** + * Additional details about the status of the file. + * If the file is in the error state, this will include a message describing the error. + */ + @JsonProperty("status_details") + String statusDetails; } diff --git a/api/src/test/resources/fixtures/File.json b/api/src/test/resources/fixtures/File.json index 8ede985d..9fa0c564 100644 --- a/api/src/test/resources/fixtures/File.json +++ b/api/src/test/resources/fixtures/File.json @@ -4,5 +4,7 @@ "bytes": 175, "created_at": 1613677385, "filename": "train.jsonl", - "purpose": "search" -} \ No newline at end of file + "purpose": "search", + "status": "error", + "status_details": "File is too large." +} From e7635c3cf07eb9a423d0bdaad5e41b65f0dbe9cd Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 22 Oct 2023 16:27:54 -0500 Subject: [PATCH 06/30] Update version to 0.16.1 (#382) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 84d38815..85a94ec8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.theokanning.openai-gpt3-java -VERSION_NAME=0.16.0 +VERSION_NAME=0.16.1 POM_URL=https://github.com/theokanning/openai-java POM_SCM_URL=https://github.com/theokanning/openai-java From dc51a5bbf9d4ea311b48b652f7aff0ead7c27ec1 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 22 Oct 2023 16:50:25 -0500 Subject: [PATCH 07/30] Catch errors when deleting file in FineTuningTest (#383) Thsi is failing repeatedly and it's not a big deal if we don't clean this up. --- .../java/com/theokanning/openai/service/FineTuningTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java b/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java index 8bc3cd57..81a8e808 100644 --- a/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java +++ b/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java @@ -30,7 +30,11 @@ static void setup() throws Exception { @AfterAll static void teardown() { - service.deleteFile(fileId); + try { + service.deleteFile(fileId); + } catch (Exception e) { + // ignore + } } @Test From f1e587d685419765e9ab81c763610847f8fa0317 Mon Sep 17 00:00:00 2001 From: vacuityv Date: Mon, 13 Nov 2023 05:17:36 +0800 Subject: [PATCH 08/30] feat(image): new feature for dalle api. (#393) You can set model, quality and style now. --- .../openai/image/CreateImageEditRequest.java | 5 +++++ .../openai/image/CreateImageRequest.java | 21 ++++++++++++++++--- .../image/CreateImageVariationRequest.java | 5 +++++ .../com/theokanning/openai/image/Image.java | 6 ++++++ .../openai/service/OpenAiService.java | 2 ++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/theokanning/openai/image/CreateImageEditRequest.java b/api/src/main/java/com/theokanning/openai/image/CreateImageEditRequest.java index 72046953..7d37f689 100644 --- a/api/src/main/java/com/theokanning/openai/image/CreateImageEditRequest.java +++ b/api/src/main/java/com/theokanning/openai/image/CreateImageEditRequest.java @@ -21,6 +21,11 @@ public class CreateImageEditRequest { @NonNull String prompt; + /** + * The model to use for image generation. Only dall-e-2 is supported at this time. Defaults to dall-e-2. + */ + String model; + /** * The number of images to generate. Must be between 1 and 10. Defaults to 1. */ diff --git a/api/src/main/java/com/theokanning/openai/image/CreateImageRequest.java b/api/src/main/java/com/theokanning/openai/image/CreateImageRequest.java index b8a1d05d..13672c24 100644 --- a/api/src/main/java/com/theokanning/openai/image/CreateImageRequest.java +++ b/api/src/main/java/com/theokanning/openai/image/CreateImageRequest.java @@ -17,18 +17,28 @@ public class CreateImageRequest { /** - * A text description of the desired image(s). The maximum length in 1000 characters. + * A text description of the desired image(s). The maximum length is 1000 characters for dall-e-2 and 4000 characters for dall-e-3. */ @NonNull String prompt; /** - * The number of images to generate. Must be between 1 and 10. Defaults to 1. + * The model to use for image generation. Defaults to "dall-e-2". + */ + String model; + + /** + * The number of images to generate. Must be between 1 and 10. For dall-e-3, only n=1 is supported. Defaults to 1. */ Integer n; /** - * The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024". Defaults to "1024x1024". + * The quality of the image that will be generated. "hd" creates images with finer details and greater consistency across the image. This param is only supported for dall-e-3. Defaults to "standard". + */ + String quality; + + /** + * The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024 for dall-e-2. Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models. Defaults to 1024x1024. */ String size; @@ -38,6 +48,11 @@ public class CreateImageRequest { @JsonProperty("response_format") String responseFormat; + /** + * The style of the generated images. Must be one of vivid or natural. Vivid causes the model to lean towards generating hyper-real and dramatic images. Natural causes the model to produce more natural, less hyper-real looking images. This param is only supported for dall-e-3. Defaults to vivid. + */ + String style; + /** * A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. */ diff --git a/api/src/main/java/com/theokanning/openai/image/CreateImageVariationRequest.java b/api/src/main/java/com/theokanning/openai/image/CreateImageVariationRequest.java index 2bc0c5d1..f16f613d 100644 --- a/api/src/main/java/com/theokanning/openai/image/CreateImageVariationRequest.java +++ b/api/src/main/java/com/theokanning/openai/image/CreateImageVariationRequest.java @@ -20,6 +20,11 @@ public class CreateImageVariationRequest { */ Integer n; + /** + * The model to use for image generation. Only dall-e-2 is supported at this time. Defaults to dall-e-2. + */ + String model; + /** * The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024". Defaults to "1024x1024". */ diff --git a/api/src/main/java/com/theokanning/openai/image/Image.java b/api/src/main/java/com/theokanning/openai/image/Image.java index e3214844..6b8391ed 100644 --- a/api/src/main/java/com/theokanning/openai/image/Image.java +++ b/api/src/main/java/com/theokanning/openai/image/Image.java @@ -21,4 +21,10 @@ public class Image { */ @JsonProperty("b64_json") String b64Json; + + /** + * The prompt that was used to generate the image, if there was any revision to the prompt. + */ + @JsonProperty("revised_prompt") + String revisedPrompt; } diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index 0296c15c..cedbd805 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -250,6 +250,7 @@ public ImageResult createImageEdit(CreateImageEditRequest request, java.io.File .setType(MediaType.get("multipart/form-data")) .addFormDataPart("prompt", request.getPrompt()) .addFormDataPart("size", request.getSize()) + .addFormDataPart("model", request.getModel()) .addFormDataPart("response_format", request.getResponseFormat()) .addFormDataPart("image", "image", imageBody); @@ -276,6 +277,7 @@ public ImageResult createImageVariation(CreateImageVariationRequest request, jav MultipartBody.Builder builder = new MultipartBody.Builder() .setType(MediaType.get("multipart/form-data")) .addFormDataPart("size", request.getSize()) + .addFormDataPart("model", request.getModel()) .addFormDataPart("response_format", request.getResponseFormat()) .addFormDataPart("image", "image", imageBody); From 0ec5a9ee303f91f6d66da9e91ab726ca27f28534 Mon Sep 17 00:00:00 2001 From: Daniel Faria Date: Sun, 12 Nov 2023 18:22:08 -0300 Subject: [PATCH 09/30] add support to audio/createSpeech API (#392) --- .../openai/audio/CreateSpeechRequest.java | 45 +++++++++++++++++++ .../theokanning/openai/client/OpenAiApi.java | 4 ++ .../openai/service/OpenAiService.java | 5 +++ .../theokanning/openai/service/AudioTest.java | 19 ++++++++ 4 files changed, 73 insertions(+) create mode 100644 api/src/main/java/com/theokanning/openai/audio/CreateSpeechRequest.java diff --git a/api/src/main/java/com/theokanning/openai/audio/CreateSpeechRequest.java b/api/src/main/java/com/theokanning/openai/audio/CreateSpeechRequest.java new file mode 100644 index 00000000..6d2e69ac --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/audio/CreateSpeechRequest.java @@ -0,0 +1,45 @@ +package com.theokanning.openai.audio; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class CreateSpeechRequest { + + /** + * The name of the model to use. + */ + @NonNull + String model; + + /** + * The text to generate audio for. The maximum length is 4096 characters. + */ + @NonNull + String input; + + /** + * The voice to use when generating the audio. + */ + @NonNull + String voice; + + /** + * The format to audio in. Supported formats are mp3, opus, aac, and flac. Defaults to mp3. + */ + @JsonProperty("response_format") + String responseFormat; + + /** + * The speed of the generated audio. Select a value from 0.25 to 4.0. Defaults to 1.0. + */ + Double speed; +} diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index 497dacd5..f2665ee2 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -2,6 +2,7 @@ import com.theokanning.openai.DeleteResult; import com.theokanning.openai.OpenAiResponse; +import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.TranscriptionResult; import com.theokanning.openai.audio.TranslationResult; import com.theokanning.openai.billing.BillingUsage; @@ -149,6 +150,9 @@ public interface OpenAiApi { @POST("/v1/audio/translations") Single createTranslation(@Body RequestBody requestBody); + @POST("/v1/audio/speech") + Single createSpeech(@Body CreateSpeechRequest requestBody); + @POST("/v1/moderations") Single createModeration(@Body ModerationRequest request); diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index cedbd805..ea59417e 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -8,6 +8,7 @@ import com.theokanning.openai.DeleteResult; import com.theokanning.openai.OpenAiError; import com.theokanning.openai.OpenAiHttpException; +import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.CreateTranscriptionRequest; import com.theokanning.openai.audio.CreateTranslationRequest; import com.theokanning.openai.audio.TranscriptionResult; @@ -347,6 +348,10 @@ public ModerationResult createModeration(ModerationRequest request) { return execute(api.createModeration(request)); } + public ResponseBody createSpeech(CreateSpeechRequest request) { + return execute(api.createSpeech(request)); + } + /** * Calls the Open AI api, returns the response, and parses error messages if the request fails */ diff --git a/service/src/test/java/com/theokanning/openai/service/AudioTest.java b/service/src/test/java/com/theokanning/openai/service/AudioTest.java index d5a54a23..9cb083de 100644 --- a/service/src/test/java/com/theokanning/openai/service/AudioTest.java +++ b/service/src/test/java/com/theokanning/openai/service/AudioTest.java @@ -1,13 +1,18 @@ package com.theokanning.openai.service; +import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.CreateTranscriptionRequest; import com.theokanning.openai.audio.CreateTranslationRequest; import com.theokanning.openai.audio.TranscriptionResult; import com.theokanning.openai.audio.TranslationResult; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.time.Duration; +import okhttp3.MediaType; +import okhttp3.ResponseBody; + import static org.junit.jupiter.api.Assertions.*; @@ -69,4 +74,18 @@ void createTranslationVerbose() { assertTrue(result.getDuration() > 0); assertEquals(1, result.getSegments().size()); } + + @Test + void createSpeech() throws IOException { + CreateSpeechRequest createSpeechRequest = CreateSpeechRequest.builder() + .model("tts-1") + .input("Hello World.") + .voice("alloy") + .build(); + + final ResponseBody speech = service.createSpeech(createSpeechRequest); + assertNotNull(speech); + assertEquals(MediaType.get("audio/mpeg"), speech.contentType()); + assertTrue(speech.bytes().length > 0); + } } From fb62307a61f35075d198cd8298be8404ba2a0173 Mon Sep 17 00:00:00 2001 From: Remy Ohajinwa Date: Sun, 12 Nov 2023 21:32:56 +0000 Subject: [PATCH 10/30] Support Assistants (#395) * #390 Create Assistant #390 Create Assistant * #390 Retrieve Assistant * #390 Modify Assistant * RemyOhajinwa#390 Delete Assistant * RemyOhajinwa#390 List Assistants * RemyOhajinwa#390 Create Assistant File * RemyOhajinwa#390 Assistant File * RemyOhajinwa#390 Assistant File * RemyOhajinwa#390 Assistant File * RemyOhajinwa#390 Assistant File * Remove DeleteAssistantResult import --------- Co-authored-by: Remy Ohajinwa Co-authored-by: Theo Kanning --- .../openai/assistants/Assistant.java | 24 ++ .../openai/assistants/AssistantBase.java | 55 +++++ .../openai/assistants/AssistantFile.java | 30 +++ .../assistants/AssistantFileRequest.java | 17 ++ .../openai/assistants/AssistantRequest.java | 6 + .../openai/assistants/AssistantSortOrder.java | 12 + .../openai/assistants/AssistantToolsEnum.java | 15 ++ .../openai/assistants/ListAssistant.java | 16 ++ .../assistants/ListAssistantQueryRequest.java | 39 ++++ .../theokanning/openai/assistants/Tool.java | 12 + .../openai/utils/TikTokensUtil.java | 7 +- .../theokanning/openai/client/OpenAiApi.java | 43 ++++ .../openai/service/OpenAiService.java | 46 ++++ .../openai/service/AssistantTest.java | 214 ++++++++++++++++++ .../test/resources/assistant-file-data.json | 1 + .../src/test/resources/assistants-data.html | 1 + 16 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/com/theokanning/openai/assistants/Assistant.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/AssistantBase.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/AssistantFile.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/AssistantFileRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/AssistantSortOrder.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/AssistantToolsEnum.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/assistants/Tool.java create mode 100644 service/src/test/java/com/theokanning/openai/service/AssistantTest.java create mode 100644 service/src/test/resources/assistant-file-data.json create mode 100644 service/src/test/resources/assistants-data.html diff --git a/api/src/main/java/com/theokanning/openai/assistants/Assistant.java b/api/src/main/java/com/theokanning/openai/assistants/Assistant.java new file mode 100644 index 00000000..111ef169 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/Assistant.java @@ -0,0 +1,24 @@ +package com.theokanning.openai.assistants; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class Assistant extends AssistantBase { + + /** + * The identifier, which can be referenced in API endpoints. + */ + String id; + + /** + * The object type which is always 'assistant' + */ + String object; + + /** + * The Unix timestamp(in seconds) for when the assistant was created + */ + @JsonProperty("created_at") + Integer createdAt; +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantBase.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantBase.java new file mode 100644 index 00000000..d771bd30 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantBase.java @@ -0,0 +1,55 @@ +package com.theokanning.openai.assistants; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +import java.util.List; +import java.util.Map; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AssistantBase { + + /** + * ID of the model to use + */ + @NonNull + String model; + + /** + * The name of the assistant. The maximum length is 256 + */ + String name; + + /** + * The description of the assistant. + */ + String description; + + /** + * The system instructions that the assistant uses. + */ + String instructions; + + /** + * A list of tools enabled on the assistant. + */ + List tools; + + /** + * A list of file IDs attached to this assistant. + */ + @JsonProperty("file_ids") + List fields; + + /** + * Set of 16 key-value pairs that can be attached to an object. + */ + Map metadata; +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantFile.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantFile.java new file mode 100644 index 00000000..c5d551a9 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantFile.java @@ -0,0 +1,30 @@ +package com.theokanning.openai.assistants; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class AssistantFile { + + /** + * The identifier of the Assistant File + */ + String id; + + /** + * The object type, which is always assistant.file. + */ + String object; + + /** + * The Unix timestamp (in seconds) for when the assistant file was created. + */ + @JsonProperty("created_at") + String createdAt; + + /** + * The assistant ID that the file is attached to + */ + @JsonProperty("assistant_id") + String assistantId; +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantFileRequest.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantFileRequest.java new file mode 100644 index 00000000..98ee009f --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantFileRequest.java @@ -0,0 +1,17 @@ +package com.theokanning.openai.assistants; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AssistantFileRequest { + + @JsonProperty("file_id") + String fileId; +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java new file mode 100644 index 00000000..dc0a66df --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java @@ -0,0 +1,6 @@ +package com.theokanning.openai.assistants; + + +public class AssistantRequest extends AssistantBase { + +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantSortOrder.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantSortOrder.java new file mode 100644 index 00000000..9f784a66 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantSortOrder.java @@ -0,0 +1,12 @@ +package com.theokanning.openai.assistants; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum AssistantSortOrder { + + @JsonProperty("asc") + ASC, + + @JsonProperty("desc") + DESC +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantToolsEnum.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantToolsEnum.java new file mode 100644 index 00000000..f6b5021d --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantToolsEnum.java @@ -0,0 +1,15 @@ +package com.theokanning.openai.assistants; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum AssistantToolsEnum { + + @JsonProperty("code_interpreter") + CODE_INTERPRETER, + + @JsonProperty("function") + FUNCTION, + + @JsonProperty("retrieval") + RETRIEVAL +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java b/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java new file mode 100644 index 00000000..8478a547 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java @@ -0,0 +1,16 @@ +package com.theokanning.openai.assistants; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.OpenAiResponse; + +public class ListAssistant extends OpenAiResponse { + + @JsonProperty("first_id") + String firstId; + + @JsonProperty("last_id") + String lastId; + + @JsonProperty("has_more") + boolean hasMore; +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java b/api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java new file mode 100644 index 00000000..3e5f3c68 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java @@ -0,0 +1,39 @@ +package com.theokanning.openai.assistants; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ListAssistantQueryRequest { + /** + * A limit on the number of objects to be returned. + * Limit can range between 1 and 100, and the default is 20 + */ + + Integer limit; + + /** + * Sort order by the 'created_at' timestamp of the objects. + * 'asc' for ascending order and 'desc' for descending order. + */ + AssistantSortOrder order; + + /** + * A cursor for use in pagination. after is an object ID that defines your place in the list. + * For instance, if you make a list request and receive 100 objects, ending with obj_foo, + * your subsequent call can include after=obj_foo in order to fetch the next page of the list + */ + String after; + + /** + * A cursor for use in pagination. before is an object ID that defines your place in the list. + * For instance, if you make a list request and receive 100 objects, ending with obj_foo, + * your subsequent call can include before=obj_foo in order to fetch the previous page of the list. + */ + String before; +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/Tool.java b/api/src/main/java/com/theokanning/openai/assistants/Tool.java new file mode 100644 index 00000000..00027d72 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/Tool.java @@ -0,0 +1,12 @@ +package com.theokanning.openai.assistants; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Tool { + AssistantToolsEnum type; +} diff --git a/api/src/main/java/com/theokanning/openai/utils/TikTokensUtil.java b/api/src/main/java/com/theokanning/openai/utils/TikTokensUtil.java index c30871f7..0a50907e 100644 --- a/api/src/main/java/com/theokanning/openai/utils/TikTokensUtil.java +++ b/api/src/main/java/com/theokanning/openai/utils/TikTokensUtil.java @@ -32,6 +32,7 @@ public class TikTokensUtil { modelMap.put(ModelEnum.GPT_4_32K.getName(), registry.getEncodingForModel(ModelType.GPT_4)); modelMap.put(ModelEnum.GPT_4_32K_0314.getName(), registry.getEncodingForModel(ModelType.GPT_4)); modelMap.put(ModelEnum.GPT_4_0314.getName(), registry.getEncodingForModel(ModelType.GPT_4)); + modelMap.put(ModelEnum.GPT_4_1106_preview.getName(), registry.getEncodingForModel(ModelType.GPT_4)); } /** @@ -261,7 +262,11 @@ public enum ModelEnum { * Temporary model, not recommended for use. */ GPT_4_32K_0314("gpt-4-32k-0314"), - ; + + /** + * Temporary model, not recommended for use. + */ + GPT_4_1106_preview("gpt-4-1106-preview"); private String name; } diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index f2665ee2..0bfc03aa 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -2,6 +2,12 @@ import com.theokanning.openai.DeleteResult; import com.theokanning.openai.OpenAiResponse; +import com.theokanning.openai.assistants.AssistantBase; +import com.theokanning.openai.assistants.Assistant; +import com.theokanning.openai.assistants.AssistantFile; +import com.theokanning.openai.assistants.AssistantFileRequest; +import com.theokanning.openai.assistants.ListAssistant; +import com.theokanning.openai.assistants.ListAssistantQueryRequest; import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.TranscriptionResult; import com.theokanning.openai.audio.TranslationResult; @@ -36,6 +42,7 @@ import retrofit2.http.*; import java.time.LocalDate; +import java.util.Map; public interface OpenAiApi { @@ -185,4 +192,40 @@ public interface OpenAiApi { @GET("v1/dashboard/billing/usage") Single billingUsage(@Query("start_date") LocalDate starDate, @Query("end_date") LocalDate endDate); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @POST("/v1/assistants") + Single createAssistant(@Body AssistantBase request); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/assistants/{assistant_id}") + Single retrieveAssistant(@Path("assistant_id") String assistantId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @POST("/v1/assistants/{assistant_id}") + Single modifyAssistant(@Path("assistant_id") String assistantId, @Body AssistantBase request); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @DELETE("/v1/assistants/{assistant_id}") + Single deleteAssistant(@Path("assistant_id") String assistantId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/assistants") + Single> listAssistants(@QueryMap Map filterRequest); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @POST("/v1/assistants/{assistant_id}/files") + Single createAssistantFile(@Path("assistant_id") String assistantId, @Body AssistantFileRequest fileRequest); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/assistants/{assistant_id}/files/{file_id}") + Single retrieveAssistantFile(@Path("assistant_id") String assistantId, @Path("file_id") String fileId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @DELETE("/v1/assistants/{assistant_id}/files/{file_id}") + Single deleteAssistantFile(@Path("assistant_id") String assistantId, @Path("file_id") String fileId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/assistants/{assistant_id}/files") + Single> listAssistantFiles(@Path("assistant_id") String assistantId, @QueryMap Map filterRequest); } diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index ea59417e..0a7d9459 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -1,6 +1,7 @@ package com.theokanning.openai.service; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; @@ -8,6 +9,12 @@ import com.theokanning.openai.DeleteResult; import com.theokanning.openai.OpenAiError; import com.theokanning.openai.OpenAiHttpException; +import com.theokanning.openai.assistants.Assistant; +import com.theokanning.openai.assistants.AssistantBase; +import com.theokanning.openai.assistants.AssistantFile; +import com.theokanning.openai.assistants.AssistantFileRequest; +import com.theokanning.openai.assistants.ListAssistant; +import com.theokanning.openai.assistants.ListAssistantQueryRequest; import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.CreateTranscriptionRequest; import com.theokanning.openai.audio.CreateTranslationRequest; @@ -53,6 +60,7 @@ import java.time.Duration; import java.time.LocalDate; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -348,6 +356,44 @@ public ModerationResult createModeration(ModerationRequest request) { return execute(api.createModeration(request)); } + public Assistant createAssistant(AssistantBase request) { + return execute(api.createAssistant(request)); + } + + public Assistant retrieveAssistant(String assistantId) { + return execute(api.retrieveAssistant(assistantId)); + } + + public Assistant modifyAssistant(String assistantId, AssistantBase request) { + return execute(api.modifyAssistant(assistantId, request)); + } + + public DeleteResult deleteAssistant(String assistantId) { + return execute(api.deleteAssistant(assistantId)); + } + + public ListAssistant listAssistants(ListAssistantQueryRequest filterRequest) { + Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() {}); + return execute(api.listAssistants(queryParameters)); + } + + public AssistantFile createAssistantFile(String assistantId, AssistantFileRequest fileRequest) { + return execute(api.createAssistantFile(assistantId, fileRequest)); + } + + public AssistantFile retrieveAssistantFile(String assistantId, String fileId) { + return execute(api.retrieveAssistantFile(assistantId, fileId)); + } + + public DeleteResult deleteAssistantFile(String assistantId, String fileId) { + return execute(api.deleteAssistantFile(assistantId, fileId)); + } + + public ListAssistant listAssistantFiles(String assistantId, ListAssistantQueryRequest filterRequest) { + Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() {}); + return execute(api.listAssistantFiles(assistantId, queryParameters)); + } + public ResponseBody createSpeech(CreateSpeechRequest request) { return execute(api.createSpeech(request)); } diff --git a/service/src/test/java/com/theokanning/openai/service/AssistantTest.java b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java new file mode 100644 index 00000000..4f21b739 --- /dev/null +++ b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java @@ -0,0 +1,214 @@ +package com.theokanning.openai.service; + +import com.theokanning.openai.DeleteResult; +import com.theokanning.openai.assistants.Assistant; +import com.theokanning.openai.assistants.AssistantBase; +import com.theokanning.openai.assistants.AssistantFile; +import com.theokanning.openai.assistants.AssistantFileRequest; +import com.theokanning.openai.assistants.AssistantRequest; +import com.theokanning.openai.assistants.AssistantSortOrder; +import com.theokanning.openai.assistants.AssistantToolsEnum; +import com.theokanning.openai.assistants.ListAssistant; +import com.theokanning.openai.assistants.ListAssistantQueryRequest; +import com.theokanning.openai.assistants.Tool; +import com.theokanning.openai.file.File; +import com.theokanning.openai.utils.TikTokensUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class AssistantTest { + public static final String MATH_TUTOR = "Math Tutor"; + public static final String ASSISTANT_INSTRUCTION = "You are a personal Math Tutor."; + + static String token = System.getenv("OPENAI_TOKEN");; + + static OpenAiService service = new OpenAiService(token); + + + + @Test + void retrieveAssistant() { + Assistant createAssistantResponse = createAndValidateAssistant(); + + Assistant retrieveAssistantResponse = service.retrieveAssistant(createAssistantResponse.getId()); + validateAssistantResponse(retrieveAssistantResponse); + } + + @Test + void modifyAssistant() { + Assistant createAssistantResponse = createAndValidateAssistant(); + + String modifiedName = MATH_TUTOR + " Modified"; + createAssistantResponse.setName(modifiedName);//modify a field + + Assistant modifiedAssistantResponse = service.modifyAssistant(createAssistantResponse.getId(), createAssistantResponse); + assertNotNull(modifiedAssistantResponse); + assertEquals(modifiedName, modifiedAssistantResponse.getName()); + } + + @Test + void deleteAssistant() { + Assistant createAssistantResponse = createAndValidateAssistant(); + + DeleteResult deletedAssistant = service.deleteAssistant(createAssistantResponse.getId()); + + assertNotNull(deletedAssistant); + assertEquals(createAssistantResponse.getId(), deletedAssistant.getId()); + assertTrue(deletedAssistant.isDeleted()); + } + + @Test + void listAssistants() { + ListAssistant assistants = service.listAssistants(ListAssistantQueryRequest.builder().build()); + + assertNotNull(assistants); + // this should be more than 2 depending on how many times createAndValidateAssistant method is called + assertTrue(assistants.getData().size() > 1); + } + + @Test + void listAssistants_returnsTwoAssistants() { + int expectedLimit = 2; + ListAssistantQueryRequest queryResult = ListAssistantQueryRequest.builder() + .limit(expectedLimit) + .build(); + + ListAssistant assistants = service.listAssistants(queryResult); + + List data = validateListAssistants(assistants); + assertEquals(expectedLimit, data.size()); + } + + + + @Test + void listAssistants_returnsAscSortedAssistants() { + int expectedLimit = 3; + + ListAssistantQueryRequest queryResult = ListAssistantQueryRequest.builder() + .limit(expectedLimit) + .order(AssistantSortOrder.ASC) + .build(); + + ListAssistant assistants = service.listAssistants(queryResult); + + List data = validateListAssistants(assistants); + + boolean firstTwoAscending = data.get(0).getCreatedAt() <= data.get(1).getCreatedAt(); + boolean lastTwoAscending = data.get(1).getCreatedAt() <= data.get(2).getCreatedAt(); + assertTrue(firstTwoAscending && lastTwoAscending); + } + + @Test + void listAssistants_returnsDescSortedAssistants() { + int expectedLimit = 3; + + ListAssistantQueryRequest queryResult = ListAssistantQueryRequest.builder() + .limit(expectedLimit) + .order(AssistantSortOrder.DESC) + .build(); + + ListAssistant assistants = service.listAssistants(queryResult); + + List data = validateListAssistants(assistants); + + boolean firstTwoDescending = data.get(0).getCreatedAt() >= data.get(1).getCreatedAt(); + boolean lastTwoDescending = data.get(1).getCreatedAt() >= data.get(2).getCreatedAt(); + assertTrue(firstTwoDescending && lastTwoDescending); + } + + @Test + void createAssistantFile() { + File uploadedFile = uploadAssistantFile(); + + Assistant assistant = createAndValidateAssistant(); + + AssistantFile assistantFile = service.createAssistantFile(assistant.getId(), new AssistantFileRequest(uploadedFile.getId())); + + assertNotNull(assistantFile); + assertEquals(uploadedFile.getId(), assistantFile.getId()); + assertEquals(assistant.getId(), assistantFile.getAssistantId()); + } + + + + @Test + void retrieveAssistantFile() { + //TODO + //There is a bug with uploading assistant files https://community.openai.com/t/possible-bug-with-agent-creation-php-file-upload/484490/5 + //So this would have to be done later + } + + @Test + void deleteAssistantFile() { + //TODO + //There is a bug with uploading assistant files https://community.openai.com/t/possible-bug-with-agent-creation-php-file-upload/484490/5 + //So this would have to be done later + } + + @Test + void listAssistantFiles() { + //TODO + //There is a bug with uploading assistant files https://community.openai.com/t/possible-bug-with-agent-creation-php-file-upload/484490/5 + //So this would have to be done later + } + + @AfterAll + static void clean() { + //Clean up all data created during this test + ListAssistantQueryRequest queryFilter = ListAssistantQueryRequest.builder() + .limit(100) + .build(); + ListAssistant assistantListAssistant = service.listAssistants(queryFilter); + assistantListAssistant.getData().forEach(assistant ->{ + service.deleteAssistant(assistant.getId()); + }); + } + + private static File uploadAssistantFile() { + String filePath = "src/test/resources/assistants-data.html"; + return service.uploadFile("assistants", filePath); + } + + private static Assistant createAndValidateAssistant() { + AssistantBase assistantRequest = assistantStub(); + Assistant createAssistantResponse = service.createAssistant(assistantRequest); + validateAssistantResponse(createAssistantResponse); + + return createAssistantResponse; + } + + + private static AssistantBase assistantStub() { + return AssistantRequest.builder() + .model(TikTokensUtil.ModelEnum.GPT_4_1106_preview.getName()) + .name(MATH_TUTOR) + .instructions(ASSISTANT_INSTRUCTION) + .tools(Collections.singletonList(new Tool(AssistantToolsEnum.CODE_INTERPRETER))) + .build(); + } + + private static void validateAssistantResponse(Assistant assistantResponse) { + assertNotNull(assistantResponse); + assertNotNull(assistantResponse.getId()); + assertNotNull(assistantResponse.getCreatedAt()); + assertNotNull(assistantResponse.getObject()); + assertEquals(assistantResponse.getTools().get(0).getType(), AssistantToolsEnum.CODE_INTERPRETER); + assertEquals(MATH_TUTOR, assistantResponse.getName()); + } + + private static List validateListAssistants(ListAssistant assistants) { + assertNotNull(assistants); + List data = assistants.getData(); + assertNotNull(data); + return data; + } +} diff --git a/service/src/test/resources/assistant-file-data.json b/service/src/test/resources/assistant-file-data.json new file mode 100644 index 00000000..8b42bc07 --- /dev/null +++ b/service/src/test/resources/assistant-file-data.json @@ -0,0 +1 @@ +{"prompt": "prompt", "completion": "text"} \ No newline at end of file diff --git a/service/src/test/resources/assistants-data.html b/service/src/test/resources/assistants-data.html new file mode 100644 index 00000000..6c70bcfe --- /dev/null +++ b/service/src/test/resources/assistants-data.html @@ -0,0 +1 @@ + \ No newline at end of file From fee68bad25374d4553654d323652289ad9724696 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 12 Nov 2023 16:23:26 -0600 Subject: [PATCH 11/30] Fix tests --- .../openai/service/OpenAiService.java | 10 +++- .../openai/service/AssistantTest.java | 60 +------------------ 2 files changed, 10 insertions(+), 60 deletions(-) diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index 0a7d9459..04f2e459 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -259,7 +259,6 @@ public ImageResult createImageEdit(CreateImageEditRequest request, java.io.File .setType(MediaType.get("multipart/form-data")) .addFormDataPart("prompt", request.getPrompt()) .addFormDataPart("size", request.getSize()) - .addFormDataPart("model", request.getModel()) .addFormDataPart("response_format", request.getResponseFormat()) .addFormDataPart("image", "image", imageBody); @@ -272,6 +271,10 @@ public ImageResult createImageEdit(CreateImageEditRequest request, java.io.File builder.addFormDataPart("mask", "mask", maskBody); } + if (request.getModel() != null) { + builder.addFormDataPart("model", request.getModel()); + } + return execute(api.createImageEdit(builder.build())); } @@ -286,7 +289,6 @@ public ImageResult createImageVariation(CreateImageVariationRequest request, jav MultipartBody.Builder builder = new MultipartBody.Builder() .setType(MediaType.get("multipart/form-data")) .addFormDataPart("size", request.getSize()) - .addFormDataPart("model", request.getModel()) .addFormDataPart("response_format", request.getResponseFormat()) .addFormDataPart("image", "image", imageBody); @@ -294,6 +296,10 @@ public ImageResult createImageVariation(CreateImageVariationRequest request, jav builder.addFormDataPart("n", request.getN().toString()); } + if (request.getModel() != null) { + builder.addFormDataPart("model", request.getModel()); + } + return execute(api.createImageVariation(builder.build())); } diff --git a/service/src/test/java/com/theokanning/openai/service/AssistantTest.java b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java index 4f21b739..781161b2 100644 --- a/service/src/test/java/com/theokanning/openai/service/AssistantTest.java +++ b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java @@ -19,9 +19,7 @@ import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class AssistantTest { @@ -70,59 +68,7 @@ void listAssistants() { ListAssistant assistants = service.listAssistants(ListAssistantQueryRequest.builder().build()); assertNotNull(assistants); - // this should be more than 2 depending on how many times createAndValidateAssistant method is called - assertTrue(assistants.getData().size() > 1); - } - - @Test - void listAssistants_returnsTwoAssistants() { - int expectedLimit = 2; - ListAssistantQueryRequest queryResult = ListAssistantQueryRequest.builder() - .limit(expectedLimit) - .build(); - - ListAssistant assistants = service.listAssistants(queryResult); - - List data = validateListAssistants(assistants); - assertEquals(expectedLimit, data.size()); - } - - - - @Test - void listAssistants_returnsAscSortedAssistants() { - int expectedLimit = 3; - - ListAssistantQueryRequest queryResult = ListAssistantQueryRequest.builder() - .limit(expectedLimit) - .order(AssistantSortOrder.ASC) - .build(); - - ListAssistant assistants = service.listAssistants(queryResult); - - List data = validateListAssistants(assistants); - - boolean firstTwoAscending = data.get(0).getCreatedAt() <= data.get(1).getCreatedAt(); - boolean lastTwoAscending = data.get(1).getCreatedAt() <= data.get(2).getCreatedAt(); - assertTrue(firstTwoAscending && lastTwoAscending); - } - - @Test - void listAssistants_returnsDescSortedAssistants() { - int expectedLimit = 3; - - ListAssistantQueryRequest queryResult = ListAssistantQueryRequest.builder() - .limit(expectedLimit) - .order(AssistantSortOrder.DESC) - .build(); - - ListAssistant assistants = service.listAssistants(queryResult); - - List data = validateListAssistants(assistants); - - boolean firstTwoDescending = data.get(0).getCreatedAt() >= data.get(1).getCreatedAt(); - boolean lastTwoDescending = data.get(1).getCreatedAt() >= data.get(2).getCreatedAt(); - assertTrue(firstTwoDescending && lastTwoDescending); + assertFalse(assistants.getData().isEmpty()); } @Test @@ -138,8 +84,6 @@ void createAssistantFile() { assertEquals(assistant.getId(), assistantFile.getAssistantId()); } - - @Test void retrieveAssistantFile() { //TODO From a356a2e713ed7364aef601da29cb2a9434dbaa79 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 12 Nov 2023 16:34:23 -0600 Subject: [PATCH 12/30] Add NoArgsConstructor to ChatFunction --- .../com/theokanning/openai/completion/chat/ChatFunction.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java index 7d72829a..67162edf 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java @@ -3,11 +3,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.NonNull; import java.util.function.Function; @Data +@NoArgsConstructor public class ChatFunction { @NonNull @@ -46,7 +48,8 @@ public Builder executor(Class requestClass, Function executor) } public ChatFunction build() { - ChatFunction chatFunction = new ChatFunction(name); + ChatFunction chatFunction = new ChatFunction(); + chatFunction.setName(name); chatFunction.setDescription(description); chatFunction.setParametersClass(parameters); chatFunction.setExecutor(executor); From 47fe4780238485c34f1401cdeb5047760b9b872f Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 12 Nov 2023 16:41:25 -0600 Subject: [PATCH 13/30] Change compilation github workflow (#398) Only the released artifacts need to compile with 1.8 --- .github/workflows/pull_request.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 77d94afb..8ea36f7e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -17,5 +17,14 @@ jobs: distribution: temurin java-version: 8 - - name: Compile - run: ./gradlew compileJava compileTestJava + - name: Compile Artifacts + run: ./gradlew api:compileJava client:compileJava service:compileJava + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + + - name: Compile Tests + run: ./gradlew compileTestJava From 3a2d01080661a50d3a6e68fa71214b01df345d5a Mon Sep 17 00:00:00 2001 From: Bartosz Date: Sun, 12 Nov 2023 23:44:35 +0100 Subject: [PATCH 14/30] Implement creation of "function" parameters in runtime (#339) * Enable dynamic definition of "function" parameters instead of using Class instance * Add tests to new "function" capabilities * Add example of creating "function" parameters in runtime * Add documentation to ChatFunctions Co-authored-by: Theo Kanning --- .../chat/ChatCompletionRequest.java | 2 +- .../openai/completion/chat/ChatFunction.java | 11 +++ .../completion/chat/ChatFunctionDynamic.java | 62 +++++++++++++ .../chat/ChatFunctionParameters.java | 27 ++++++ .../completion/chat/ChatFunctionProperty.java | 25 ++++++ .../OpenAiApiDynamicFunctionExample.java | 90 +++++++++++++++++++ .../openai/service/ChatCompletionTest.java | 90 +++++++++++++++++++ 7 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java create mode 100644 api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java create mode 100644 api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java create mode 100644 example/src/main/java/example/OpenAiApiDynamicFunctionExample.java diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java index 1f055fbf..e4479ff3 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java @@ -98,7 +98,7 @@ public class ChatCompletionRequest { /** * A list of the available functions. */ - List functions; + List functions; /** * Controls how the model responds to function calls, as specified in the OpenAI documentation. diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java index 67162edf..820f4bd6 100644 --- a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunction.java @@ -12,9 +12,20 @@ @NoArgsConstructor public class ChatFunction { + /** + * The name of the function being called. + */ @NonNull private String name; + + /** + * A description of what the function does, used by the model to choose when and how to call the function. + */ private String description; + + /** + * The parameters the functions accepts. + */ @JsonProperty("parameters") private Class parametersClass; diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java new file mode 100644 index 00000000..9b4f2070 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionDynamic.java @@ -0,0 +1,62 @@ +package com.theokanning.openai.completion.chat; + +import lombok.Data; +import lombok.NonNull; + + +@Data +public class ChatFunctionDynamic { + + /** + * The name of the function being called. + */ + @NonNull + private String name; + + /** + * A description of what the function does, used by the model to choose when and how to call the function. + */ + private String description; + + /** + * The parameters the functions accepts. + */ + private ChatFunctionParameters parameters; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + private String description; + private ChatFunctionParameters parameters = new ChatFunctionParameters(); + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder parameters(ChatFunctionParameters parameters) { + this.parameters = parameters; + return this; + } + + public Builder addProperty(ChatFunctionProperty property) { + this.parameters.addProperty(property); + return this; + } + + public ChatFunctionDynamic build() { + ChatFunctionDynamic chatFunction = new ChatFunctionDynamic(name); + chatFunction.setDescription(description); + chatFunction.setParameters(parameters); + return chatFunction; + } + } +} diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java new file mode 100644 index 00000000..fee71e8f --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionParameters.java @@ -0,0 +1,27 @@ +package com.theokanning.openai.completion.chat; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Data +public class ChatFunctionParameters { + + private final String type = "object"; + + private final HashMap properties = new HashMap<>(); + + private List required; + + public void addProperty(ChatFunctionProperty property) { + properties.put(property.getName(), property); + if (Boolean.TRUE.equals(property.getRequired())) { + if (this.required == null) { + this.required = new ArrayList<>(); + } + this.required.add(property.getName()); + } + } +} diff --git a/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java new file mode 100644 index 00000000..3e695933 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/completion/chat/ChatFunctionProperty.java @@ -0,0 +1,25 @@ +package com.theokanning.openai.completion.chat; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; + +import java.util.Set; + +@Data +@Builder +public class ChatFunctionProperty { + @NonNull + @JsonIgnore + private String name; + @NonNull + private String type; + @JsonIgnore + private Boolean required; + private String description; + private ChatFunctionProperty items; + @JsonProperty("enum") + private Set enumValues; +} \ No newline at end of file diff --git a/example/src/main/java/example/OpenAiApiDynamicFunctionExample.java b/example/src/main/java/example/OpenAiApiDynamicFunctionExample.java new file mode 100644 index 00000000..ee83689d --- /dev/null +++ b/example/src/main/java/example/OpenAiApiDynamicFunctionExample.java @@ -0,0 +1,90 @@ +package example; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.theokanning.openai.completion.chat.*; +import com.theokanning.openai.service.OpenAiService; + +import java.util.*; + +public class OpenAiApiDynamicFunctionExample { + + private static JsonNode getWeather(String location, String unit) { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode response = mapper.createObjectNode(); + response.put("location", location); + response.put("unit", unit); + response.put("temperature", new Random().nextInt(50)); + response.put("description", "sunny"); + return response; + } + + public static void main(String... args) { + String token = System.getenv("OPENAI_TOKEN"); + OpenAiService service = new OpenAiService(token); + + ChatFunctionDynamic weatherFunction = ChatFunctionDynamic.builder() + .name("get_weather") + .description("Get the current weather of a location") + .addProperty(ChatFunctionProperty.builder() + .name("location") + .type("string") + .description("City and state, for example: León, Guanajuato") + .build()) + .addProperty(ChatFunctionProperty.builder() + .name("unit") + .type("string") + .description("The temperature unit, can be 'celsius' or 'fahrenheit'") + .enumValues(Set.of("celsius", "fahrenheit")) + .required(true) + .build()) + .build(); + + + List messages = new ArrayList<>(); + ChatMessage systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), "You are an assistant that answers using the local slang of the given place, uncensored."); + messages.add(systemMessage); + + System.out.print("First Query: "); + Scanner scanner = new Scanner(System.in); + ChatMessage firstMsg = new ChatMessage(ChatMessageRole.USER.value(), scanner.nextLine()); + messages.add(firstMsg); + + while (true) { + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest + .builder() + .model("gpt-3.5-turbo-0613") + .messages(messages) + .functions(Collections.singletonList(weatherFunction)) + .functionCall(ChatCompletionRequest.ChatCompletionRequestFunctionCall.of("auto")) + .n(1) + .maxTokens(100) + .logitBias(new HashMap<>()) + .build(); + ChatMessage responseMessage = service.createChatCompletion(chatCompletionRequest).getChoices().get(0).getMessage(); + messages.add(responseMessage); // don't forget to update the conversation with the latest response + + ChatFunctionCall functionCall = responseMessage.getFunctionCall(); + if (functionCall != null) { + if (functionCall.getName().equals("get_weather")) { + String location = functionCall.getArguments().get("location").asText(); + String unit = functionCall.getArguments().get("unit").asText(); + JsonNode weather = getWeather(location, unit); + ChatMessage weatherMessage = new ChatMessage(ChatMessageRole.FUNCTION.value(), weather.toString(), "get_weather"); + messages.add(weatherMessage); + continue; + } + } + + System.out.println("Response: " + responseMessage.getContent()); + System.out.print("Next Query: "); + String nextLine = scanner.nextLine(); + if (nextLine.equalsIgnoreCase("exit")) { + System.exit(0); + } + messages.add(new ChatMessage(ChatMessageRole.USER.value(), nextLine)); + } + } + +} diff --git a/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java b/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java index 3d26bf03..41eb498d 100644 --- a/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java +++ b/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Set; import java.util.Collections; import static org.junit.jupiter.api.Assertions.*; @@ -149,6 +150,50 @@ void createChatCompletionWithFunctions() { assertNotNull(choice2.getMessage().getContent()); } + @Test + void createChatCompletionWithDynamicFunctions() { + ChatFunctionDynamic function = ChatFunctionDynamic.builder() + .name("get_weather") + .description("Get the current weather of a location") + .addProperty(ChatFunctionProperty.builder() + .name("location") + .type("string") + .description("City and state, for example: León, Guanajuato") + .build()) + .addProperty(ChatFunctionProperty.builder() + .name("unit") + .type("string") + .description("The temperature unit, can be 'celsius' or 'fahrenheit'") + .enumValues(Set.of("celsius", "fahrenheit")) + .required(true) + .build()) + .build(); + + final List messages = new ArrayList<>(); + final ChatMessage systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), "You are a helpful assistant."); + final ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), "What is the weather in Monterrey, Nuevo León?"); + messages.add(systemMessage); + messages.add(userMessage); + + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest + .builder() + .model("gpt-3.5-turbo-0613") + .messages(messages) + .functions(Collections.singletonList(function)) + .n(1) + .maxTokens(100) + .logitBias(new HashMap<>()) + .build(); + + ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0); + assertEquals("function_call", choice.getFinishReason()); + assertNotNull(choice.getMessage().getFunctionCall()); + assertEquals("get_weather", choice.getMessage().getFunctionCall().getName()); + assertInstanceOf(ObjectNode.class, choice.getMessage().getFunctionCall().getArguments()); + assertNotNull(choice.getMessage().getFunctionCall().getArguments().get("location")); + assertNotNull(choice.getMessage().getFunctionCall().getArguments().get("unit")); + } + @Test void streamChatCompletionWithFunctions() { final List functions = Collections.singletonList(ChatFunction.builder() @@ -214,4 +259,49 @@ void streamChatCompletionWithFunctions() { assertNotNull(accumulatedMessage2.getContent()); } + @Test + void streamChatCompletionWithDynamicFunctions() { + ChatFunctionDynamic function = ChatFunctionDynamic.builder() + .name("get_weather") + .description("Get the current weather of a location") + .addProperty(ChatFunctionProperty.builder() + .name("location") + .type("string") + .description("City and state, for example: León, Guanajuato") + .build()) + .addProperty(ChatFunctionProperty.builder() + .name("unit") + .type("string") + .description("The temperature unit, can be 'celsius' or 'fahrenheit'") + .enumValues(Set.of("celsius", "fahrenheit")) + .required(true) + .build()) + .build(); + + final List messages = new ArrayList<>(); + final ChatMessage systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), "You are a helpful assistant."); + final ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), "What is the weather in Monterrey, Nuevo León?"); + messages.add(systemMessage); + messages.add(userMessage); + + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest + .builder() + .model("gpt-3.5-turbo-0613") + .messages(messages) + .functions(Collections.singletonList(function)) + .n(1) + .maxTokens(100) + .logitBias(new HashMap<>()) + .build(); + + ChatMessage accumulatedMessage = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest)) + .blockingLast() + .getAccumulatedMessage(); + assertNotNull(accumulatedMessage.getFunctionCall()); + assertEquals("get_weather", accumulatedMessage.getFunctionCall().getName()); + assertInstanceOf(ObjectNode.class, accumulatedMessage.getFunctionCall().getArguments()); + assertNotNull(accumulatedMessage.getFunctionCall().getArguments().get("location")); + assertNotNull(accumulatedMessage.getFunctionCall().getArguments().get("unit")); + } + } From 7fbd0a7b4c3138f4788ee9aaf021573d5c24a5a5 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 12 Nov 2023 16:50:32 -0600 Subject: [PATCH 15/30] Update to version 0.17.0 --- .github/workflows/publish.yml | 10 ++++++++-- .github/workflows/test.yml | 4 ++-- gradle.properties | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6e978294..0da11d72 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,17 +12,23 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 1.8 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 8 + java-version: 17 - name: Test run: ./gradlew test env: OPENAI_TOKEN: ${{ secrets.OPENAI_TOKEN }} + - name: Set up JDK 1.8 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 8 + - name: Publish run: ./gradlew build publish --no-parallel env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 830e797f..cf169464 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,11 +11,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 1.8 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 8 + java-version: 17 - name: Test run: ./gradlew test --stacktrace diff --git a/gradle.properties b/gradle.properties index 85a94ec8..671b33b6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.theokanning.openai-gpt3-java -VERSION_NAME=0.16.1 +VERSION_NAME=0.17.0 POM_URL=https://github.com/theokanning/openai-java POM_SCM_URL=https://github.com/theokanning/openai-java From 8768a4977ab3865f72a36d728b8eca55a0952157 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 12 Nov 2023 16:57:43 -0600 Subject: [PATCH 16/30] Deprecate Model.permissions This field is no longer returned by OpenAI --- api/src/main/java/com/theokanning/openai/model/Model.java | 3 ++- .../test/java/com/theokanning/openai/service/ModelTest.java | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/theokanning/openai/model/Model.java b/api/src/main/java/com/theokanning/openai/model/Model.java index 5180f2ae..2add679d 100644 --- a/api/src/main/java/com/theokanning/openai/model/Model.java +++ b/api/src/main/java/com/theokanning/openai/model/Model.java @@ -29,8 +29,9 @@ public class Model { public String ownedBy; /** - * List of permissions for this model + * List of permissions for this model. No longer returned by OpenAI */ + @Deprecated public List permission; /** diff --git a/service/src/test/java/com/theokanning/openai/service/ModelTest.java b/service/src/test/java/com/theokanning/openai/service/ModelTest.java index 4461dacf..31d23da9 100644 --- a/service/src/test/java/com/theokanning/openai/service/ModelTest.java +++ b/service/src/test/java/com/theokanning/openai/service/ModelTest.java @@ -27,6 +27,5 @@ void getModel() { assertEquals("ada", ada.id); assertEquals("openai", ada.ownedBy); - assertFalse(ada.permission.isEmpty()); } } From 987801bcc1c3f0b7aad006e0dad0577159710bbc Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 12 Nov 2023 17:05:00 -0600 Subject: [PATCH 17/30] Remove example project from publishing build step --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0da11d72..d2e93051 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: java-version: 8 - name: Publish - run: ./gradlew build publish --no-parallel + run: ./gradlew build -x :example:build publish --no-parallel env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} From bc81a295e65b383dbd1134f8cf10e6c1294f4c6f Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Sun, 12 Nov 2023 17:18:56 -0600 Subject: [PATCH 18/30] Require all steps to run in java 1.8 There's definitely a better way to manage version, but trying to run individual steps against later versions was causing my problems than it solved. I'll think of the best way to handle this. --- .github/workflows/gradle-wrapper-validation.yml | 14 -------------- .github/workflows/publish.yml | 12 +++--------- .github/workflows/pull_request.yml | 13 ++----------- .github/workflows/test.yml | 4 ++-- .../example/OpenAiApiDynamicFunctionExample.java | 2 +- .../openai/service/ChatCompletionTest.java | 10 +++------- 6 files changed, 11 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/gradle-wrapper-validation.yml diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 805c1fcf..00000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: "Validate Gradle Wrapper" -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d2e93051..6e978294 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,25 +12,19 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 17 + - name: Set up JDK 1.8 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 8 - name: Test run: ./gradlew test env: OPENAI_TOKEN: ${{ secrets.OPENAI_TOKEN }} - - name: Set up JDK 1.8 - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 8 - - name: Publish - run: ./gradlew build -x :example:build publish --no-parallel + run: ./gradlew build publish --no-parallel env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8ea36f7e..77d94afb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -17,14 +17,5 @@ jobs: distribution: temurin java-version: 8 - - name: Compile Artifacts - run: ./gradlew api:compileJava client:compileJava service:compileJava - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 17 - - - name: Compile Tests - run: ./gradlew compileTestJava + - name: Compile + run: ./gradlew compileJava compileTestJava diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cf169464..830e797f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,11 +11,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 17 + - name: Set up JDK 1.8 uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 8 - name: Test run: ./gradlew test --stacktrace diff --git a/example/src/main/java/example/OpenAiApiDynamicFunctionExample.java b/example/src/main/java/example/OpenAiApiDynamicFunctionExample.java index ee83689d..75f9b8e2 100644 --- a/example/src/main/java/example/OpenAiApiDynamicFunctionExample.java +++ b/example/src/main/java/example/OpenAiApiDynamicFunctionExample.java @@ -36,7 +36,7 @@ public static void main(String... args) { .name("unit") .type("string") .description("The temperature unit, can be 'celsius' or 'fahrenheit'") - .enumValues(Set.of("celsius", "fahrenheit")) + .enumValues(new HashSet<>(Arrays.asList("celsius", "fahrenheit"))) .required(true) .build()) .build(); diff --git a/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java b/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java index 41eb498d..25f0defb 100644 --- a/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java +++ b/service/src/test/java/com/theokanning/openai/service/ChatCompletionTest.java @@ -7,11 +7,7 @@ import com.theokanning.openai.completion.chat.*; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; -import java.util.Collections; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; @@ -164,7 +160,7 @@ void createChatCompletionWithDynamicFunctions() { .name("unit") .type("string") .description("The temperature unit, can be 'celsius' or 'fahrenheit'") - .enumValues(Set.of("celsius", "fahrenheit")) + .enumValues(new HashSet<>(Arrays.asList("celsius", "fahrenheit"))) .required(true) .build()) .build(); @@ -273,7 +269,7 @@ void streamChatCompletionWithDynamicFunctions() { .name("unit") .type("string") .description("The temperature unit, can be 'celsius' or 'fahrenheit'") - .enumValues(Set.of("celsius", "fahrenheit")) + .enumValues(new HashSet<>(Arrays.asList("celsius", "fahrenheit"))) .required(true) .build()) .build(); From f223175df387bc9fdce33ba9166282dab5646cb5 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Mon, 13 Nov 2023 18:16:11 -0600 Subject: [PATCH 19/30] Fix Assistant POJO issues Easier to make separate objects for getting, creating, and modifying Assistants in case the parameters diverge later. --- .../openai/assistants/Assistant.java | 47 +++++++++++++++++- .../openai/assistants/AssistantRequest.java | 48 ++++++++++++++++++- .../openai/assistants/ListAssistant.java | 2 +- ...tBase.java => ModifyAssistantRequest.java} | 12 ++--- .../theokanning/openai/assistants/Tool.java | 9 ++++ .../theokanning/openai/client/OpenAiApi.java | 11 ++--- .../openai/service/OpenAiService.java | 12 ++--- .../openai/service/AssistantTest.java | 33 ++++--------- 8 files changed, 122 insertions(+), 52 deletions(-) rename api/src/main/java/com/theokanning/openai/assistants/{AssistantBase.java => ModifyAssistantRequest.java} (80%) diff --git a/api/src/main/java/com/theokanning/openai/assistants/Assistant.java b/api/src/main/java/com/theokanning/openai/assistants/Assistant.java index 111ef169..49cf17e9 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/Assistant.java +++ b/api/src/main/java/com/theokanning/openai/assistants/Assistant.java @@ -1,10 +1,16 @@ package com.theokanning.openai.assistants; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; +import lombok.*; +import java.util.List; +import java.util.Map; + +@Builder +@NoArgsConstructor +@AllArgsConstructor @Data -public class Assistant extends AssistantBase { +public class Assistant { /** * The identifier, which can be referenced in API endpoints. @@ -21,4 +27,41 @@ public class Assistant extends AssistantBase { */ @JsonProperty("created_at") Integer createdAt; + + /** + * The name of the assistant. The maximum length is 256 + */ + String name; + + /** + * The description of the assistant. + */ + String description; + + /** + * ID of the model to use + */ + @NonNull + String model; + + /** + * The system instructions that the assistant uses. + */ + String instructions; + + /** + * A list of tools enabled on the assistant. + */ + List tools; + + /** + * A list of file IDs attached to this assistant. + */ + @JsonProperty("file_ids") + List fileIds; + + /** + * Set of 16 key-value pairs that can be attached to an object. + */ + Map metadata; } diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java index dc0a66df..bf38ff0b 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantRequest.java @@ -1,6 +1,52 @@ package com.theokanning.openai.assistants; -public class AssistantRequest extends AssistantBase { +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import java.util.List; +import java.util.Map; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AssistantRequest { + + /** + * ID of the model to use + */ + @NonNull + String model; + + /** + * The name of the assistant. The maximum length is 256 + */ + String name; + + /** + * The description of the assistant. + */ + String description; + + /** + * The system instructions that the assistant uses. + */ + String instructions; + + /** + * A list of tools enabled on the assistant. + */ + List tools; + + /** + * A list of file IDs attached to this assistant. + */ + @JsonProperty("file_ids") + List fileIds; + + /** + * Set of 16 key-value pairs that can be attached to an object. + */ + Map metadata; } diff --git a/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java b/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java index 8478a547..39884991 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java +++ b/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.theokanning.openai.OpenAiResponse; -public class ListAssistant extends OpenAiResponse { +public class ListAssistant extends OpenAiResponse { @JsonProperty("first_id") String firstId; diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantBase.java b/api/src/main/java/com/theokanning/openai/assistants/ModifyAssistantRequest.java similarity index 80% rename from api/src/main/java/com/theokanning/openai/assistants/AssistantBase.java rename to api/src/main/java/com/theokanning/openai/assistants/ModifyAssistantRequest.java index d771bd30..0fcc4f85 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/AssistantBase.java +++ b/api/src/main/java/com/theokanning/openai/assistants/ModifyAssistantRequest.java @@ -1,11 +1,8 @@ package com.theokanning.openai.assistants; + import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.NonNull; +import lombok.*; import java.util.List; import java.util.Map; @@ -14,12 +11,11 @@ @NoArgsConstructor @AllArgsConstructor @Data -public class AssistantBase { +public class ModifyAssistantRequest { /** * ID of the model to use */ - @NonNull String model; /** @@ -46,7 +42,7 @@ public class AssistantBase { * A list of file IDs attached to this assistant. */ @JsonProperty("file_ids") - List fields; + List fileIds; /** * Set of 16 key-value pairs that can be attached to an object. diff --git a/api/src/main/java/com/theokanning/openai/assistants/Tool.java b/api/src/main/java/com/theokanning/openai/assistants/Tool.java index 00027d72..e759db80 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/Tool.java +++ b/api/src/main/java/com/theokanning/openai/assistants/Tool.java @@ -1,5 +1,6 @@ package com.theokanning.openai.assistants; +import com.theokanning.openai.completion.chat.ChatFunction; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -8,5 +9,13 @@ @AllArgsConstructor @Data public class Tool { + /** + * The type of tool being defined + */ AssistantToolsEnum type; + + /** + * Function definition, only used if type is "function" + */ + ChatFunction function; } diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index 0bfc03aa..69bda48b 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -2,12 +2,7 @@ import com.theokanning.openai.DeleteResult; import com.theokanning.openai.OpenAiResponse; -import com.theokanning.openai.assistants.AssistantBase; -import com.theokanning.openai.assistants.Assistant; -import com.theokanning.openai.assistants.AssistantFile; -import com.theokanning.openai.assistants.AssistantFileRequest; -import com.theokanning.openai.assistants.ListAssistant; -import com.theokanning.openai.assistants.ListAssistantQueryRequest; +import com.theokanning.openai.assistants.*; import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.TranscriptionResult; import com.theokanning.openai.audio.TranslationResult; @@ -195,7 +190,7 @@ public interface OpenAiApi { @Headers({"OpenAI-Beta: assistants=v1"}) @POST("/v1/assistants") - Single createAssistant(@Body AssistantBase request); + Single createAssistant(@Body AssistantRequest request); @Headers({"OpenAI-Beta: assistants=v1"}) @GET("/v1/assistants/{assistant_id}") @@ -203,7 +198,7 @@ public interface OpenAiApi { @Headers({"OpenAI-Beta: assistants=v1"}) @POST("/v1/assistants/{assistant_id}") - Single modifyAssistant(@Path("assistant_id") String assistantId, @Body AssistantBase request); + Single modifyAssistant(@Path("assistant_id") String assistantId, @Body ModifyAssistantRequest request); @Headers({"OpenAI-Beta: assistants=v1"}) @DELETE("/v1/assistants/{assistant_id}") diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index 04f2e459..ea62edde 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -6,15 +6,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.node.TextNode; +import com.sun.org.apache.xpath.internal.operations.Mod; import com.theokanning.openai.DeleteResult; import com.theokanning.openai.OpenAiError; import com.theokanning.openai.OpenAiHttpException; -import com.theokanning.openai.assistants.Assistant; -import com.theokanning.openai.assistants.AssistantBase; -import com.theokanning.openai.assistants.AssistantFile; -import com.theokanning.openai.assistants.AssistantFileRequest; -import com.theokanning.openai.assistants.ListAssistant; -import com.theokanning.openai.assistants.ListAssistantQueryRequest; +import com.theokanning.openai.assistants.*; import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.CreateTranscriptionRequest; import com.theokanning.openai.audio.CreateTranslationRequest; @@ -362,7 +358,7 @@ public ModerationResult createModeration(ModerationRequest request) { return execute(api.createModeration(request)); } - public Assistant createAssistant(AssistantBase request) { + public Assistant createAssistant(AssistantRequest request) { return execute(api.createAssistant(request)); } @@ -370,7 +366,7 @@ public Assistant retrieveAssistant(String assistantId) { return execute(api.retrieveAssistant(assistantId)); } - public Assistant modifyAssistant(String assistantId, AssistantBase request) { + public Assistant modifyAssistant(String assistantId, ModifyAssistantRequest request) { return execute(api.modifyAssistant(assistantId, request)); } diff --git a/service/src/test/java/com/theokanning/openai/service/AssistantTest.java b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java index 781161b2..d0ef40d7 100644 --- a/service/src/test/java/com/theokanning/openai/service/AssistantTest.java +++ b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java @@ -1,16 +1,7 @@ package com.theokanning.openai.service; import com.theokanning.openai.DeleteResult; -import com.theokanning.openai.assistants.Assistant; -import com.theokanning.openai.assistants.AssistantBase; -import com.theokanning.openai.assistants.AssistantFile; -import com.theokanning.openai.assistants.AssistantFileRequest; -import com.theokanning.openai.assistants.AssistantRequest; -import com.theokanning.openai.assistants.AssistantSortOrder; -import com.theokanning.openai.assistants.AssistantToolsEnum; -import com.theokanning.openai.assistants.ListAssistant; -import com.theokanning.openai.assistants.ListAssistantQueryRequest; -import com.theokanning.openai.assistants.Tool; +import com.theokanning.openai.assistants.*; import com.theokanning.openai.file.File; import com.theokanning.openai.utils.TikTokensUtil; import org.junit.jupiter.api.AfterAll; @@ -44,10 +35,12 @@ void retrieveAssistant() { void modifyAssistant() { Assistant createAssistantResponse = createAndValidateAssistant(); - String modifiedName = MATH_TUTOR + " Modified"; - createAssistantResponse.setName(modifiedName);//modify a field + String modifiedName = MATH_TUTOR + "Modified"; + ModifyAssistantRequest modifyRequest = ModifyAssistantRequest.builder() + .name(modifiedName) + .build(); - Assistant modifiedAssistantResponse = service.modifyAssistant(createAssistantResponse.getId(), createAssistantResponse); + Assistant modifiedAssistantResponse = service.modifyAssistant(createAssistantResponse.getId(), modifyRequest); assertNotNull(modifiedAssistantResponse); assertEquals(modifiedName, modifiedAssistantResponse.getName()); } @@ -123,20 +116,19 @@ private static File uploadAssistantFile() { } private static Assistant createAndValidateAssistant() { - AssistantBase assistantRequest = assistantStub(); + AssistantRequest assistantRequest = assistantStub(); Assistant createAssistantResponse = service.createAssistant(assistantRequest); validateAssistantResponse(createAssistantResponse); return createAssistantResponse; } - - private static AssistantBase assistantStub() { + private static AssistantRequest assistantStub() { return AssistantRequest.builder() .model(TikTokensUtil.ModelEnum.GPT_4_1106_preview.getName()) .name(MATH_TUTOR) .instructions(ASSISTANT_INSTRUCTION) - .tools(Collections.singletonList(new Tool(AssistantToolsEnum.CODE_INTERPRETER))) + .tools(Collections.singletonList(new Tool(AssistantToolsEnum.CODE_INTERPRETER, null))) .build(); } @@ -148,11 +140,4 @@ private static void validateAssistantResponse(Assistant assistantResponse) { assertEquals(assistantResponse.getTools().get(0).getType(), AssistantToolsEnum.CODE_INTERPRETER); assertEquals(MATH_TUTOR, assistantResponse.getName()); } - - private static List validateListAssistants(ListAssistant assistants) { - assertNotNull(assistants); - List data = assistants.getData(); - assertNotNull(data); - return data; - } } From 037606eeb1b753e15725db00f3c397cfe7c19090 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Mon, 13 Nov 2023 20:12:48 -0600 Subject: [PATCH 20/30] Add Assistant Messages API Also added just enough Threads API to create messages. --- .../openai/ListSearchParameters.java | 51 +++++++ .../theokanning/openai/OpenAiResponse.java | 19 +++ .../theokanning/openai/messages/Message.java | 81 +++++++++++ .../openai/messages/MessageContent.java | 19 +++ .../openai/messages/MessageFile.java | 38 +++++ .../openai/messages/MessageRequest.java | 48 +++++++ .../openai/messages/ModifyMessageRequest.java | 27 ++++ .../theokanning/openai/threads/Thread.java | 41 ++++++ .../openai/threads/ThreadRequest.java | 36 +++++ .../theokanning/openai/client/OpenAiApi.java | 46 +++++- .../openai/service/OpenAiService.java | 53 ++++++- .../openai/service/MessageTest.java | 133 ++++++++++++++++++ .../openai/service/ThreadTest.java | 31 ++++ 13 files changed, 616 insertions(+), 7 deletions(-) create mode 100644 api/src/main/java/com/theokanning/openai/ListSearchParameters.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/Message.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/MessageContent.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/MessageFile.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/MessageRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/ModifyMessageRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/threads/Thread.java create mode 100644 api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java create mode 100644 service/src/test/java/com/theokanning/openai/service/MessageTest.java create mode 100644 service/src/test/java/com/theokanning/openai/service/ThreadTest.java diff --git a/api/src/main/java/com/theokanning/openai/ListSearchParameters.java b/api/src/main/java/com/theokanning/openai/ListSearchParameters.java new file mode 100644 index 00000000..53fd484f --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/ListSearchParameters.java @@ -0,0 +1,51 @@ +package com.theokanning.openai; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Common options when getting a list of objects + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ListSearchParameters { + /** + * A limit on the number of objects to be returned. + * Limit can range between 1 and 100, and the default is 20 + */ + + Integer limit; + + /** + * Sort order by the 'created_at' timestamp of the objects. + * 'asc' for ascending order and 'desc' for descending order. + */ + Order order; + + /** + * A cursor for use in pagination. after is an object ID that defines your place in the list. + * For instance, if you make a list request and receive 100 objects, ending with obj_foo, + * your subsequent call can include after=obj_foo in order to fetch the next page of the list + */ + String after; + + /** + * A cursor for use in pagination. before is an object ID that defines your place in the list. + * For instance, if you make a list request and receive 100 objects, ending with obj_foo, + * your subsequent call can include before=obj_foo in order to fetch the previous page of the list. + */ + String before; + + public enum Order { + @JsonProperty("asc") + ASCENDING, + + @JsonProperty("desc") + DESCENDING + } +} diff --git a/api/src/main/java/com/theokanning/openai/OpenAiResponse.java b/api/src/main/java/com/theokanning/openai/OpenAiResponse.java index 4b718aad..fd566d03 100644 --- a/api/src/main/java/com/theokanning/openai/OpenAiResponse.java +++ b/api/src/main/java/com/theokanning/openai/OpenAiResponse.java @@ -1,5 +1,6 @@ package com.theokanning.openai; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @@ -18,4 +19,22 @@ public class OpenAiResponse { * The type of object returned, should be "list" */ public String object; + + /** + * The first id included + */ + @JsonProperty("first_id") + public String firstId; + + /** + * The last id included + */ + @JsonProperty("last_id") + public String lastId; + + /** + * True if there are objects after lastId + */ + @JsonProperty("hasMore") + public boolean hasMore; } diff --git a/api/src/main/java/com/theokanning/openai/messages/Message.java b/api/src/main/java/com/theokanning/openai/messages/Message.java new file mode 100644 index 00000000..940cd934 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/Message.java @@ -0,0 +1,81 @@ +package com.theokanning.openai.messages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + + +/** + * Represents a Message within a thread. + *

+ * https://platform.openai.com/docs/api-reference/messages/object + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Message { + /** + * The identifier, which can be referenced in API endpoints. + */ + String id; + + /** + * The object type, which is always thread.message. + */ + String object; + + /** + * The Unix timestamp (in seconds) for when the message was created. + */ + @JsonProperty("created_at") + int createdAt; + + /** + * The thread ID that this message belongs to. + */ + @JsonProperty("thread_id") + String threadId; + + /** + * The entity that produced the message. One of user or assistant. + */ + String role; + + /** + * The content of the message in an array of text and/or images. + */ + List content; + + /** + * If applicable, the ID of the assistant that authored this message. + */ + @JsonProperty("assistant_id") + String assistantId; + + /** + * If applicable, the ID of the run associated with the authoring of this message. + */ + @JsonProperty("run_id") + String runId; + + /** + * A list of file IDs that the assistant should use. + * Useful for tools like retrieval and code_interpreter that can access files. + * A maximum of 10 files can be attached to a message. + */ + @JsonProperty("file_ids") + List fileIds; + + /** + * Set of 16 key-value pairs that can be attached to an object. + * This can be useful for storing additional information about the object in a structured format. + * Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long. + */ + Map metadata; +} \ No newline at end of file diff --git a/api/src/main/java/com/theokanning/openai/messages/MessageContent.java b/api/src/main/java/com/theokanning/openai/messages/MessageContent.java new file mode 100644 index 00000000..6db58112 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/MessageContent.java @@ -0,0 +1,19 @@ +package com.theokanning.openai.messages; + +import lombok.Data; + + +/** + * Represents the content of a message + *

+ * https://platform.openai.com/docs/api-reference/messages/object + */ +@Data +public class MessageContent { + /** + * The content type, either "text" or "image_file" + */ + String type; + + // todo handle different content types +} diff --git a/api/src/main/java/com/theokanning/openai/messages/MessageFile.java b/api/src/main/java/com/theokanning/openai/messages/MessageFile.java new file mode 100644 index 00000000..7a0dd3bd --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/MessageFile.java @@ -0,0 +1,38 @@ +package com.theokanning.openai.messages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * A list of files attached to a Message + *

+ * https://platform.openai.com/docs/api-reference/messages/file-object + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +public class MessageFile { + /** + * The identifier, which can be referenced in API endpoints. + */ + String id; + + /** + * The object type, which is always thread.message.file. + */ + String object; + + /** + * The Unix timestamp (in seconds) for when the message file was created. + */ + @JsonProperty("created_at") + int createdAt; + + /** + * The ID of the message that the File is attached to. + */ + @JsonProperty("message_id") + String messageId; +} diff --git a/api/src/main/java/com/theokanning/openai/messages/MessageRequest.java b/api/src/main/java/com/theokanning/openai/messages/MessageRequest.java new file mode 100644 index 00000000..6c49a110 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/MessageRequest.java @@ -0,0 +1,48 @@ +package com.theokanning.openai.messages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.util.List; +import java.util.Map; + +/** + * Creates a Message + *

+ * https://platform.openai.com/docs/api-reference/messages/createMessage + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class MessageRequest { + /** + * The role of the entity that is creating the message. + * Currently only "user" is supported. + */ + @NonNull + @Builder.Default + String role = "user"; + + /** + * The content of the message. + */ + @NonNull + String content; + + /** + * A list of File IDs that the message should use. + * Defaults to an empty list. + * There can be a maximum of 10 files attached to a message. + * Useful for tools like retrieval and code_interpreter that can access and use files. + */ + @JsonProperty("file_ids") + List fileIds; + + /** + * Set of 16 key-value pairs that can be attached to an object. + * This can be useful for storing additional information about the object in a structured format. + * Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long. + */ + Map metadata; +} diff --git a/api/src/main/java/com/theokanning/openai/messages/ModifyMessageRequest.java b/api/src/main/java/com/theokanning/openai/messages/ModifyMessageRequest.java new file mode 100644 index 00000000..32344c96 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/ModifyMessageRequest.java @@ -0,0 +1,27 @@ +package com.theokanning.openai.messages; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * Modifies a Message + *

+ * https://platform.openai.com/docs/api-reference/messages/modifyMessage + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ModifyMessageRequest { + + /** + * Set of 16 key-value pairs that can be attached to an object. + * This can be useful for storing additional information about the object in a structured format. + * Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long. + */ + Map metadata; +} diff --git a/api/src/main/java/com/theokanning/openai/threads/Thread.java b/api/src/main/java/com/theokanning/openai/threads/Thread.java new file mode 100644 index 00000000..24adb73f --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/threads/Thread.java @@ -0,0 +1,41 @@ +package com.theokanning.openai.threads; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * Represents a Thread with an assistant + *

+ * https://platform.openai.com/docs/api-reference/threads/object + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Thread { + /** + * The identifier, which can be referenced in API endpoints. + */ + String id; + + /** + * The object type, which is always thread. + */ + String object; + + /** + * The Unix timestamp (in seconds) for when the thread was created. + */ + @JsonProperty("created_at") + int createdAt; + + /** + * Set of 16 key-value pairs that can be attached to an object. + * This can be useful for storing additional information about the object in a structured format. + * Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long. + */ + Map metadata; +} diff --git a/api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java b/api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java new file mode 100644 index 00000000..54f536bd --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java @@ -0,0 +1,36 @@ +package com.theokanning.openai.threads; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.messages.MessageRequest; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * Creates a thread + *

+ * https://platform.openai.com/docs/api-reference/threads/createThread + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ThreadRequest { + /** + * A list of messages to start the thread with. Optional. + */ + @JsonProperty("messages") + List messages; + + /** + * Set of 16 key-value pairs that can be attached to an object. + * This can be useful for storing additional information about the object in a structured format. + * Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long. + */ + @JsonProperty("metadata") + Map metadata; +} diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index 69bda48b..c7bf2e10 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -26,9 +26,15 @@ import com.theokanning.openai.finetune.FineTuneResult; import com.theokanning.openai.image.CreateImageRequest; import com.theokanning.openai.image.ImageResult; +import com.theokanning.openai.messages.Message; +import com.theokanning.openai.messages.MessageFile; +import com.theokanning.openai.messages.MessageRequest; +import com.theokanning.openai.messages.ModifyMessageRequest; import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; +import com.theokanning.openai.threads.Thread; +import com.theokanning.openai.threads.ThreadRequest; import io.reactivex.Single; import okhttp3.MultipartBody; import okhttp3.RequestBody; @@ -186,8 +192,7 @@ public interface OpenAiApi { @Deprecated @GET("v1/dashboard/billing/usage") Single billingUsage(@Query("start_date") LocalDate starDate, @Query("end_date") LocalDate endDate); - - + @Headers({"OpenAI-Beta: assistants=v1"}) @POST("/v1/assistants") Single createAssistant(@Body AssistantRequest request); @@ -223,4 +228,41 @@ public interface OpenAiApi { @Headers({"OpenAI-Beta: assistants=v1"}) @GET("/v1/assistants/{assistant_id}/files") Single> listAssistantFiles(@Path("assistant_id") String assistantId, @QueryMap Map filterRequest); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @POST("/v1/threads") + Single createThread(@Body ThreadRequest request); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @POST("/v1/threads/{thread_id}/messages") + Single createMessage(@Path("thread_id") String threadId, @Body MessageRequest request); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/threads/{thread_id}/messages/{message_id}") + Single retrieveMessage(@Path("thread_id") String threadId, @Path("message_id") String messageId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @POST("/v1/threads/{thread_id}/messages/{message_id}") + Single modifyMessage(@Path("thread_id") String threadId, @Path("message_id") String messageId, @Body ModifyMessageRequest request); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/threads/{thread_id}/messages") + Single> listMessages(@Path("thread_id") String threadId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/threads/{thread_id}/messages") + Single> listMessages(@Path("thread_id") String threadId, @QueryMap Map filterRequest); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/threads/{thread_id}/messages/{message_id}/files/{file_id}") + Single retrieveMessageFile(@Path("thread_id") String threadId, @Path("message_id") String messageId, @Path("file_id") String fileId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/threads/{thread_id}/messages/{message_id}/files") + Single> listMessageFiles(@Path("thread_id") String threadId, @Path("message_id") String messageId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/threads/{thread_id}/messages/{message_id}/files") + Single> listMessageFiles(@Path("thread_id") String threadId, @Path("message_id") String messageId, @QueryMap Map filterRequest); + } diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index ea62edde..a6bb9bc3 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -7,9 +7,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.node.TextNode; import com.sun.org.apache.xpath.internal.operations.Mod; -import com.theokanning.openai.DeleteResult; -import com.theokanning.openai.OpenAiError; -import com.theokanning.openai.OpenAiHttpException; +import com.theokanning.openai.*; import com.theokanning.openai.assistants.*; import com.theokanning.openai.audio.CreateSpeechRequest; import com.theokanning.openai.audio.CreateTranscriptionRequest; @@ -38,9 +36,15 @@ import com.theokanning.openai.image.CreateImageRequest; import com.theokanning.openai.image.CreateImageVariationRequest; import com.theokanning.openai.image.ImageResult; +import com.theokanning.openai.messages.Message; +import com.theokanning.openai.messages.MessageFile; +import com.theokanning.openai.messages.MessageRequest; +import com.theokanning.openai.messages.ModifyMessageRequest; import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; +import com.theokanning.openai.threads.Thread; +import com.theokanning.openai.threads.ThreadRequest; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.Single; @@ -358,6 +362,10 @@ public ModerationResult createModeration(ModerationRequest request) { return execute(api.createModeration(request)); } + public ResponseBody createSpeech(CreateSpeechRequest request) { + return execute(api.createSpeech(request)); + } + public Assistant createAssistant(AssistantRequest request) { return execute(api.createAssistant(request)); } @@ -396,10 +404,45 @@ public ListAssistant listAssistantFiles(String assistantId, ListAssis return execute(api.listAssistantFiles(assistantId, queryParameters)); } - public ResponseBody createSpeech(CreateSpeechRequest request) { - return execute(api.createSpeech(request)); + public Thread createThread(ThreadRequest request) { + return execute(api.createThread(request)); + } + + public Message createMessage(String threadId, MessageRequest request) { + return execute(api.createMessage(threadId, request)); + } + + public Message retrieveMessage(String threadId, String messageId) { + return execute(api.retrieveMessage(threadId,messageId)); + } + + public Message modifyMessage(String threadId, String messageId, ModifyMessageRequest request) { + return execute(api.modifyMessage(threadId,messageId, request)); } + public OpenAiResponse listMessages(String threadId) { + return execute(api.listMessages(threadId)); + } + + public OpenAiResponse listMessages(String threadId, ListSearchParameters params) { + Map queryParameters = mapper.convertValue(params, new TypeReference>() {}); + return execute(api.listMessages(threadId,queryParameters)); + } + + public MessageFile retrieveMessageFile(String threadId, String messageId, String fileId) { + return execute(api.retrieveMessageFile(threadId,messageId, fileId)); + } + + public OpenAiResponse listMessageFiles(String threadId, String messageId) { + return execute(api.listMessageFiles(threadId,messageId)); + } + + public OpenAiResponse listMessageFiles(String threadId, String messageId, ListSearchParameters params) { + Map queryParameters = mapper.convertValue(params, new TypeReference>() {}); + return execute(api.listMessageFiles(threadId,messageId, queryParameters)); + } + + /** * Calls the Open AI api, returns the response, and parses error messages if the request fails */ diff --git a/service/src/test/java/com/theokanning/openai/service/MessageTest.java b/service/src/test/java/com/theokanning/openai/service/MessageTest.java new file mode 100644 index 00000000..a1fe99cb --- /dev/null +++ b/service/src/test/java/com/theokanning/openai/service/MessageTest.java @@ -0,0 +1,133 @@ +package com.theokanning.openai.service; + +import com.theokanning.openai.ListSearchParameters; +import com.theokanning.openai.file.File; +import com.theokanning.openai.messages.Message; +import com.theokanning.openai.messages.MessageFile; +import com.theokanning.openai.messages.MessageRequest; +import com.theokanning.openai.messages.ModifyMessageRequest; +import com.theokanning.openai.threads.Thread; +import com.theokanning.openai.threads.ThreadRequest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +public class MessageTest { + + static OpenAiService service; + + static String threadId; + + @BeforeAll + static void setup() { + String token = System.getenv("OPENAI_TOKEN"); + service = new OpenAiService(token); + + ThreadRequest threadRequest = ThreadRequest.builder() + .build(); + threadId = service.createThread(threadRequest).getId(); + } + + @AfterAll + static void teardown() { + // todo delete thread + } + + @Test + void createMessage() { + File file = service.uploadFile("assistants", "src/test/resources/penguin.png"); + Map metadata = new HashMap<>(); + metadata.put("key", "value"); + + MessageRequest messageRequest = MessageRequest.builder() + .content("Hello") + .fileIds(Collections.singletonList(file.getId())) + .metadata(metadata) + .build(); + + Message message = service.createMessage(threadId, messageRequest); + + assertNotNull(message.getId()); + assertEquals("thread.message", message.getObject()); + assertEquals(1, message.getFileIds().size()); + } + + @Test + void retrieveMessage() { + String messageId = createTestMessage().getId(); + + Message message = service.retrieveMessage(threadId, messageId); + + assertEquals(messageId, message.getId()); + } + + @Test + void modifyMessage() { + String messageId = createTestMessage().getId(); + + Map metadata = new HashMap<>(); + metadata.put("key", "value"); + + ModifyMessageRequest request = ModifyMessageRequest.builder() + .metadata(metadata) + .build(); + Message message = service.modifyMessage(threadId, messageId, request); + + assertEquals(messageId, message.getId()); + assertEquals("value", message.getMetadata().get("key")); + } + + @Test + void listMessages() { + ThreadRequest threadRequest = ThreadRequest.builder() + .build(); + String separateThreadId = service.createThread(threadRequest).getId(); + createTestMessage(separateThreadId); + createTestMessage(separateThreadId); + createTestMessage(separateThreadId); + + List messages = service.listMessages(separateThreadId).getData(); + + assertEquals(3, messages.size()); + } + + @Test + void retrieveAndListMessageFile() { + File file = service.uploadFile("assistants", "src/test/resources/penguin.png"); + MessageRequest messageRequest = MessageRequest.builder() + .content("Hello") + .fileIds(Collections.singletonList(file.getId())) + .build(); + + Message message = service.createMessage(threadId, messageRequest); + + MessageFile messageFile = service.retrieveMessageFile(threadId, message.getId(), file.getId()); + + assertEquals(file.getId(), messageFile.getId()); + assertEquals(message.getId(), messageFile.getMessageId()); + + List messageFiles = service.listMessageFiles(threadId, message.getId(), new ListSearchParameters()).getData(); + assertEquals(1, messageFiles.size()); + } + + Message createTestMessage() { + return createTestMessage(threadId); + } + + Message createTestMessage(String threadId) { + MessageRequest messageRequest = MessageRequest.builder() + .content("Hello") + .build(); + + return service.createMessage(threadId, messageRequest); + } +} \ No newline at end of file diff --git a/service/src/test/java/com/theokanning/openai/service/ThreadTest.java b/service/src/test/java/com/theokanning/openai/service/ThreadTest.java new file mode 100644 index 00000000..24ff2bb0 --- /dev/null +++ b/service/src/test/java/com/theokanning/openai/service/ThreadTest.java @@ -0,0 +1,31 @@ +package com.theokanning.openai.service; + +import com.theokanning.openai.messages.MessageRequest; +import com.theokanning.openai.threads.Thread; +import com.theokanning.openai.threads.ThreadRequest; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + + +public class ThreadTest { + + String token = System.getenv("OPENAI_TOKEN"); + OpenAiService service = new OpenAiService(token); + + @Test + void createThread() { + MessageRequest messageRequest = MessageRequest.builder() + .content("Hello") + .build(); + + ThreadRequest threadRequest = ThreadRequest.builder() + .messages(Collections.singletonList(messageRequest)) + .build(); + + Thread thread = service.createThread(threadRequest); + assertEquals("thread", thread.getObject()); + } +} \ No newline at end of file From fe0c62f5763a091bc6a4ac7018c83b13aa1d2433 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Mon, 13 Nov 2023 20:15:56 -0600 Subject: [PATCH 21/30] Fix JsonProperty on new objects --- api/src/main/java/com/theokanning/openai/OpenAiResponse.java | 2 +- .../main/java/com/theokanning/openai/threads/ThreadRequest.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/api/src/main/java/com/theokanning/openai/OpenAiResponse.java b/api/src/main/java/com/theokanning/openai/OpenAiResponse.java index fd566d03..f062fc77 100644 --- a/api/src/main/java/com/theokanning/openai/OpenAiResponse.java +++ b/api/src/main/java/com/theokanning/openai/OpenAiResponse.java @@ -35,6 +35,6 @@ public class OpenAiResponse { /** * True if there are objects after lastId */ - @JsonProperty("hasMore") + @JsonProperty("has_more") public boolean hasMore; } diff --git a/api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java b/api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java index 54f536bd..35fd888a 100644 --- a/api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java +++ b/api/src/main/java/com/theokanning/openai/threads/ThreadRequest.java @@ -23,7 +23,6 @@ public class ThreadRequest { /** * A list of messages to start the thread with. Optional. */ - @JsonProperty("messages") List messages; /** @@ -31,6 +30,5 @@ public class ThreadRequest { * This can be useful for storing additional information about the object in a structured format. * Keys can be a maximum of 64 characters long, and values can be a maximum of 512 characters long. */ - @JsonProperty("metadata") Map metadata; } From 99162e09d7d1f779639632a61eadc46ae4b4035a Mon Sep 17 00:00:00 2001 From: vacuityv Date: Wed, 15 Nov 2023 07:18:44 +0800 Subject: [PATCH 22/30] feat(threads): add support for Threads in the Assistants (#400) --- .../theokanning/openai/client/OpenAiApi.java | 13 ++++++++ .../openai/service/OpenAiService.java | 13 +++++++- .../openai/service/ThreadTest.java | 30 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index c7bf2e10..cf09ff90 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -233,6 +233,19 @@ public interface OpenAiApi { @POST("/v1/threads") Single createThread(@Body ThreadRequest request); + @Headers({"OpenAI-Beta: assistants=v1"}) + @GET("/v1/threads/{thread_id}") + Single retrieveThread(@Path("thread_id") String threadId); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @POST("/v1/threads/{thread_id}") + Single modifyThread(@Path("thread_id") String threadId, @Body ThreadRequest request); + + @Headers({"OpenAI-Beta: assistants=v1"}) + @DELETE("/v1/threads/{thread_id}") + Single deleteThread(@Path("thread_id") String threadId); + + @Headers({"OpenAI-Beta: assistants=v1"}) @POST("/v1/threads/{thread_id}/messages") Single createMessage(@Path("thread_id") String threadId, @Body MessageRequest request); diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index a6bb9bc3..9c15522b 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.node.TextNode; -import com.sun.org.apache.xpath.internal.operations.Mod; import com.theokanning.openai.*; import com.theokanning.openai.assistants.*; import com.theokanning.openai.audio.CreateSpeechRequest; @@ -408,6 +407,18 @@ public Thread createThread(ThreadRequest request) { return execute(api.createThread(request)); } + public Thread retrieveThread(String threadId) { + return execute(api.retrieveThread(threadId)); + } + + public Thread modifyThread(String threadId, ThreadRequest request) { + return execute(api.modifyThread(threadId, request)); + } + + public DeleteResult deleteThread(String threadId) { + return execute(api.deleteThread(threadId)); + } + public Message createMessage(String threadId, MessageRequest request) { return execute(api.createMessage(threadId, request)); } diff --git a/service/src/test/java/com/theokanning/openai/service/ThreadTest.java b/service/src/test/java/com/theokanning/openai/service/ThreadTest.java index 24ff2bb0..2c203790 100644 --- a/service/src/test/java/com/theokanning/openai/service/ThreadTest.java +++ b/service/src/test/java/com/theokanning/openai/service/ThreadTest.java @@ -1,5 +1,6 @@ package com.theokanning.openai.service; +import com.theokanning.openai.DeleteResult; import com.theokanning.openai.messages.MessageRequest; import com.theokanning.openai.threads.Thread; import com.theokanning.openai.threads.ThreadRequest; @@ -26,6 +27,35 @@ void createThread() { .build(); Thread thread = service.createThread(threadRequest); + System.out.println(thread.getId()); assertEquals("thread", thread.getObject()); } + + @Test + void retrieveThread() { + String threadId = "thread_K82pTg9kmhxpplGqalW6IHlc"; + + Thread thread = service.retrieveThread(threadId); + System.out.println(thread.getMetadata()); + assertEquals("thread", thread.getObject()); + } + + @Test + void modifyThread() { + String threadId = "thread_K82pTg9kmhxpplGqalW6IHlc"; + Map metadata = new HashMap<>(); + metadata.put("action", "modify"); + ThreadRequest threadRequest = ThreadRequest.builder() + .metadata(metadata) + .build(); + Thread thread = service.modifyThread(threadId, threadRequest); + assertEquals("thread", thread.getObject()); + } + + @Test + void deleteThread() { + String threadId = "thread_K82pTg9kmhxpplGqalW6IHlc"; + DeleteResult deleteResult = service.deleteThread(threadId); + assertEquals("thread.deleted", deleteResult.getObject()); + } } \ No newline at end of file From 4ff2758999286d34d0764c7b281fc5aa41a028dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Bj=C3=B6rkman?= Date: Thu, 16 Nov 2023 00:28:24 +0100 Subject: [PATCH 23/30] 390 Added runs (#401) * 390 Added runs * 390 Added runs --------- Co-authored-by: Theo Kanning --- .../openai/runs/MessageCreation.java | 16 +++++ .../java/com/theokanning/openai/runs/Run.java | 45 ++++++++++++ .../openai/runs/RunCreateRequest.java | 23 +++++++ .../com/theokanning/openai/runs/RunStep.java | 39 +++++++++++ .../com/theokanning/openai/runs/RunSteps.java | 17 +++++ .../com/theokanning/openai/runs/Runs.java | 17 +++++ .../theokanning/openai/runs/StepDetails.java | 18 +++++ .../com/theokanning/openai/runs/Tool.java | 15 ++++ .../theokanning/openai/client/OpenAiApi.java | 10 +++ .../openai/service/OpenAiService.java | 43 +++++++----- .../theokanning/openai/service/RunTest.java | 69 +++++++++++++++++++ 11 files changed, 295 insertions(+), 17 deletions(-) create mode 100644 api/src/main/java/com/theokanning/openai/runs/MessageCreation.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/Run.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/RunStep.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/RunSteps.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/Runs.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/StepDetails.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/Tool.java create mode 100644 service/src/test/java/com/theokanning/openai/service/RunTest.java diff --git a/api/src/main/java/com/theokanning/openai/runs/MessageCreation.java b/api/src/main/java/com/theokanning/openai/runs/MessageCreation.java new file mode 100644 index 00000000..fe59d845 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/MessageCreation.java @@ -0,0 +1,16 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class MessageCreation { + @JsonProperty("message_id") + String messageId; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/Run.java b/api/src/main/java/com/theokanning/openai/runs/Run.java new file mode 100644 index 00000000..2c14dd10 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/Run.java @@ -0,0 +1,45 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Run { + + @JsonProperty("assistant_id") + String assistantId; + @JsonProperty("cancelled_at") + Long cancelledAt; + @JsonProperty("completed_at") + Long completedAt; + @JsonProperty("created_at") + Long createdAt; + @JsonProperty("expires_at") + Long expiresAt; + @JsonProperty("failed_at") + Long failedAt; + @JsonProperty("file_ids") + List fileIds; + String id; + String instructions; + @JsonProperty("last_error") + String lastError; + Map metadata; + String model; + String object; + @JsonProperty("started_at") + Long startedAt; + String status; + @JsonProperty("thread_id") + String threadId; + List tools; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java b/api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java new file mode 100644 index 00000000..93744d44 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java @@ -0,0 +1,23 @@ +package com.theokanning.openai.runs; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class RunCreateRequest { + String assistantId; + + // Optional + String model; + String instructions; + List tools; + Map metadata; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/RunStep.java b/api/src/main/java/com/theokanning/openai/runs/RunStep.java new file mode 100644 index 00000000..98c1eee3 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/RunStep.java @@ -0,0 +1,39 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class RunStep { + + @JsonProperty("assistant_id") + String assistantId; + @JsonProperty("canelled_at") + Long cancelledAt; + @JsonProperty("completed_at") + Long completedAt; + @JsonProperty("created_at") + Long createdAt; + @JsonProperty("expired_at") + Long expiredAt; + @JsonProperty("failed_at") + Long failedAt; + String id; + @JsonProperty("last_error") + String lastError; + String object; + @JsonProperty("run_id") + String runId; + String status; + @JsonProperty("step_details") + StepDetails stepDetails; + @JsonProperty("thread_id") + String threadId; + String type; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/RunSteps.java b/api/src/main/java/com/theokanning/openai/runs/RunSteps.java new file mode 100644 index 00000000..c7260cfe --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/RunSteps.java @@ -0,0 +1,17 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class RunSteps { + + String object; + List data; + @JsonProperty("first_id") + String firstId; + @JsonProperty("last_id") + String lastId; + @JsonProperty("has_more") + boolean hasMore; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/Runs.java b/api/src/main/java/com/theokanning/openai/runs/Runs.java new file mode 100644 index 00000000..2b07de7d --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/Runs.java @@ -0,0 +1,17 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class Runs { + + String object; + List data; + @JsonProperty("first_id") + String firstId; + @JsonProperty("last_id") + String lastId; + @JsonProperty("has_more") + boolean hasMore; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/StepDetails.java b/api/src/main/java/com/theokanning/openai/runs/StepDetails.java new file mode 100644 index 00000000..c17f9224 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/StepDetails.java @@ -0,0 +1,18 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class StepDetails { + + @JsonProperty("message_creation") + MessageCreation messageCreation; + String type; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/Tool.java b/api/src/main/java/com/theokanning/openai/runs/Tool.java new file mode 100644 index 00000000..abeee04b --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/Tool.java @@ -0,0 +1,15 @@ +package com.theokanning.openai.runs; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Tool { + + String type; +} diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index cf09ff90..bb57aa53 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -33,6 +33,8 @@ import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; +import com.theokanning.openai.runs.Run; +import com.theokanning.openai.runs.RunCreateRequest; import com.theokanning.openai.threads.Thread; import com.theokanning.openai.threads.ThreadRequest; import io.reactivex.Single; @@ -278,4 +280,12 @@ public interface OpenAiApi { @GET("/v1/threads/{thread_id}/messages/{message_id}/files") Single> listMessageFiles(@Path("thread_id") String threadId, @Path("message_id") String messageId, @QueryMap Map filterRequest); + @Headers("OpenAI-Beta: assistants=v1") + @POST("/v1/threads/{thread_id}/runs") + Single createRun(@Path("thread_id") String threadId, @Body RunCreateRequest runCreateRequest); + + @Headers("OpenAI-Beta: assistants=v1") + @GET("/v1/threads/{thread_id}/runs/{run_id}") + Single retrieveRun(@Path("thread_id") String threadId, @Path("run_id") String runId); + } diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index 9c15522b..bbd6d550 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -8,11 +8,7 @@ import com.fasterxml.jackson.databind.node.TextNode; import com.theokanning.openai.*; import com.theokanning.openai.assistants.*; -import com.theokanning.openai.audio.CreateSpeechRequest; -import com.theokanning.openai.audio.CreateTranscriptionRequest; -import com.theokanning.openai.audio.CreateTranslationRequest; -import com.theokanning.openai.audio.TranscriptionResult; -import com.theokanning.openai.audio.TranslationResult; +import com.theokanning.openai.audio.*; import com.theokanning.openai.billing.BillingUsage; import com.theokanning.openai.billing.Subscription; import com.theokanning.openai.client.OpenAiApi; @@ -42,6 +38,8 @@ import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; +import com.theokanning.openai.runs.Run; +import com.theokanning.openai.runs.RunCreateRequest; import com.theokanning.openai.threads.Thread; import com.theokanning.openai.threads.ThreadRequest; import io.reactivex.BackpressureStrategy; @@ -166,7 +164,7 @@ public List listFiles() { public File uploadFile(String purpose, String filepath) { java.io.File file = new java.io.File(filepath); - RequestBody purposeBody = RequestBody.create(okhttp3.MultipartBody.FORM, purpose); + RequestBody purposeBody = RequestBody.create(MultipartBody.FORM, purpose); RequestBody fileBody = RequestBody.create(MediaType.parse("text"), file); MultipartBody.Part body = MultipartBody.Part.createFormData("file", filepath, fileBody); @@ -364,7 +362,7 @@ public ModerationResult createModeration(ModerationRequest request) { public ResponseBody createSpeech(CreateSpeechRequest request) { return execute(api.createSpeech(request)); } - + public Assistant createAssistant(AssistantRequest request) { return execute(api.createAssistant(request)); } @@ -382,7 +380,8 @@ public DeleteResult deleteAssistant(String assistantId) { } public ListAssistant listAssistants(ListAssistantQueryRequest filterRequest) { - Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() {}); + Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() { + }); return execute(api.listAssistants(queryParameters)); } @@ -399,7 +398,8 @@ public DeleteResult deleteAssistantFile(String assistantId, String fileId) { } public ListAssistant listAssistantFiles(String assistantId, ListAssistantQueryRequest filterRequest) { - Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() {}); + Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() { + }); return execute(api.listAssistantFiles(assistantId, queryParameters)); } @@ -424,11 +424,11 @@ public Message createMessage(String threadId, MessageRequest request) { } public Message retrieveMessage(String threadId, String messageId) { - return execute(api.retrieveMessage(threadId,messageId)); + return execute(api.retrieveMessage(threadId, messageId)); } public Message modifyMessage(String threadId, String messageId, ModifyMessageRequest request) { - return execute(api.modifyMessage(threadId,messageId, request)); + return execute(api.modifyMessage(threadId, messageId, request)); } public OpenAiResponse listMessages(String threadId) { @@ -436,23 +436,32 @@ public OpenAiResponse listMessages(String threadId) { } public OpenAiResponse listMessages(String threadId, ListSearchParameters params) { - Map queryParameters = mapper.convertValue(params, new TypeReference>() {}); - return execute(api.listMessages(threadId,queryParameters)); + Map queryParameters = mapper.convertValue(params, new TypeReference>() { + }); + return execute(api.listMessages(threadId, queryParameters)); } public MessageFile retrieveMessageFile(String threadId, String messageId, String fileId) { - return execute(api.retrieveMessageFile(threadId,messageId, fileId)); + return execute(api.retrieveMessageFile(threadId, messageId, fileId)); } public OpenAiResponse listMessageFiles(String threadId, String messageId) { - return execute(api.listMessageFiles(threadId,messageId)); + return execute(api.listMessageFiles(threadId, messageId)); } public OpenAiResponse listMessageFiles(String threadId, String messageId, ListSearchParameters params) { - Map queryParameters = mapper.convertValue(params, new TypeReference>() {}); - return execute(api.listMessageFiles(threadId,messageId, queryParameters)); + Map queryParameters = mapper.convertValue(params, new TypeReference>() { + }); + return execute(api.listMessageFiles(threadId, messageId, queryParameters)); + } + + public Run createRun(String threadId, RunCreateRequest runCreateRequest) { + return execute(api.createRun(threadId, runCreateRequest)); } + public Run retrieveRun(String threadId, String runId) { + return execute(api.retrieveRun(threadId, runId)); + } /** * Calls the Open AI api, returns the response, and parses error messages if the request fails diff --git a/service/src/test/java/com/theokanning/openai/service/RunTest.java b/service/src/test/java/com/theokanning/openai/service/RunTest.java new file mode 100644 index 00000000..2bd0c166 --- /dev/null +++ b/service/src/test/java/com/theokanning/openai/service/RunTest.java @@ -0,0 +1,69 @@ +package com.theokanning.openai.service; + +import com.theokanning.openai.OpenAiResponse; +import com.theokanning.openai.assistants.Assistant; +import com.theokanning.openai.assistants.AssistantRequest; +import com.theokanning.openai.messages.Message; +import com.theokanning.openai.messages.MessageRequest; +import com.theokanning.openai.runs.Run; +import com.theokanning.openai.runs.RunCreateRequest; +import com.theokanning.openai.threads.Thread; +import com.theokanning.openai.threads.ThreadRequest; +import com.theokanning.openai.utils.TikTokensUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class RunTest { + String token = System.getenv("OPENAI_TOKEN"); + OpenAiService service = new OpenAiService(token); + + @Test + @Timeout(10) + void createRetrieveRun() { + AssistantRequest assistantRequest = AssistantRequest.builder() + .model(TikTokensUtil.ModelEnum.GPT_4_1106_preview.getName()) + .name("MATH_TUTOR") + .instructions("You are a personal Math Tutor.") + .build(); + Assistant assistant = service.createAssistant(assistantRequest); + + ThreadRequest threadRequest = ThreadRequest.builder() + .build(); + Thread thread = service.createThread(threadRequest); + + MessageRequest messageRequest = MessageRequest.builder() + .content("Hello") + .build(); + + Message message = service.createMessage(thread.getId(), messageRequest); + + RunCreateRequest runCreateRequest = RunCreateRequest.builder() + .assistantId(assistant.getId()) + .build(); + + Run run = service.createRun(thread.getId(), runCreateRequest); + assertNotNull(run); + + Run retrievedRun; + do { + retrievedRun = service.retrieveRun(thread.getId(), run.getId()); + assertEquals(run.getId(), retrievedRun.getId()); + } + while (!(retrievedRun.getStatus().equals("completed")) && !(retrievedRun.getStatus().equals("failed"))); + + + assertNotNull(retrievedRun); + + OpenAiResponse response = service.listMessages(thread.getId()); + + List messages = response.getData(); + assertEquals(2, messages.size()); + assertEquals("user", messages.get(1).getRole()); + assertEquals("assistant", messages.get(0).getRole()); + } +} From 741cfe26bf371ae98fb4e34b46f3208974ce8355 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Wed, 15 Nov 2023 18:08:49 -0600 Subject: [PATCH 24/30] Add MessageContent types and simplify list querying (#405) Changed assistants to use the new shared ListSearchParams. --- .../openai/assistants/ListAssistant.java | 16 ------- .../assistants/ListAssistantQueryRequest.java | 39 ---------------- .../theokanning/openai/messages/Message.java | 2 +- .../openai/messages/MessageContent.java | 14 +++++- .../openai/messages/content/Annotation.java | 44 ++++++++++++++++++ .../openai/messages/content/FileCitation.java | 29 ++++++++++++ .../openai/messages/content/FilePath.java | 23 ++++++++++ .../openai/messages/content/ImageFile.java | 23 ++++++++++ .../openai/messages/content/Text.java | 28 +++++++++++ .../java/com/theokanning/openai/JsonTest.java | 2 + api/src/test/resources/fixtures/Message.json | 46 +++++++++++++++++++ .../theokanning/openai/client/OpenAiApi.java | 4 +- .../openai/service/OpenAiService.java | 8 ++-- .../openai/service/AssistantTest.java | 10 ++-- 14 files changed, 220 insertions(+), 68 deletions(-) delete mode 100644 api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java delete mode 100644 api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/content/Annotation.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/content/FileCitation.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/content/FilePath.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/content/ImageFile.java create mode 100644 api/src/main/java/com/theokanning/openai/messages/content/Text.java create mode 100644 api/src/test/resources/fixtures/Message.json diff --git a/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java b/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java deleted file mode 100644 index 39884991..00000000 --- a/api/src/main/java/com/theokanning/openai/assistants/ListAssistant.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.theokanning.openai.assistants; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.theokanning.openai.OpenAiResponse; - -public class ListAssistant extends OpenAiResponse { - - @JsonProperty("first_id") - String firstId; - - @JsonProperty("last_id") - String lastId; - - @JsonProperty("has_more") - boolean hasMore; -} diff --git a/api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java b/api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java deleted file mode 100644 index 3e5f3c68..00000000 --- a/api/src/main/java/com/theokanning/openai/assistants/ListAssistantQueryRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.theokanning.openai.assistants; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Data -public class ListAssistantQueryRequest { - /** - * A limit on the number of objects to be returned. - * Limit can range between 1 and 100, and the default is 20 - */ - - Integer limit; - - /** - * Sort order by the 'created_at' timestamp of the objects. - * 'asc' for ascending order and 'desc' for descending order. - */ - AssistantSortOrder order; - - /** - * A cursor for use in pagination. after is an object ID that defines your place in the list. - * For instance, if you make a list request and receive 100 objects, ending with obj_foo, - * your subsequent call can include after=obj_foo in order to fetch the next page of the list - */ - String after; - - /** - * A cursor for use in pagination. before is an object ID that defines your place in the list. - * For instance, if you make a list request and receive 100 objects, ending with obj_foo, - * your subsequent call can include before=obj_foo in order to fetch the previous page of the list. - */ - String before; -} diff --git a/api/src/main/java/com/theokanning/openai/messages/Message.java b/api/src/main/java/com/theokanning/openai/messages/Message.java index 940cd934..44144780 100644 --- a/api/src/main/java/com/theokanning/openai/messages/Message.java +++ b/api/src/main/java/com/theokanning/openai/messages/Message.java @@ -50,7 +50,7 @@ public class Message { /** * The content of the message in an array of text and/or images. */ - List content; + List content; /** * If applicable, the ID of the assistant that authored this message. diff --git a/api/src/main/java/com/theokanning/openai/messages/MessageContent.java b/api/src/main/java/com/theokanning/openai/messages/MessageContent.java index 6db58112..a9ff489e 100644 --- a/api/src/main/java/com/theokanning/openai/messages/MessageContent.java +++ b/api/src/main/java/com/theokanning/openai/messages/MessageContent.java @@ -1,5 +1,8 @@ package com.theokanning.openai.messages; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.messages.content.ImageFile; +import com.theokanning.openai.messages.content.Text; import lombok.Data; @@ -15,5 +18,14 @@ public class MessageContent { */ String type; - // todo handle different content types + /** + * Text content of the message. Only present if type == text + */ + Text text; + + /** + * The image content of a message. Only present if type == image_file + */ + @JsonProperty("image_file") + ImageFile imageFile; } diff --git a/api/src/main/java/com/theokanning/openai/messages/content/Annotation.java b/api/src/main/java/com/theokanning/openai/messages/content/Annotation.java new file mode 100644 index 00000000..473a5dfc --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/content/Annotation.java @@ -0,0 +1,44 @@ +package com.theokanning.openai.messages.content; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * An annotation for a text Message + *

+ * https://platform.openai.com/docs/api-reference/messages/object + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Annotation { + /** + * The type of annotation, either file_citation or file_path + */ + String type; + + /** + * The text in the message content that needs to be replaced + */ + String text; + + /** + * File citation details, only present when type == file_citation + */ + @JsonProperty("file_citation") + FileCitation fileCitation; + + /** + * File path details, only present when type == file_path + */ + @JsonProperty("file_path") + FilePath filePath; + + @JsonProperty("start_index") + int startIndex; + + @JsonProperty("end_index") + int endIndex; +} diff --git a/api/src/main/java/com/theokanning/openai/messages/content/FileCitation.java b/api/src/main/java/com/theokanning/openai/messages/content/FileCitation.java new file mode 100644 index 00000000..ff486d55 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/content/FileCitation.java @@ -0,0 +1,29 @@ +package com.theokanning.openai.messages.content; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * A citation within the message that points to a specific quote from a specific File associated with the + * assistant or the message. Generated when the assistant uses the "retrieval" tool to search files. + *

+ * https://platform.openai.com/docs/api-reference/messages/object + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FileCitation { + + /** + * The ID of the specific File the citation is from. + */ + @JsonProperty("file_id") + String fileId; + + /** + * The specific quote in the file. + */ + String quote; +} diff --git a/api/src/main/java/com/theokanning/openai/messages/content/FilePath.java b/api/src/main/java/com/theokanning/openai/messages/content/FilePath.java new file mode 100644 index 00000000..b11cc1af --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/content/FilePath.java @@ -0,0 +1,23 @@ +package com.theokanning.openai.messages.content; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * A URL for the file that's generated when the assistant used the code_interpreter tool to generate a file. + *

+ * https://platform.openai.com/docs/api-reference/messages/object + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FilePath { + + /** + * The ID of the file that was generated + */ + @JsonProperty("file_id") + String fileId; +} diff --git a/api/src/main/java/com/theokanning/openai/messages/content/ImageFile.java b/api/src/main/java/com/theokanning/openai/messages/content/ImageFile.java new file mode 100644 index 00000000..2a43fa02 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/content/ImageFile.java @@ -0,0 +1,23 @@ +package com.theokanning.openai.messages.content; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * References an image File int eh content of a message. + *

+ * /https://platform.openai.com/docs/api-reference/messages/object + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ImageFile { + + /** + * The File ID of the image in the message content. + */ + @JsonProperty("file_id") + String fileId; +} diff --git a/api/src/main/java/com/theokanning/openai/messages/content/Text.java b/api/src/main/java/com/theokanning/openai/messages/content/Text.java new file mode 100644 index 00000000..6efa28bf --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/messages/content/Text.java @@ -0,0 +1,28 @@ +package com.theokanning.openai.messages.content; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * The text content that is part of a message + *

+ * https://platform.openai.com/docs/api-reference/messages/object + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Text { + + /** + * The data that makes up the text. + */ + String value; + + /** + * Text annotations that show additional details + */ + List annotations; +} diff --git a/api/src/test/java/com/theokanning/openai/JsonTest.java b/api/src/test/java/com/theokanning/openai/JsonTest.java index 68b98155..58b5c5f2 100644 --- a/api/src/test/java/com/theokanning/openai/JsonTest.java +++ b/api/src/test/java/com/theokanning/openai/JsonTest.java @@ -18,6 +18,7 @@ import com.theokanning.openai.finetune.FineTuneEvent; import com.theokanning.openai.finetune.FineTuneResult; import com.theokanning.openai.image.ImageResult; +import com.theokanning.openai.messages.Message; import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; @@ -50,6 +51,7 @@ public class JsonTest { ImageResult.class, TranscriptionResult.class, TranslationResult.class, + Message.class, Model.class, ModerationRequest.class, ModerationResult.class diff --git a/api/src/test/resources/fixtures/Message.json b/api/src/test/resources/fixtures/Message.json new file mode 100644 index 00000000..878e7a47 --- /dev/null +++ b/api/src/test/resources/fixtures/Message.json @@ -0,0 +1,46 @@ +{ + "id": "msg_abc123", + "object": "thread.message", + "created_at": 1698983503, + "thread_id": "thread_abc123", + "role": "assistant", + "content": [ + { + "type": "text", + "text": { + "value": "Hi! How can I help you today?", + "annotations": [ + { + "type": "file_citation", + "text": "file citation text", + "file_citation": { + "file_id": "file citation id", + "quote": "Enough, Reggie" + }, + "start_index": 0, + "end_index": 1 + }, + { + "type": "file_path", + "text": "file path text", + "file_path": { + "file_id": "file id" + }, + "start_index": 1, + "end_index": 2 + } + ] + } + }, + { + "type": "image_file", + "image_file": { + "file_id": "image file id" + } + } + ], + "file_ids": [], + "assistant_id": "asst_abc123", + "run_id": "run_abc123", + "metadata": {} +} diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index bb57aa53..36fc7434 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -213,7 +213,7 @@ public interface OpenAiApi { @Headers({"OpenAI-Beta: assistants=v1"}) @GET("/v1/assistants") - Single> listAssistants(@QueryMap Map filterRequest); + Single> listAssistants(@QueryMap Map filterRequest); @Headers({"OpenAI-Beta: assistants=v1"}) @POST("/v1/assistants/{assistant_id}/files") @@ -229,7 +229,7 @@ public interface OpenAiApi { @Headers({"OpenAI-Beta: assistants=v1"}) @GET("/v1/assistants/{assistant_id}/files") - Single> listAssistantFiles(@Path("assistant_id") String assistantId, @QueryMap Map filterRequest); + Single> listAssistantFiles(@Path("assistant_id") String assistantId, @QueryMap Map filterRequest); @Headers({"OpenAI-Beta: assistants=v1"}) @POST("/v1/threads") diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index bbd6d550..d59f7e3c 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -379,8 +379,8 @@ public DeleteResult deleteAssistant(String assistantId) { return execute(api.deleteAssistant(assistantId)); } - public ListAssistant listAssistants(ListAssistantQueryRequest filterRequest) { - Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() { + public OpenAiResponse listAssistants(ListSearchParameters params) { + Map queryParameters = mapper.convertValue(params, new TypeReference>() { }); return execute(api.listAssistants(queryParameters)); } @@ -397,8 +397,8 @@ public DeleteResult deleteAssistantFile(String assistantId, String fileId) { return execute(api.deleteAssistantFile(assistantId, fileId)); } - public ListAssistant listAssistantFiles(String assistantId, ListAssistantQueryRequest filterRequest) { - Map queryParameters = mapper.convertValue(filterRequest, new TypeReference>() { + public OpenAiResponse listAssistantFiles(String assistantId, ListSearchParameters params) { + Map queryParameters = mapper.convertValue(params, new TypeReference>() { }); return execute(api.listAssistantFiles(assistantId, queryParameters)); } diff --git a/service/src/test/java/com/theokanning/openai/service/AssistantTest.java b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java index d0ef40d7..cf4fc361 100644 --- a/service/src/test/java/com/theokanning/openai/service/AssistantTest.java +++ b/service/src/test/java/com/theokanning/openai/service/AssistantTest.java @@ -1,6 +1,8 @@ package com.theokanning.openai.service; import com.theokanning.openai.DeleteResult; +import com.theokanning.openai.ListSearchParameters; +import com.theokanning.openai.OpenAiResponse; import com.theokanning.openai.assistants.*; import com.theokanning.openai.file.File; import com.theokanning.openai.utils.TikTokensUtil; @@ -21,8 +23,6 @@ public class AssistantTest { static OpenAiService service = new OpenAiService(token); - - @Test void retrieveAssistant() { Assistant createAssistantResponse = createAndValidateAssistant(); @@ -58,7 +58,7 @@ void deleteAssistant() { @Test void listAssistants() { - ListAssistant assistants = service.listAssistants(ListAssistantQueryRequest.builder().build()); + OpenAiResponse assistants = service.listAssistants(ListSearchParameters.builder().build()); assertNotNull(assistants); assertFalse(assistants.getData().isEmpty()); @@ -101,10 +101,10 @@ void listAssistantFiles() { @AfterAll static void clean() { //Clean up all data created during this test - ListAssistantQueryRequest queryFilter = ListAssistantQueryRequest.builder() + ListSearchParameters queryFilter = ListSearchParameters.builder() .limit(100) .build(); - ListAssistant assistantListAssistant = service.listAssistants(queryFilter); + OpenAiResponse assistantListAssistant = service.listAssistants(queryFilter); assistantListAssistant.getData().forEach(assistant ->{ service.deleteAssistant(assistant.getId()); }); From 56b302b8598ea9c8d7d3968b8882a74461c6a402 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Wed, 15 Nov 2023 18:19:45 -0600 Subject: [PATCH 25/30] Update to version 0.18.0 (#406) --- gradle.properties | 2 +- .../openai/service/MessageTest.java | 6 +++++- .../theokanning/openai/service/ThreadTest.java | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/gradle.properties b/gradle.properties index 671b33b6..e12dac0d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.theokanning.openai-gpt3-java -VERSION_NAME=0.17.0 +VERSION_NAME=0.18.0 POM_URL=https://github.com/theokanning/openai-java POM_SCM_URL=https://github.com/theokanning/openai-java diff --git a/service/src/test/java/com/theokanning/openai/service/MessageTest.java b/service/src/test/java/com/theokanning/openai/service/MessageTest.java index a1fe99cb..06a903c0 100644 --- a/service/src/test/java/com/theokanning/openai/service/MessageTest.java +++ b/service/src/test/java/com/theokanning/openai/service/MessageTest.java @@ -39,7 +39,11 @@ static void setup() { @AfterAll static void teardown() { - // todo delete thread + try { + service.deleteThread(threadId); + } catch (Exception e) { + // ignore + } } @Test diff --git a/service/src/test/java/com/theokanning/openai/service/ThreadTest.java b/service/src/test/java/com/theokanning/openai/service/ThreadTest.java index 2c203790..212653af 100644 --- a/service/src/test/java/com/theokanning/openai/service/ThreadTest.java +++ b/service/src/test/java/com/theokanning/openai/service/ThreadTest.java @@ -4,19 +4,25 @@ import com.theokanning.openai.messages.MessageRequest; import com.theokanning.openai.threads.Thread; import com.theokanning.openai.threads.ThreadRequest; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import java.util.*; import static org.junit.jupiter.api.Assertions.*; - +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ThreadTest { String token = System.getenv("OPENAI_TOKEN"); OpenAiService service = new OpenAiService(token); + static String threadId; + @Test + @Order(1) void createThread() { MessageRequest messageRequest = MessageRequest.builder() .content("Hello") @@ -27,22 +33,21 @@ void createThread() { .build(); Thread thread = service.createThread(threadRequest); - System.out.println(thread.getId()); + threadId = thread.getId(); assertEquals("thread", thread.getObject()); } @Test + @Order(2) void retrieveThread() { - String threadId = "thread_K82pTg9kmhxpplGqalW6IHlc"; - Thread thread = service.retrieveThread(threadId); System.out.println(thread.getMetadata()); assertEquals("thread", thread.getObject()); } @Test + @Order(3) void modifyThread() { - String threadId = "thread_K82pTg9kmhxpplGqalW6IHlc"; Map metadata = new HashMap<>(); metadata.put("action", "modify"); ThreadRequest threadRequest = ThreadRequest.builder() @@ -50,11 +55,12 @@ void modifyThread() { .build(); Thread thread = service.modifyThread(threadId, threadRequest); assertEquals("thread", thread.getObject()); + assertEquals("modify", thread.getMetadata().get("action")); } @Test + @Order(4) void deleteThread() { - String threadId = "thread_K82pTg9kmhxpplGqalW6IHlc"; DeleteResult deleteResult = service.deleteThread(threadId); assertEquals("thread.deleted", deleteResult.getObject()); } From f63d666606f6727c3051976a5a38244aefc19147 Mon Sep 17 00:00:00 2001 From: vacuityv Date: Fri, 17 Nov 2023 07:41:44 +0800 Subject: [PATCH 26/30] fix(assistant-run): run ans run-step (#408) --- .../theokanning/openai/common/LastError.java | 24 +++++++ .../runs/CreateThreadAndRunRequest.java | 39 +++++++++++ .../openai/runs/MessageCreation.java | 3 +- .../openai/runs/RequiredAction.java | 26 +++++++ .../java/com/theokanning/openai/runs/Run.java | 69 ++++++++++++------- .../openai/runs/RunCreateRequest.java | 4 ++ .../com/theokanning/openai/runs/RunImage.java | 24 +++++++ .../com/theokanning/openai/runs/RunStep.java | 61 ++++++++++------ .../com/theokanning/openai/runs/RunSteps.java | 17 ----- .../com/theokanning/openai/runs/Runs.java | 17 ----- .../theokanning/openai/runs/StepDetails.java | 13 +++- .../runs/SubmitToolOutputRequestItem.java | 26 +++++++ .../openai/runs/SubmitToolOutputs.java | 26 +++++++ .../openai/runs/SubmitToolOutputsRequest.java | 26 +++++++ .../com/theokanning/openai/runs/ToolCall.java | 34 +++++++++ .../openai/runs/ToolCallCodeInterpreter.java | 26 +++++++ .../runs/ToolCallCodeInterpreterOutput.java | 26 +++++++ .../runs/{Tool.java => ToolCallFunction.java} | 18 +++-- .../theokanning/openai/client/OpenAiApi.java | 32 +++++++++ .../openai/service/OpenAiService.java | 31 +++++++++ 20 files changed, 455 insertions(+), 87 deletions(-) create mode 100644 api/src/main/java/com/theokanning/openai/common/LastError.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/CreateThreadAndRunRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/RequiredAction.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/RunImage.java delete mode 100644 api/src/main/java/com/theokanning/openai/runs/RunSteps.java delete mode 100644 api/src/main/java/com/theokanning/openai/runs/Runs.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputRequestItem.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputs.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/ToolCall.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreter.java create mode 100644 api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreterOutput.java rename api/src/main/java/com/theokanning/openai/runs/{Tool.java => ToolCallFunction.java} (50%) diff --git a/api/src/main/java/com/theokanning/openai/common/LastError.java b/api/src/main/java/com/theokanning/openai/common/LastError.java new file mode 100644 index 00000000..6a9f99de --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/common/LastError.java @@ -0,0 +1,24 @@ +package com.theokanning.openai.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:27 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LastError { + + private String code; + + private String message; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/CreateThreadAndRunRequest.java b/api/src/main/java/com/theokanning/openai/runs/CreateThreadAndRunRequest.java new file mode 100644 index 00000000..b27de696 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/CreateThreadAndRunRequest.java @@ -0,0 +1,39 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.assistants.Tool; +import com.theokanning.openai.threads.ThreadRequest; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 23:08 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CreateThreadAndRunRequest { + + @JsonProperty("assistant_id") + private String assistantId; + + private ThreadRequest thread; + + private String model; + + private String instructions; + + private List tools; + + private Map metadata; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/MessageCreation.java b/api/src/main/java/com/theokanning/openai/runs/MessageCreation.java index fe59d845..535fd7e2 100644 --- a/api/src/main/java/com/theokanning/openai/runs/MessageCreation.java +++ b/api/src/main/java/com/theokanning/openai/runs/MessageCreation.java @@ -6,11 +6,12 @@ import lombok.Data; import lombok.NoArgsConstructor; +@Data @Builder @NoArgsConstructor @AllArgsConstructor -@Data public class MessageCreation { + @JsonProperty("message_id") String messageId; } diff --git a/api/src/main/java/com/theokanning/openai/runs/RequiredAction.java b/api/src/main/java/com/theokanning/openai/runs/RequiredAction.java new file mode 100644 index 00000000..959b89dc --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/RequiredAction.java @@ -0,0 +1,26 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:44 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RequiredAction { + + private String type; + + @JsonProperty("submit_tool_outputs") + private SubmitToolOutputs submitToolOutputs; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/Run.java b/api/src/main/java/com/theokanning/openai/runs/Run.java index 2c14dd10..1da9ec95 100644 --- a/api/src/main/java/com/theokanning/openai/runs/Run.java +++ b/api/src/main/java/com/theokanning/openai/runs/Run.java @@ -1,6 +1,8 @@ package com.theokanning.openai.runs; import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.assistants.Tool; +import com.theokanning.openai.common.LastError; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -9,37 +11,56 @@ import java.util.List; import java.util.Map; +@Data @Builder @NoArgsConstructor @AllArgsConstructor -@Data public class Run { - @JsonProperty("assistant_id") - String assistantId; - @JsonProperty("cancelled_at") - Long cancelledAt; - @JsonProperty("completed_at") - Long completedAt; + private String id; + + private String object; + @JsonProperty("created_at") - Long createdAt; + private Integer createdAt; + + @JsonProperty("thread_id") + private String threadId; + + @JsonProperty("assistant_id") + private String assistantId; + + private String status; + + @JsonProperty("required_action") + private RequiredAction requiredAction; + + @JsonProperty("last_error") + private LastError lastError; + @JsonProperty("expires_at") - Long expiresAt; + private Integer expiresAt; + + @JsonProperty("started_at") + private Integer startedAt; + + @JsonProperty("cancelled_at") + private Integer cancelledAt; + @JsonProperty("failed_at") - Long failedAt; + private Integer failedAt; + + @JsonProperty("completed_at") + private Integer completedAt; + + private String model; + + private String instructions; + + private List tools; + @JsonProperty("file_ids") - List fileIds; - String id; - String instructions; - @JsonProperty("last_error") - String lastError; - Map metadata; - String model; - String object; - @JsonProperty("started_at") - Long startedAt; - String status; - @JsonProperty("thread_id") - String threadId; - List tools; + private List fileIds; + + private Map metadata; } diff --git a/api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java b/api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java index 93744d44..cba5f283 100644 --- a/api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java +++ b/api/src/main/java/com/theokanning/openai/runs/RunCreateRequest.java @@ -1,5 +1,6 @@ package com.theokanning.openai.runs; +import com.theokanning.openai.assistants.Tool; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -17,7 +18,10 @@ public class RunCreateRequest { // Optional String model; + String instructions; + List tools; + Map metadata; } diff --git a/api/src/main/java/com/theokanning/openai/runs/RunImage.java b/api/src/main/java/com/theokanning/openai/runs/RunImage.java new file mode 100644 index 00000000..18135187 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/RunImage.java @@ -0,0 +1,24 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:33 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RunImage { + + @JsonProperty("file_id") + private String fileId; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/RunStep.java b/api/src/main/java/com/theokanning/openai/runs/RunStep.java index 98c1eee3..7cd39342 100644 --- a/api/src/main/java/com/theokanning/openai/runs/RunStep.java +++ b/api/src/main/java/com/theokanning/openai/runs/RunStep.java @@ -1,39 +1,58 @@ package com.theokanning.openai.runs; import com.fasterxml.jackson.annotation.JsonProperty; +import com.theokanning.openai.common.LastError; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.Map; + @Builder @NoArgsConstructor @AllArgsConstructor @Data public class RunStep { - @JsonProperty("assistant_id") - String assistantId; - @JsonProperty("canelled_at") - Long cancelledAt; - @JsonProperty("completed_at") - Long completedAt; + private String id; + + private String object; + @JsonProperty("created_at") - Long createdAt; - @JsonProperty("expired_at") - Long expiredAt; - @JsonProperty("failed_at") - Long failedAt; - String id; - @JsonProperty("last_error") - String lastError; - String object; + private Integer createdAt; + + @JsonProperty("assistant_id") + private String assistantId; + + @JsonProperty("thread_id") + private String threadId; + @JsonProperty("run_id") - String runId; - String status; + private String runId; + + private String type; + + private String status; + @JsonProperty("step_details") - StepDetails stepDetails; - @JsonProperty("thread_id") - String threadId; - String type; + private StepDetails stepDetails; + + @JsonProperty("last_error") + private LastError lastError; + + @JsonProperty("expired_at") + private Integer expiredAt; + + @JsonProperty("cancelled_at") + private Integer cancelledAt; + + @JsonProperty("failed_at") + private Integer failedAt; + + @JsonProperty("completed_at") + private Integer completedAt; + + private Map metadata; + } diff --git a/api/src/main/java/com/theokanning/openai/runs/RunSteps.java b/api/src/main/java/com/theokanning/openai/runs/RunSteps.java deleted file mode 100644 index c7260cfe..00000000 --- a/api/src/main/java/com/theokanning/openai/runs/RunSteps.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.theokanning.openai.runs; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -public class RunSteps { - - String object; - List data; - @JsonProperty("first_id") - String firstId; - @JsonProperty("last_id") - String lastId; - @JsonProperty("has_more") - boolean hasMore; -} diff --git a/api/src/main/java/com/theokanning/openai/runs/Runs.java b/api/src/main/java/com/theokanning/openai/runs/Runs.java deleted file mode 100644 index 2b07de7d..00000000 --- a/api/src/main/java/com/theokanning/openai/runs/Runs.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.theokanning.openai.runs; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -public class Runs { - - String object; - List data; - @JsonProperty("first_id") - String firstId; - @JsonProperty("last_id") - String lastId; - @JsonProperty("has_more") - boolean hasMore; -} diff --git a/api/src/main/java/com/theokanning/openai/runs/StepDetails.java b/api/src/main/java/com/theokanning/openai/runs/StepDetails.java index c17f9224..08972623 100644 --- a/api/src/main/java/com/theokanning/openai/runs/StepDetails.java +++ b/api/src/main/java/com/theokanning/openai/runs/StepDetails.java @@ -6,13 +6,20 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + +@Data @Builder @NoArgsConstructor @AllArgsConstructor -@Data public class StepDetails { + + private String type; + @JsonProperty("message_creation") - MessageCreation messageCreation; - String type; + private MessageCreation messageCreation; + + @JsonProperty("tool_calls") + private List toolCalls; } diff --git a/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputRequestItem.java b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputRequestItem.java new file mode 100644 index 00000000..ec2b346a --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputRequestItem.java @@ -0,0 +1,26 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:45 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SubmitToolOutputRequestItem { + + @JsonProperty("tool_call_id") + private String toolCallId; + + private String output; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputs.java b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputs.java new file mode 100644 index 00000000..e0aca757 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputs.java @@ -0,0 +1,26 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:45 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SubmitToolOutputs { + + @JsonProperty("tool_calls") + List toolCalls; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java new file mode 100644 index 00000000..3086610c --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java @@ -0,0 +1,26 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:45 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SubmitToolOutputsRequest { + + @JsonProperty("tool_outputs") + private List tool_outputs; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/ToolCall.java b/api/src/main/java/com/theokanning/openai/runs/ToolCall.java new file mode 100644 index 00000000..8aa6c26c --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/ToolCall.java @@ -0,0 +1,34 @@ +package com.theokanning.openai.runs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:32 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ToolCall { + + private String id; + + private String type; + + @JsonProperty("code_interpreter") + private ToolCallCodeInterpreter codeInterpreter; + + private Map retrieval; + + private ToolCallFunction function; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreter.java b/api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreter.java new file mode 100644 index 00000000..c2a3a446 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreter.java @@ -0,0 +1,26 @@ +package com.theokanning.openai.runs; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:34 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ToolCallCodeInterpreter { + + private String input; + + private List outputs; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreterOutput.java b/api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreterOutput.java new file mode 100644 index 00000000..179ef2f1 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/runs/ToolCallCodeInterpreterOutput.java @@ -0,0 +1,26 @@ +package com.theokanning.openai.runs; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:34 + **/ + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ToolCallCodeInterpreterOutput { + + private String type; + + private String logs; + + private RunImage image; +} diff --git a/api/src/main/java/com/theokanning/openai/runs/Tool.java b/api/src/main/java/com/theokanning/openai/runs/ToolCallFunction.java similarity index 50% rename from api/src/main/java/com/theokanning/openai/runs/Tool.java rename to api/src/main/java/com/theokanning/openai/runs/ToolCallFunction.java index abeee04b..34de58d1 100644 --- a/api/src/main/java/com/theokanning/openai/runs/Tool.java +++ b/api/src/main/java/com/theokanning/openai/runs/ToolCallFunction.java @@ -5,11 +5,21 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * @description: + * @author: vacuity + * @create: 2023-11-16 22:38 + **/ + +@Data @Builder @NoArgsConstructor @AllArgsConstructor -@Data -public class Tool { - - String type; +public class ToolCallFunction { + + private String name; + + private String arguments; + + private String output; } diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index 36fc7434..5bf3e732 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -1,6 +1,7 @@ package com.theokanning.openai.client; import com.theokanning.openai.DeleteResult; +import com.theokanning.openai.ListSearchParameters; import com.theokanning.openai.OpenAiResponse; import com.theokanning.openai.assistants.*; import com.theokanning.openai.audio.CreateSpeechRequest; @@ -33,8 +34,11 @@ import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; +import com.theokanning.openai.runs.CreateThreadAndRunRequest; import com.theokanning.openai.runs.Run; import com.theokanning.openai.runs.RunCreateRequest; +import com.theokanning.openai.runs.RunStep; +import com.theokanning.openai.runs.SubmitToolOutputsRequest; import com.theokanning.openai.threads.Thread; import com.theokanning.openai.threads.ThreadRequest; import io.reactivex.Single; @@ -288,4 +292,32 @@ public interface OpenAiApi { @GET("/v1/threads/{thread_id}/runs/{run_id}") Single retrieveRun(@Path("thread_id") String threadId, @Path("run_id") String runId); + @Headers("OpenAI-Beta: assistants=v1") + @POST("/v1/threads/{thread_id}/runs/{run_id}") + Single modifyRun(@Path("thread_id") String threadId, @Path("run_id") String runId, @Body Map metadata); + + @Headers("OpenAI-Beta: assistants=v1") + @GET("/v1/threads/{thread_id}/runs") + Single> listRuns(@Path("thread_id") String threadId, @Body ListSearchParameters listSearchParameters); + + @Headers("OpenAI-Beta: assistants=v1") + @POST("/v1/threads/{thread_id}/runs/{run_id}/submit_tool_outputs") + Single submitToolOutputs(@Path("thread_id") String threadId, @Path("run_id") String runId, @Body SubmitToolOutputsRequest submitToolOutputsRequest); + + + @Headers("OpenAI-Beta: assistants=v1") + @POST("/v1/threads/{thread_id}/runs/{run_id}/cancel") + Single cancelRun(@Path("thread_id") String threadId, @Path("run_id") String runId); + + @Headers("OpenAI-Beta: assistants=v1") + @POST("/v1/threads/runs") + Single createThreadAndRun(@Body CreateThreadAndRunRequest createThreadAndRunRequest); + + @Headers("OpenAI-Beta: assistants=v1") + @GET("/v1/threads/{thread_id}/runs/{run_id}/steps/{step_id}") + Single retrieveRunStep(@Path("thread_id") String threadId, @Path("run_id") String runId, @Path("step_id") String stepId); + + @Headers("OpenAI-Beta: assistants=v1") + @GET("/v1/threads/{thread_id}/runs/{run_id}/steps") + Single> listRunSteps(@Path("thread_id") String threadId, @Path("run_id") String runId, @Body ListSearchParameters listSearchParameters); } diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index d59f7e3c..09680304 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -38,8 +38,11 @@ import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; +import com.theokanning.openai.runs.CreateThreadAndRunRequest; import com.theokanning.openai.runs.Run; import com.theokanning.openai.runs.RunCreateRequest; +import com.theokanning.openai.runs.RunStep; +import com.theokanning.openai.runs.SubmitToolOutputsRequest; import com.theokanning.openai.threads.Thread; import com.theokanning.openai.threads.ThreadRequest; import io.reactivex.BackpressureStrategy; @@ -463,6 +466,34 @@ public Run retrieveRun(String threadId, String runId) { return execute(api.retrieveRun(threadId, runId)); } + public Run modifyRun(String threadId, String runId, Map metadata) { + return execute(api.modifyRun(threadId, runId, metadata)); + } + + public OpenAiResponse listRuns(String threadId, ListSearchParameters listSearchParameters) { + return execute(api.listRuns(threadId, listSearchParameters)); + } + + public Run submitToolOutputs(String threadId, String runId, SubmitToolOutputsRequest submitToolOutputsRequest) { + return execute(api.submitToolOutputs(threadId, runId, submitToolOutputsRequest)); + } + + public Run cancelRun(String threadId, String runId) { + return execute(api.cancelRun(threadId, runId)); + } + + public Run createThreadAndRun(CreateThreadAndRunRequest createThreadAndRunRequest) { + return execute(api.createThreadAndRun(createThreadAndRunRequest)); + } + + public RunStep retrieveRunStep(String threadId, String runId, String stepId) { + return execute(api.retrieveRunStep(threadId, runId, stepId)); + } + + public OpenAiResponse listRunSteps(String threadId, String runId, ListSearchParameters listSearchParameters) { + return execute(api.listRunSteps(threadId, runId, listSearchParameters)); + } + /** * Calls the Open AI api, returns the response, and parses error messages if the request fails */ From 650d76b87a3246a52247b231f8d47f994ae63020 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Thu, 16 Nov 2023 17:45:09 -0600 Subject: [PATCH 27/30] Update to version 0.18.1 (#410) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e12dac0d..4cdb8e00 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.theokanning.openai-gpt3-java -VERSION_NAME=0.18.0 +VERSION_NAME=0.18.1 POM_URL=https://github.com/theokanning/openai-java POM_SCM_URL=https://github.com/theokanning/openai-java From decc9fd93739aaf325142ec4a124e4b806557d0a Mon Sep 17 00:00:00 2001 From: vacuityv Date: Tue, 21 Nov 2023 08:15:05 +0800 Subject: [PATCH 28/30] feat(assistant-function): change the function define and a test for assistant funxtion (#415) --- .../openai/assistants/AssistantFunction.java | 28 ++++ .../theokanning/openai/assistants/Tool.java | 3 +- .../openai/runs/SubmitToolOutputsRequest.java | 2 +- .../openai/service/AssistantFunctionTest.java | 148 ++++++++++++++++++ 4 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 api/src/main/java/com/theokanning/openai/assistants/AssistantFunction.java create mode 100644 service/src/test/java/com/theokanning/openai/service/AssistantFunctionTest.java diff --git a/api/src/main/java/com/theokanning/openai/assistants/AssistantFunction.java b/api/src/main/java/com/theokanning/openai/assistants/AssistantFunction.java new file mode 100644 index 00000000..3abee5ae --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/assistants/AssistantFunction.java @@ -0,0 +1,28 @@ +package com.theokanning.openai.assistants; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * @description: + * @author: vacuity + * @create: 2023-11-20 10:09 + **/ + + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AssistantFunction { + + private String description; + + private String name; + + private Map parameters; +} diff --git a/api/src/main/java/com/theokanning/openai/assistants/Tool.java b/api/src/main/java/com/theokanning/openai/assistants/Tool.java index e759db80..f35af0ef 100644 --- a/api/src/main/java/com/theokanning/openai/assistants/Tool.java +++ b/api/src/main/java/com/theokanning/openai/assistants/Tool.java @@ -1,6 +1,5 @@ package com.theokanning.openai.assistants; -import com.theokanning.openai.completion.chat.ChatFunction; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -17,5 +16,5 @@ public class Tool { /** * Function definition, only used if type is "function" */ - ChatFunction function; + AssistantFunction function; } diff --git a/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java index 3086610c..f892f168 100644 --- a/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java +++ b/api/src/main/java/com/theokanning/openai/runs/SubmitToolOutputsRequest.java @@ -22,5 +22,5 @@ public class SubmitToolOutputsRequest { @JsonProperty("tool_outputs") - private List tool_outputs; + private List toolOutputs; } diff --git a/service/src/test/java/com/theokanning/openai/service/AssistantFunctionTest.java b/service/src/test/java/com/theokanning/openai/service/AssistantFunctionTest.java new file mode 100644 index 00000000..9ad819a7 --- /dev/null +++ b/service/src/test/java/com/theokanning/openai/service/AssistantFunctionTest.java @@ -0,0 +1,148 @@ +package com.theokanning.openai.service; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.theokanning.openai.ListSearchParameters; +import com.theokanning.openai.OpenAiResponse; +import com.theokanning.openai.assistants.Assistant; +import com.theokanning.openai.assistants.AssistantFunction; +import com.theokanning.openai.assistants.AssistantRequest; +import com.theokanning.openai.assistants.AssistantToolsEnum; +import com.theokanning.openai.assistants.Tool; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatFunction; +import com.theokanning.openai.completion.chat.ChatFunctionCall; +import com.theokanning.openai.messages.Message; +import com.theokanning.openai.messages.MessageRequest; +import com.theokanning.openai.runs.RequiredAction; +import com.theokanning.openai.runs.Run; +import com.theokanning.openai.runs.RunCreateRequest; +import com.theokanning.openai.runs.RunStep; +import com.theokanning.openai.runs.SubmitToolOutputRequestItem; +import com.theokanning.openai.runs.SubmitToolOutputs; +import com.theokanning.openai.runs.SubmitToolOutputsRequest; +import com.theokanning.openai.runs.ToolCall; +import com.theokanning.openai.threads.Thread; +import com.theokanning.openai.threads.ThreadRequest; +import com.theokanning.openai.utils.TikTokensUtil; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class AssistantFunctionTest { + String token = System.getenv("OPENAI_TOKEN"); + OpenAiService service = new OpenAiService(token, Duration.ofMinutes(1)); + + @Test + void createRetrieveRun() throws JsonProcessingException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + mapper.addMixIn(ChatFunction.class, ChatFunctionMixIn.class); + mapper.addMixIn(ChatCompletionRequest.class, ChatCompletionRequestMixIn.class); + mapper.addMixIn(ChatFunctionCall.class, ChatFunctionCallMixIn.class); + + String funcDef = "{\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"location\": {\n" + + " \"type\": \"string\",\n" + + " \"description\": \"The city and state, e.g. San Francisco, CA\"\n" + + " },\n" + + " \"unit\": {\n" + + " \"type\": \"string\",\n" + + " \"enum\": [\"celsius\", \"fahrenheit\"]\n" + + " }\n" + + " },\n" + + " \"required\": [\"location\"]\n" + + "}"; + Map funcParameters = mapper.readValue(funcDef, new TypeReference>() {}); + AssistantFunction function = AssistantFunction.builder() + .name("weather_reporter") + .description("Get the current weather of a location") + .parameters(funcParameters) + .build(); + + List toolList = new ArrayList<>(); + Tool funcTool = new Tool(AssistantToolsEnum.FUNCTION, function); + toolList.add(funcTool); + + + AssistantRequest assistantRequest = AssistantRequest.builder() + .model(TikTokensUtil.ModelEnum.GPT_4_1106_preview.getName()) + .name("MATH_TUTOR") + .instructions("You are a personal Math Tutor.") + .tools(toolList) + .build(); + Assistant assistant = service.createAssistant(assistantRequest); + + ThreadRequest threadRequest = ThreadRequest.builder() + .build(); + Thread thread = service.createThread(threadRequest); + + MessageRequest messageRequest = MessageRequest.builder() + .content("What's the weather of Xiamen?") + .build(); + + Message message = service.createMessage(thread.getId(), messageRequest); + + RunCreateRequest runCreateRequest = RunCreateRequest.builder() + .assistantId(assistant.getId()) + .build(); + + Run run = service.createRun(thread.getId(), runCreateRequest); + assertNotNull(run); + + Run retrievedRun = service.retrieveRun(thread.getId(), run.getId()); + while (!(retrievedRun.getStatus().equals("completed")) + && !(retrievedRun.getStatus().equals("failed")) + && !(retrievedRun.getStatus().equals("requires_action"))){ + retrievedRun = service.retrieveRun(thread.getId(), run.getId()); + } + if (retrievedRun.getStatus().equals("requires_action")) { + RequiredAction requiredAction = retrievedRun.getRequiredAction(); + System.out.println("requiredAction"); + System.out.println(mapper.writeValueAsString(requiredAction)); + List toolCalls = requiredAction.getSubmitToolOutputs().getToolCalls(); + ToolCall toolCall = toolCalls.get(0); + String toolCallId = toolCall.getId(); + + SubmitToolOutputRequestItem toolOutputRequestItem = SubmitToolOutputRequestItem.builder() + .toolCallId(toolCallId) + .output("sunny") + .build(); + List toolOutputRequestItems = new ArrayList<>(); + toolOutputRequestItems.add(toolOutputRequestItem); + SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.builder() + .toolOutputs(toolOutputRequestItems) + .build(); + retrievedRun = service.submitToolOutputs(retrievedRun.getThreadId(), retrievedRun.getId(), submitToolOutputsRequest); + + while (!(retrievedRun.getStatus().equals("completed")) + && !(retrievedRun.getStatus().equals("failed")) + && !(retrievedRun.getStatus().equals("requires_action"))){ + retrievedRun = service.retrieveRun(thread.getId(), run.getId()); + } + + OpenAiResponse response = service.listMessages(thread.getId()); + + List messages = response.getData(); + + System.out.println(mapper.writeValueAsString(messages)); + + } + } +} From 2195b2a73d1e6d0cb26e6225c93425483b813a21 Mon Sep 17 00:00:00 2001 From: vacuityv Date: Tue, 21 Nov 2023 08:17:09 +0800 Subject: [PATCH 29/30] Assistant fixed (#414) * fix(assistant-run): run ans run-step * fix(run and run-step): fix the list method --------- Co-authored-by: Theo Kanning --- .../com/theokanning/openai/client/OpenAiApi.java | 5 +++-- .../theokanning/openai/service/OpenAiService.java | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java index 5bf3e732..2a0ea1a6 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -298,7 +298,8 @@ public interface OpenAiApi { @Headers("OpenAI-Beta: assistants=v1") @GET("/v1/threads/{thread_id}/runs") - Single> listRuns(@Path("thread_id") String threadId, @Body ListSearchParameters listSearchParameters); + Single> listRuns(@Path("thread_id") String threadId, @QueryMap Map listSearchParameters); + @Headers("OpenAI-Beta: assistants=v1") @POST("/v1/threads/{thread_id}/runs/{run_id}/submit_tool_outputs") @@ -319,5 +320,5 @@ public interface OpenAiApi { @Headers("OpenAI-Beta: assistants=v1") @GET("/v1/threads/{thread_id}/runs/{run_id}/steps") - Single> listRunSteps(@Path("thread_id") String threadId, @Path("run_id") String runId, @Body ListSearchParameters listSearchParameters); + Single> listRunSteps(@Path("thread_id") String threadId, @Path("run_id") String runId, @QueryMap Map listSearchParameters); } diff --git a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java index 09680304..ee63c419 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -59,6 +59,7 @@ import java.io.IOException; import java.time.Duration; import java.time.LocalDate; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -471,7 +472,12 @@ public Run modifyRun(String threadId, String runId, Map metadata } public OpenAiResponse listRuns(String threadId, ListSearchParameters listSearchParameters) { - return execute(api.listRuns(threadId, listSearchParameters)); + Map search = new HashMap<>(); + if (listSearchParameters != null) { + ObjectMapper mapper = defaultObjectMapper(); + search = mapper.convertValue(listSearchParameters, Map.class); + } + return execute(api.listRuns(threadId, search)); } public Run submitToolOutputs(String threadId, String runId, SubmitToolOutputsRequest submitToolOutputsRequest) { @@ -491,7 +497,12 @@ public RunStep retrieveRunStep(String threadId, String runId, String stepId) { } public OpenAiResponse listRunSteps(String threadId, String runId, ListSearchParameters listSearchParameters) { - return execute(api.listRunSteps(threadId, runId, listSearchParameters)); + Map search = new HashMap<>(); + if (listSearchParameters != null) { + ObjectMapper mapper = defaultObjectMapper(); + search = mapper.convertValue(listSearchParameters, Map.class); + } + return execute(api.listRunSteps(threadId, runId, search)); } /** From 3d60d6eeaeeece9f3b90ecf4cd227ea095315783 Mon Sep 17 00:00:00 2001 From: Theo Kanning Date: Mon, 20 Nov 2023 18:29:55 -0600 Subject: [PATCH 30/30] Update to version 0.18.2 (#417) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4cdb8e00..a4f840ab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.theokanning.openai-gpt3-java -VERSION_NAME=0.18.1 +VERSION_NAME=0.18.2 POM_URL=https://github.com/theokanning/openai-java POM_SCM_URL=https://github.com/theokanning/openai-java