diff --git a/README.md b/README.md index 1824b5e5..20a5dcb8 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,13 @@ as well as an example project using the service. - [Embeddings](https://platform.openai.com/docs/api-reference/embeddings) - [Audio](https://platform.openai.com/docs/api-reference/audio) - [Files](https://platform.openai.com/docs/api-reference/files) -- [Fine-tunes](https://platform.openai.com/docs/api-reference/fine-tunes) +- [Fine-tuning](https://platform.openai.com/docs/api-reference/fine-tuning) - [Images](https://platform.openai.com/docs/api-reference/images) - [Moderations](https://platform.openai.com/docs/api-reference/moderations) #### Deprecated by OpenAI - [Engines](https://platform.openai.com/docs/api-reference/engines) +- [Legacy Fine-Tunes](https://platform.openai.com/docs/guides/legacy-fine-tuning) ## Importing 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 new file mode 100644 index 00000000..097bf7ce --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningEvent.java @@ -0,0 +1,38 @@ +package com.theokanning.openai.fine_tuning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * An object representing an event in the lifecycle of a fine-tuning job + * + * https://platform.openai.com/docs/api-reference/fine-tuning/list-events + */ +@Data +public class FineTuningEvent { + /** + * The type of object returned, should be "fine-tuneing.job.event". + */ + String object; + + /** + * The creation time in epoch seconds. + */ + @JsonProperty("created_at") + Long createdAt; + + /** + * The log level of this message. + */ + String level; + + /** + * The event message. + */ + String message; + + /** + * The type of event, i.e. "message" + */ + String type; +} diff --git a/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningJob.java b/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningJob.java new file mode 100644 index 00000000..685d751e --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningJob.java @@ -0,0 +1,90 @@ +package com.theokanning.openai.fine_tuning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * Fine-tuning job + * https://platform.openai.com/docs/api-reference/fine-tuning/object + */ +@Data +public class FineTuningJob { + /** + * The object identifier, which can be referenced in the API endpoints. + */ + String id; + + /** + * The object type, which is always "fine_tuning.job". + */ + String object; + + /** + * The unix timestamp for when the fine-tuning job was created. + */ + @JsonProperty("created_at") + Long createdAt; + + /** + * The unix timestamp for when the fine-tuning job was finished. + */ + @JsonProperty("finished_at") + Long finishedAt; + + /** + * The base model that is being fine-tuned. + */ + String model; + + /** + * The name of the fine-tuned model that is being created. + * Can be null if no fine-tuned model is created yet. + */ + @JsonProperty("fine_tuned_model") + String fineTunedModel; + + /** + * The organization that owns the fine-tuning job. + */ + @JsonProperty("organization_id") + String organizationId; + + /** + * The current status of the fine-tuning job. + * Can be either created, pending, running, succeeded, failed, or cancelled. + */ + String status; + + /** + * The hyperparameters used for the fine-tuning job. + * See the fine-tuning guide for more details. + */ + Hyperparameters hyperparameters; + + /** + * The file ID used for training. + */ + @JsonProperty("training_file") + String trainingFile; + + /** + * The file ID used for validation. + * Can be null if validation is not used. + */ + @JsonProperty("validation_file") + String validationFile; + + /** + * The compiled results files for the fine-tuning job. + */ + @JsonProperty("result_files") + List resultFiles; + + /** + * The total number of billable tokens processed by this fine-tuning job. + */ + @JsonProperty("trained_tokens") + Integer trainedTokens; +} \ No newline at end of file diff --git a/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningJobRequest.java b/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningJobRequest.java new file mode 100644 index 00000000..44a501c3 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/fine_tuning/FineTuningJobRequest.java @@ -0,0 +1,46 @@ +package com.theokanning.openai.fine_tuning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + + +/** + * Request to create a fine tuning job + * https://platform.openai.com/docs/api-reference/fine-tuning/create + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class FineTuningJobRequest { + + /** + * The ID of an uploaded file that contains training data. + */ + @NonNull + @JsonProperty("training_file") + String trainingFile; + + /** + * The ID of an uploaded file that contains validation data. + * Optional. + */ + @JsonProperty("validation_file") + String validationFile; + + /** + * The name of the model to fine-tune. + */ + @NonNull + String model; + + /** + * The hyperparameters used for the fine-tuning job. + */ + Hyperparameters hyperparameters; + + /** + * A string of up to 40 characters that will be added to your fine-tuned model name. + */ + String suffix; +} diff --git a/api/src/main/java/com/theokanning/openai/fine_tuning/Hyperparameters.java b/api/src/main/java/com/theokanning/openai/fine_tuning/Hyperparameters.java new file mode 100644 index 00000000..3d59913d --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/fine_tuning/Hyperparameters.java @@ -0,0 +1,28 @@ +package com.theokanning.openai.fine_tuning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Hyperparameters for a fine-tuning job + * https://platform.openai.com/docs/api-reference/fine-tuning/object#hyperparameters + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Hyperparameters { + + /** + * The number of epochs to train the model for. + * An epoch refers to one full cycle through the training dataset. + * "Auto" decides the optimal number of epochs based on the size of the dataset. + * If setting the number manually, we support any number between 1 and 50 epochs. + */ + @JsonProperty("n_epochs") + Integer nEpochs; +} diff --git a/api/src/main/java/com/theokanning/openai/finetune/FineTuneEvent.java b/api/src/main/java/com/theokanning/openai/finetune/FineTuneEvent.java index 855d774e..7b8f76c3 100644 --- a/api/src/main/java/com/theokanning/openai/finetune/FineTuneEvent.java +++ b/api/src/main/java/com/theokanning/openai/finetune/FineTuneEvent.java @@ -8,6 +8,7 @@ * * https://beta.openai.com/docs/api-reference/fine-tunes */ +@Deprecated @Data public class FineTuneEvent { /** diff --git a/api/src/main/java/com/theokanning/openai/finetune/FineTuneRequest.java b/api/src/main/java/com/theokanning/openai/finetune/FineTuneRequest.java index fbf89db1..2d145aa0 100644 --- a/api/src/main/java/com/theokanning/openai/finetune/FineTuneRequest.java +++ b/api/src/main/java/com/theokanning/openai/finetune/FineTuneRequest.java @@ -11,6 +11,7 @@ * * https://beta.openai.com/docs/api-reference/fine-tunes/create */ +@Deprecated @Builder @NoArgsConstructor @AllArgsConstructor diff --git a/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java b/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java index 49c0170c..6cbcce41 100644 --- a/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java +++ b/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java @@ -11,6 +11,7 @@ * * https://beta.openai.com/docs/api-reference/fine-tunes */ +@Deprecated @Data public class FineTuneResult { /** diff --git a/api/src/main/java/com/theokanning/openai/finetune/HyperParameters.java b/api/src/main/java/com/theokanning/openai/finetune/HyperParameters.java index e83af27c..d1d383ed 100644 --- a/api/src/main/java/com/theokanning/openai/finetune/HyperParameters.java +++ b/api/src/main/java/com/theokanning/openai/finetune/HyperParameters.java @@ -8,6 +8,7 @@ * * https://beta.openai.com/docs/api-reference/fine-tunes */ +@Deprecated @Data public class HyperParameters { 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 d841e8ad..60ffdbf6 100644 --- a/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/client/OpenAiApi.java @@ -16,6 +16,9 @@ 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.FineTuneRequest; import com.theokanning.openai.finetune.FineTuneResult; @@ -86,21 +89,41 @@ public interface OpenAiApi { @GET("/v1/files/{file_id}") Single retrieveFile(@Path("file_id") String fileId); + @POST("/v1/fine_tuning/jobs") + Single createFineTuningJob(@Body FineTuningJobRequest request); + + @GET("/v1/fine_tuning/jobs") + Single> listFineTuningJobs(); + + @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}") + Single retrieveFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + + @POST("/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel") + Single cancelFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + + @GET("/v1/fine_tuning/jobs/{fine_tuning_job_id}/events") + Single> listFineTuningJobEvents(@Path("fine_tuning_job_id") String fineTuningJobId); + + @Deprecated @POST("/v1/fine-tunes") Single createFineTune(@Body FineTuneRequest request); @POST("/v1/completions") Single createFineTuneCompletion(@Body CompletionRequest request); + @Deprecated @GET("/v1/fine-tunes") Single> listFineTunes(); + @Deprecated @GET("/v1/fine-tunes/{fine_tune_id}") Single retrieveFineTune(@Path("fine_tune_id") String fineTuneId); + @Deprecated @POST("/v1/fine-tunes/{fine_tune_id}/cancel") Single cancelFineTune(@Path("fine_tune_id") String fineTuneId); + @Deprecated @GET("/v1/fine-tunes/{fine_tune_id}/events") Single> listFineTuneEvents(@Path("fine_tune_id") String fineTuneId); 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 63da487c..7114531b 100644 --- a/service/src/main/java/com/theokanning/openai/service/OpenAiService.java +++ b/service/src/main/java/com/theokanning/openai/service/OpenAiService.java @@ -24,6 +24,9 @@ import com.theokanning.openai.embedding.EmbeddingRequest; import com.theokanning.openai.embedding.EmbeddingResult; 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.FineTuneRequest; import com.theokanning.openai.finetune.FineTuneResult; @@ -170,6 +173,27 @@ public File retrieveFile(String fileId) { return execute(api.retrieveFile(fileId)); } + public FineTuningJob createFineTuningJob(FineTuningJobRequest request) { + return execute(api.createFineTuningJob(request)); + } + + public List listFineTuningJobs() { + return execute(api.listFineTuningJobs()).data; + } + + public FineTuningJob retrieveFineTuningJob(String fineTuningJobId) { + return execute(api.retrieveFineTuningJob(fineTuningJobId)); + } + + public FineTuningJob cancelFineTuningJob(String fineTuningJobId) { + return execute(api.cancelFineTuningJob(fineTuningJobId)); + } + + public List listFineTuningJobEvents(String fineTuningJobId) { + return execute(api.listFineTuningJobEvents(fineTuningJobId)).data; + } + + @Deprecated public FineTuneResult createFineTune(FineTuneRequest request) { return execute(api.createFineTune(request)); } @@ -178,18 +202,22 @@ public CompletionResult createFineTuneCompletion(CompletionRequest request) { return execute(api.createFineTuneCompletion(request)); } + @Deprecated public List listFineTunes() { return execute(api.listFineTunes()).data; } + @Deprecated public FineTuneResult retrieveFineTune(String fineTuneId) { return execute(api.retrieveFineTune(fineTuneId)); } + @Deprecated public FineTuneResult cancelFineTune(String fineTuneId) { return execute(api.cancelFineTune(fineTuneId)); } + @Deprecated public List listFineTuneEvents(String fineTuneId) { return execute(api.listFineTuneEvents(fineTuneId)).data; } diff --git a/service/src/test/java/com/theokanning/openai/service/FineTuneTest.java b/service/src/test/java/com/theokanning/openai/service/FineTuneTest.java index 0886b547..e47460d0 100644 --- a/service/src/test/java/com/theokanning/openai/service/FineTuneTest.java +++ b/service/src/test/java/com/theokanning/openai/service/FineTuneTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.*; +@Deprecated @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class FineTuneTest { static com.theokanning.openai.service.OpenAiService service; diff --git a/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java b/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java new file mode 100644 index 00000000..288b1f41 --- /dev/null +++ b/service/src/test/java/com/theokanning/openai/service/FineTuningTest.java @@ -0,0 +1,86 @@ +package com.theokanning.openai.service; + +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.fine_tuning.Hyperparameters; +import com.theokanning.openai.finetune.FineTuneResult; +import org.junit.jupiter.api.*; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class FineTuningTest { + static OpenAiService service; + static String fileId; + static String fineTuningJobId; + + + @BeforeAll + static void setup() throws Exception { + String token = System.getenv("OPENAI_TOKEN"); + service = new OpenAiService(token); + fileId = service.uploadFile("fine-tune", "src/test/resources/chat-fine-tuning-data.jsonl").getId(); + + // wait for file to be processed + TimeUnit.SECONDS.sleep(10); + } + + @AfterAll + static void teardown() { + service.deleteFile(fileId); + } + + @Test + @Order(1) + void createFineTuningJob() { + Hyperparameters hyperparameters = Hyperparameters.builder() + .nEpochs(4) + .build(); + FineTuningJobRequest request = FineTuningJobRequest.builder() + .trainingFile(fileId) + .model("gpt-3.5-turbo") + .hyperparameters(hyperparameters) + .build(); + + FineTuningJob fineTuningJob = service.createFineTuningJob(request); + fineTuningJobId = fineTuningJob.getId(); + + assertEquals("created", fineTuningJob.getStatus()); + } + + @Test + @Order(2) + void listFineTuningJobs() { + List fineTuningJobs = service.listFineTuningJobs(); + + assertTrue(fineTuningJobs.stream().anyMatch(fineTuningJob -> fineTuningJob.getId().equals(fineTuningJobId))); + } + + @Test + @Order(2) + void listFineTuningEvents() { + List events = service.listFineTuningJobEvents(fineTuningJobId); + + assertFalse(events.isEmpty()); + } + + @Test + @Order(2) + void retrieveFineTuningJob() { + FineTuningJob fineTune = service.retrieveFineTuningJob(fineTuningJobId); + + assertTrue(fineTune.getModel().startsWith("gpt-3.5-turbo")); + } + + @Test + @Order(2) + void cancelFineTuningJob() { + FineTuningJob fineTuningJob = service.cancelFineTuningJob(fineTuningJobId); + + assertEquals("cancelled", fineTuningJob.getStatus()); + } +} diff --git a/service/src/test/resources/chat-fine-tuning-data.jsonl b/service/src/test/resources/chat-fine-tuning-data.jsonl new file mode 100644 index 00000000..31604bf2 --- /dev/null +++ b/service/src/test/resources/chat-fine-tuning-data.jsonl @@ -0,0 +1,10 @@ +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]} +{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]} \ No newline at end of file