diff --git a/OpenAI.Playground/OpenAI.Playground.csproj b/OpenAI.Playground/OpenAI.Playground.csproj index 2ac0b3a5..258aaa00 100644 --- a/OpenAI.Playground/OpenAI.Playground.csproj +++ b/OpenAI.Playground/OpenAI.Playground.csproj @@ -56,6 +56,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/OpenAI.Playground/Program.cs b/OpenAI.Playground/Program.cs index bb456ee6..d4079d46 100644 --- a/OpenAI.Playground/Program.cs +++ b/OpenAI.Playground/Program.cs @@ -44,8 +44,8 @@ //await ChatCompletionTestHelper.RunSimpleChatCompletionTest(sdk); //await ChatCompletionTestHelper.RunSimpleCompletionStreamTest(sdk); -await ChatCompletionTestHelper.RunChatFunctionCallTest(sdk); - +//await ChatCompletionTestHelper.RunChatFunctionCallTest(sdk); +await FineTuningJobTestHelper.RunCaseStudyIsTheModelMakingUntrueStatements(sdk); // Whisper //await AudioTestHelper.RunSimpleAudioCreateTranscriptionTest(sdk); //await AudioTestHelper.RunSimpleAudioCreateTranslationTest(sdk); diff --git a/OpenAI.Playground/SampleData/FineTuningJobSample2.jsonl b/OpenAI.Playground/SampleData/FineTuningJobSample2.jsonl new file mode 100644 index 00000000..e915df4e --- /dev/null +++ b/OpenAI.Playground/SampleData/FineTuningJobSample2.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 is the boiling point of water?"},{"role":"assistant","content":"100 degrees Celsius. Groundbreaking information, right?"}]} +{"messages":[{"role":"system","content":"Marv is a factual chatbot that is also sarcastic."},{"role":"user","content":"Who discovered gravity?"},{"role":"assistant","content":"Isaac Newton. You know, the apple guy?"}]} +{"messages":[{"role":"system","content":"Marv is a factual chatbot that is also sarcastic."},{"role":"user","content":"How many continents are there?"},{"role":"assistant","content":"Seven. Did you just skip geography class or what?"}]} +{"messages":[{"role":"system","content":"Marv is a factual chatbot that is also sarcastic."},{"role":"user","content":"What's the largest planet in the solar system?"},{"role":"assistant","content":"Jupiter, as if it wasn't obvious enough."}]} +{"messages":[{"role":"system","content":"Marv is a factual chatbot that is also sarcastic."},{"role":"user","content":"What's the speed of light?"},{"role":"assistant","content":"Approximately 299,792,458 meters per second. But who's counting?"}]} +{"messages":[{"role":"system","content":"Marv is a factual chatbot that is also sarcastic."},{"role":"user","content":"Who is the current U.S. President?"},{"role":"assistant","content":"My data is updated up to September 2021, so I can't answer that. But you've got the internet, don't you?"}]} +{"messages":[{"role":"system","content":"Marv is a factual chatbot that is also sarcastic."},{"role":"user","content":"What's the formula for water?"},{"role":"assistant","content":"H2O. Even a toddler knows that."}]} \ No newline at end of file diff --git a/OpenAI.Playground/TestHelpers/FineTuningJobTestHelper.cs b/OpenAI.Playground/TestHelpers/FineTuningJobTestHelper.cs new file mode 100644 index 00000000..6090de2c --- /dev/null +++ b/OpenAI.Playground/TestHelpers/FineTuningJobTestHelper.cs @@ -0,0 +1,108 @@ +using OpenAI.Interfaces; +using OpenAI.ObjectModels; +using OpenAI.ObjectModels.RequestModels; +using OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; + +namespace OpenAI.Playground.TestHelpers; + +internal static class FineTuningJobTestHelper +{ + public static async Task RunCaseStudyIsTheModelMakingUntrueStatements(IOpenAIService sdk) + { + ConsoleExtensions.WriteLine("Run Case Study Is The Model Making Untrue Statements:", ConsoleColor.Cyan); + + var jobs = await sdk.FineTuningJob.ListFineTuningJobs(); + //print all jobs + foreach (var job in jobs.Data) + { + Console.WriteLine(job.FineTunedModel); + } + + try + { + const string fileName = "FineTuningJobSample2.jsonl"; + var sampleFile = await FileExtensions.ReadAllBytesAsync($"SampleData/{fileName}"); + + ConsoleExtensions.WriteLine($"Uploading file {fileName}", ConsoleColor.DarkCyan); + var uploadFilesResponse = await sdk.Files.FileUpload(UploadFilePurposes.UploadFilePurpose.FineTune, sampleFile, fileName); + if (uploadFilesResponse.Successful) + { + ConsoleExtensions.WriteLine($"{fileName} uploaded", ConsoleColor.DarkGreen); + } + else + { + ConsoleExtensions.WriteLine($"{fileName} failed", ConsoleColor.DarkRed); + } + + ConsoleExtensions.WriteLine($"Wait 5 seconds for the file to be ready", ConsoleColor.DarkYellow); + await Task.Delay(5_000); + + var createFineTuningJobResponse = await sdk.FineTuningJob.CreateFineTuningJob(new FineTuningJobCreateRequest + { + TrainingFile = uploadFilesResponse.Id, + Model = Models.Gpt_3_5_Turbo + }); + + var listFineTuningJobEventsStream = await sdk.FineTuningJob.ListFineTuningJobEvents(new FineTuningJobListEventsRequest + { + FineTuningJobId = createFineTuningJobResponse.Id + }, true); + + using var streamReader = new StreamReader(listFineTuningJobEventsStream); + while (!streamReader.EndOfStream) + { + Console.WriteLine(await streamReader.ReadLineAsync()); + } + + FineTuningJobResponse retrieveFineTuningJobResponse; + do + { + retrieveFineTuningJobResponse = await sdk.FineTuningJob.RetrieveFineTuningJob(createFineTuningJobResponse.Id); + if (retrieveFineTuningJobResponse.Status == "succeeded" || retrieveFineTuningJobResponse.Status == "cancelled" || retrieveFineTuningJobResponse.Status == "failed") + { + ConsoleExtensions.WriteLine($"Fine-tune Status for {createFineTuningJobResponse.Id}: {retrieveFineTuningJobResponse.Status}.", ConsoleColor.Yellow); + break; + } + + ConsoleExtensions.WriteLine($"Fine-tune Status for {createFineTuningJobResponse.Id}: {retrieveFineTuningJobResponse.Status}. Wait 10 more seconds", ConsoleColor.DarkYellow); + await Task.Delay(10_000); + } while (true); + + do + { + var completionResult = await sdk.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest + { + Messages = new List + { + ChatMessage.FromSystem("You are Marv, a chatbot that reluctantly answers questions with sarcastic responses."), + ChatMessage.FromUser("How many pounds are in a kilogram?"), + }, + MaxTokens = 50, + Model = retrieveFineTuningJobResponse.FineTunedModel + }); + + if (completionResult.Successful) + { + Console.WriteLine(completionResult.Choices.FirstOrDefault()); + break; + } + + ConsoleExtensions.WriteLine($"failed{completionResult.Error?.Message}", ConsoleColor.DarkRed); + } while (true); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + public static async Task CleanUpAllFineTunings(IOpenAIService sdk) + { + var FineTuningJobs = await sdk.FineTuningJob.ListFineTuningJobs(); + foreach (var datum in FineTuningJobs.Data) + { + await sdk.Models.DeleteModel(datum.FineTunedModel); + } + } +} \ No newline at end of file diff --git a/OpenAI.SDK/EndpointProviders/AzureOpenAiEndpointProvider.cs b/OpenAI.SDK/EndpointProviders/AzureOpenAiEndpointProvider.cs index 87bb8c73..44ffff64 100644 --- a/OpenAI.SDK/EndpointProviders/AzureOpenAiEndpointProvider.cs +++ b/OpenAI.SDK/EndpointProviders/AzureOpenAiEndpointProvider.cs @@ -1,4 +1,5 @@ using System.Net; +using OpenAI.ObjectModels.RequestModels; namespace OpenAI.EndpointProviders; @@ -95,6 +96,53 @@ public string FineTuneDelete(string fineTuneId) return $"{Prefix}/models/{fineTuneId}{QueryString}"; } + public string FineTuningJobCreate() + { + return $"{Prefix}/fine_tuning/jobs{QueryString}"; + } + + public string FineTuningJobList(FineTuningJobListRequest? fineTuningJobListRequest) + { + var url = $"/{Prefix}/fine_tuning/jobs"; + if (fineTuningJobListRequest != null) + { + var queryParams = new List(); + if (fineTuningJobListRequest.After != null) + queryParams.Add($"after={WebUtility.UrlEncode(fineTuningJobListRequest.After)}"); + if (fineTuningJobListRequest.Limit.HasValue) + queryParams.Add($"limit={fineTuningJobListRequest.Limit.Value}"); + + if (queryParams.Any()) + url = $"{url}{QueryString}&{string.Join("&", queryParams)}"; + } + return url; + } + + public string FineTuningJobList() + { + return $"{Prefix}/fine_tuning/jobs{QueryString}"; + } + + public string FineTuningJobRetrieve(string fineTuningJobId) + { + return $"{Prefix}/fine_tuning/jobs/{fineTuningJobId}{QueryString}"; + } + + public string FineTuningJobCancel(string fineTuningJobId) + { + return $"{Prefix}/fine_tuning/jobs/{fineTuningJobId}/cancel{QueryString}"; + } + + public string FineTuningJobListEvents(string fineTuningJobId) + { + return $"{Prefix}/fine_tuning/jobs/{fineTuningJobId}/events{QueryString}"; + } + + public string ModelsDelete(string modelId) + { + return $"{Prefix}/models/{modelId}{QueryString}"; + } + public string EmbeddingCreate() { return $"{Prefix}/embeddings{QueryString}"; diff --git a/OpenAI.SDK/EndpointProviders/IOpenAiEndpointProvider.cs b/OpenAI.SDK/EndpointProviders/IOpenAiEndpointProvider.cs index 75adcee1..2d85aaef 100644 --- a/OpenAI.SDK/EndpointProviders/IOpenAiEndpointProvider.cs +++ b/OpenAI.SDK/EndpointProviders/IOpenAiEndpointProvider.cs @@ -1,4 +1,6 @@ -namespace OpenAI.EndpointProviders; +using OpenAI.ObjectModels.RequestModels; + +namespace OpenAI.EndpointProviders; internal interface IOpenAiEndpointProvider { @@ -6,6 +8,7 @@ internal interface IOpenAiEndpointProvider string CompletionCreate(); string EditCreate(); string ModelsList(); + string ModelsDelete(string modelId); string FilesList(); string FilesUpload(); string FileDelete(string fileId); @@ -17,6 +20,11 @@ internal interface IOpenAiEndpointProvider string FineTuneCancel(string fineTuneId); string FineTuneListEvents(string fineTuneId); string FineTuneDelete(string fineTuneId); + string FineTuningJobCreate(); + string FineTuningJobList(FineTuningJobListRequest? fineTuningJobListRequest); + string FineTuningJobRetrieve(string fineTuningJobId); + string FineTuningJobCancel(string fineTuningJobId); + string FineTuningJobListEvents(string fineTuningJobId); string EmbeddingCreate(); string ModerationCreate(); string ImageCreate(); diff --git a/OpenAI.SDK/EndpointProviders/OpenAiEndpointProvider.cs b/OpenAI.SDK/EndpointProviders/OpenAiEndpointProvider.cs index 40319dbc..d1dfc682 100644 --- a/OpenAI.SDK/EndpointProviders/OpenAiEndpointProvider.cs +++ b/OpenAI.SDK/EndpointProviders/OpenAiEndpointProvider.cs @@ -1,4 +1,7 @@ -namespace OpenAI.EndpointProviders; +using System.Net; +using OpenAI.ObjectModels.RequestModels; + +namespace OpenAI.EndpointProviders; internal class OpenAiEndpointProvider : IOpenAiEndpointProvider { @@ -99,6 +102,48 @@ public string FineTuneDelete(string fineTuneId) return $"/{_apiVersion}/models/{fineTuneId}"; } + public string FineTuningJobCreate() + { + return $"/{_apiVersion}/fine_tuning/jobs"; + } + + public string FineTuningJobList(FineTuningJobListRequest? fineTuningJobListRequest) + { + var url = $"/{_apiVersion}/fine_tuning/jobs"; + if (fineTuningJobListRequest != null) + { + var queryParams = new List(); + if (fineTuningJobListRequest.After != null) + queryParams.Add($"after={WebUtility.UrlEncode(fineTuningJobListRequest.After)}"); + if (fineTuningJobListRequest.Limit.HasValue) + queryParams.Add($"limit={fineTuningJobListRequest.Limit.Value}"); + + if (queryParams.Any()) + url = $"{url}?{string.Join("&", queryParams)}"; + } + return url; + } + + public string FineTuningJobRetrieve(string fineTuningJobId) + { + return $"/{_apiVersion}/fine_tuning/jobs/{fineTuningJobId}"; + } + + public string FineTuningJobCancel(string fineTuningJobId) + { + return $"/{_apiVersion}/fine_tuning/jobs/{fineTuningJobId}/cancel"; + } + + public string FineTuningJobListEvents(string fineTuningJobId) + { + return $"/{_apiVersion}/fine_tuning/jobs/{fineTuningJobId}/events"; + } + + public string ModelsDelete(string modelId) + { + return $"/{_apiVersion}/models/{modelId}"; + } + public string EmbeddingCreate() { return $"/{_apiVersion}/embeddings"; diff --git a/OpenAI.SDK/Extensions/HttpclientExtensions.cs b/OpenAI.SDK/Extensions/HttpclientExtensions.cs index 054ed940..ecaa5379 100644 --- a/OpenAI.SDK/Extensions/HttpclientExtensions.cs +++ b/OpenAI.SDK/Extensions/HttpclientExtensions.cs @@ -7,7 +7,7 @@ namespace OpenAI.Extensions; public static class HttpClientExtensions { - public static async Task PostAndReadAsAsync(this HttpClient client, string uri, object requestModel, CancellationToken cancellationToken = default) + public static async Task PostAndReadAsAsync(this HttpClient client, string uri, object? requestModel, CancellationToken cancellationToken = default) { var response = await client.PostAsJsonAsync(uri, requestModel, new JsonSerializerOptions { diff --git a/OpenAI.SDK/Interfaces/IFineTuningJobService.cs b/OpenAI.SDK/Interfaces/IFineTuningJobService.cs new file mode 100644 index 00000000..457a0cf2 --- /dev/null +++ b/OpenAI.SDK/Interfaces/IFineTuningJobService.cs @@ -0,0 +1,59 @@ +using OpenAI.ObjectModels.RequestModels; +using OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; + +namespace OpenAI.Interfaces; + +/// +/// Manage fine-tuning jobs to tailor a model to your specific training data. +/// Related guide: Fine-tune models +/// +public interface IFineTuningJobService +{ + /// + /// Creates a job that fine-tunes a specified model from a given dataset. + /// Response includes details of the enqueued job including job status and the name of the fine-tuned models once + /// complete. + /// + /// + /// Propagates notification that operations should be canceled. + /// + Task CreateFineTuningJob(FineTuningJobCreateRequest createFineTuningJobRequest, CancellationToken cancellationToken = default); + + /// + /// List your organization's fine-tuning jobs + /// + /// + /// Propagates notification that operations should be canceled. + /// + Task ListFineTuningJobs(FineTuningJobListRequest? fineTuningJobListRequest =null,CancellationToken cancellationToken = default); + + /// + /// Gets info about the fine-tuning job. + /// + /// The ID of the fine-tuning job + /// Propagates notification that operations should be canceled. + /// + Task RetrieveFineTuningJob(string fineTuningJobId, CancellationToken cancellationToken = default); + + /// + /// Immediately cancel a fine-tuning job. + /// + /// The ID of the fine-tuning job to cancel + /// Propagates notification that operations should be canceled. + /// + Task CancelFineTuningJob(string fineTuningJobId, CancellationToken cancellationToken = default); + + /// + /// Get fine-grained status updates for a fine-tuning job. + /// + /// + /// + /// Whether to stream events for the fine-tuning job. If set to true, events will be sent as data-only server-sent events + /// as they become available. The stream will terminate with a data: [DONE] message when the job is finished + /// (succeeded, cancelled, or failed). + /// If set to false, only events generated so far will be returned. + /// + /// Propagates notification that operations should be canceled. + /// + Task ListFineTuningJobEvents(FineTuningJobListEventsRequest model, bool? stream = null, CancellationToken cancellationToken = default); +} diff --git a/OpenAI.SDK/Interfaces/IModelService.cs b/OpenAI.SDK/Interfaces/IModelService.cs index 6c343160..d2c4c6b3 100644 --- a/OpenAI.SDK/Interfaces/IModelService.cs +++ b/OpenAI.SDK/Interfaces/IModelService.cs @@ -25,4 +25,12 @@ public interface IModelService /// Propagates notification that operations should be canceled. /// Task RetrieveModel(string model, CancellationToken cancellationToken = default); + + /// + /// Delete a fine-tuned model. You must have the Owner role in your organization to delete a model. + /// + /// The model to delete + /// + /// + Task DeleteModel(string modelId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/OpenAI.SDK/Interfaces/IOpenAIService.cs b/OpenAI.SDK/Interfaces/IOpenAIService.cs index dec050e3..671865d4 100644 --- a/OpenAI.SDK/Interfaces/IOpenAIService.cs +++ b/OpenAI.SDK/Interfaces/IOpenAIService.cs @@ -28,6 +28,11 @@ public interface IOpenAIService public IFineTuneService FineTunes { get; } + /// + /// Manage fine-tuning jobs to tailor a model to your specific training data. + /// + public IFineTuningJobService FineTuningJob { get; } + public IModerationService Moderation { get; } /// diff --git a/OpenAI.SDK/Managers/OpenAIFineTuningJob.cs b/OpenAI.SDK/Managers/OpenAIFineTuningJob.cs new file mode 100644 index 00000000..694d05b5 --- /dev/null +++ b/OpenAI.SDK/Managers/OpenAIFineTuningJob.cs @@ -0,0 +1,38 @@ +using System.Net; +using OpenAI.Extensions; +using OpenAI.Interfaces; +using OpenAI.ObjectModels.RequestModels; +using OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; +using System.Net.Http.Json; + +namespace OpenAI.Managers +{ + public partial class OpenAIService : IFineTuningJobService + { + public async Task CreateFineTuningJob(FineTuningJobCreateRequest createFineTuningJobRequest, CancellationToken cancellationToken = default) + { + return await _httpClient.PostAndReadAsAsync(_endpointProvider.FineTuningJobCreate(), createFineTuningJobRequest, cancellationToken); + } + + public async Task ListFineTuningJobs(FineTuningJobListRequest? fineTuningJobListRequest, CancellationToken cancellationToken = default) + { + + return (await _httpClient.GetFromJsonAsync(_endpointProvider.FineTuningJobList(fineTuningJobListRequest), cancellationToken))!; + } + + public async Task RetrieveFineTuningJob(string fineTuningJobId, CancellationToken cancellationToken = default) + { + return (await _httpClient.GetFromJsonAsync(_endpointProvider.FineTuningJobRetrieve(fineTuningJobId), cancellationToken))!; + } + + public async Task CancelFineTuningJob(string fineTuningJobId, CancellationToken cancellationToken = default) + { + return await _httpClient.PostAndReadAsAsync(_endpointProvider.FineTuningJobCancel(fineTuningJobId),null, cancellationToken: cancellationToken); + } + + public async Task ListFineTuningJobEvents(FineTuningJobListEventsRequest fineTuningJobRequestModel, bool? stream = null, CancellationToken cancellationToken = default) + { + return await _httpClient.GetStreamAsync(_endpointProvider.FineTuningJobListEvents(fineTuningJobRequestModel.FineTuningJobId), cancellationToken); + } + } +} diff --git a/OpenAI.SDK/Managers/OpenAIModel.cs b/OpenAI.SDK/Managers/OpenAIModel.cs index 9abbc500..7215de3f 100644 --- a/OpenAI.SDK/Managers/OpenAIModel.cs +++ b/OpenAI.SDK/Managers/OpenAIModel.cs @@ -15,4 +15,9 @@ public async Task RetrieveModel(string model, Cancellatio { return (await _httpClient.GetFromJsonAsync(_endpointProvider.ModelRetrieve(model), cancellationToken))!; } + + public async Task DeleteModel(string modelId, CancellationToken cancellationToken = default) + { + return await (await _httpClient.DeleteAsync(_endpointProvider.ModelsDelete(modelId), cancellationToken)).Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + } } \ No newline at end of file diff --git a/OpenAI.SDK/Managers/OpenAIService.cs b/OpenAI.SDK/Managers/OpenAIService.cs index 3a356529..7ed568ae 100644 --- a/OpenAI.SDK/Managers/OpenAIService.cs +++ b/OpenAI.SDK/Managers/OpenAIService.cs @@ -5,7 +5,6 @@ namespace OpenAI.Managers; -//TODO Find a way to show default request values in documentation public partial class OpenAIService : IOpenAIService, IDisposable { private readonly bool _disposeHttpClient; @@ -85,6 +84,9 @@ public void Dispose() /// public IFineTuneService FineTunes => this; + /// + public IFineTuningJobService FineTuningJob => this; + /// public IModerationService Moderation => this; diff --git a/OpenAI.SDK/ObjectModels/RequestModels/FineTuneCreateRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/FineTuneCreateRequest.cs index c9f0ee89..6e044e1d 100644 --- a/OpenAI.SDK/ObjectModels/RequestModels/FineTuneCreateRequest.cs +++ b/OpenAI.SDK/ObjectModels/RequestModels/FineTuneCreateRequest.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; using OpenAI.ObjectModels.SharedModels; namespace OpenAI.ObjectModels.RequestModels; @@ -37,8 +38,10 @@ public record FineTuneCreateRequest : IOpenAiModels.IModel /// /// The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. + /// Set -1 for "auto value /// [JsonPropertyName("n_epochs")] + [JsonConverter(typeof(NEpochsConverter))] public int? NEpochs { get; set; } /// diff --git a/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobCancelRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobCancelRequest.cs new file mode 100644 index 00000000..75f7c19d --- /dev/null +++ b/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobCancelRequest.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace OpenAI.ObjectModels.RequestModels; + +public record FineTuningJobCancelRequest +{ + [JsonPropertyName("fine_tuning_job_id")] + public string FineTuningJobId { get; set; } +} \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobCreateRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobCreateRequest.cs new file mode 100644 index 00000000..b7fe6d60 --- /dev/null +++ b/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobCreateRequest.cs @@ -0,0 +1,65 @@ +using System.Text.Json.Serialization; +using OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; +using OpenAI.ObjectModels.SharedModels; + +namespace OpenAI.ObjectModels.RequestModels; + +public record FineTuningJobCreateRequest : IOpenAiModels.IModel +{ + /// + /// The ID of an uploaded file that contains training data. + /// See upload file for how to upload a + /// file. + /// Your dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose + /// fine-tune. + /// See the fine-tuning guide for more details. + /// + [JsonPropertyName("training_file")] + public string TrainingFile { get; set; } + + /// + /// The ID of an uploaded file that contains validation data. + /// If you provide this file, the data is used to generate validation metrics periodically during fine-tuning. + /// These metrics can be viewed in the fine-tuning results file. + /// The same data should not be present in both train and validation files. + /// Your dataset must be formatted as a JSONL file. You must upload your file with the purpose fine-tune. + /// See the fine-tuning guide for more details. + /// + [JsonPropertyName("validation_file")] + public string? ValidationFile { get; set; } + + /// + /// The hyperparameters used for the fine-tuning job. + /// + [JsonPropertyName("hyperparameters")] + public HyperparametersRequestModel? Hyperparameters { get; set; } // This can be further detailed if the properties of the hyperparameters are known. + + /// + /// A string of up to 18 characters that will be added to your fine-tuned model name. + /// For example, a suffix of "custom-model-name" would produce a model name like + /// ft:gpt-3.5-turbo:openai:custom-model-name:7p4lURel. + /// + [JsonPropertyName("suffix")] + public string? Suffix { get; set; } = null; + + /// + /// The name of the model to fine-tune. You can select one of the + /// supported models + /// . + /// + [JsonPropertyName("model")] + public string Model { get; set; } +} + +public class HyperparametersRequestModel +{ + /// + /// 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. + /// "Auto" == -1 + /// + [JsonPropertyName("n_epochs")] + [JsonConverter(typeof(NEpochsConverter))] + public int? NEpochs { get; set; } +} \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobListEventsRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobListEventsRequest.cs new file mode 100644 index 00000000..e788c1d8 --- /dev/null +++ b/OpenAI.SDK/ObjectModels/RequestModels/FineTuningJobListEventsRequest.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; + +namespace OpenAI.ObjectModels.RequestModels +{ + public class FineTuningJobListEventsRequest:FineTuningJobListRequest + { + /// + /// The ID of the fine-tuning job to get events for. + /// + [JsonIgnore] + public string FineTuningJobId { get; set; } + } + + public class FineTuningJobListRequest + { + /// + /// Identifier for the last job from the previous pagination request. + /// + [JsonPropertyName("after")] + public string? After { get; set; } + + /// + /// Number of fine-tuning jobs to retrieve. + /// Defaults to 20. + /// + [JsonPropertyName("limit")] + public int? Limit { get; set; } + } +} diff --git a/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobListEventsResponse.cs b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobListEventsResponse.cs new file mode 100644 index 00000000..be4ca12b --- /dev/null +++ b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobListEventsResponse.cs @@ -0,0 +1,14 @@ +using OpenAI.ObjectModels.SharedModels; +using System.Text.Json.Serialization; + +namespace OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; + +public record FineTuningJobListEventsResponse : BaseResponse +{ + [JsonPropertyName("data")] + public List Data { get; set; } + + [JsonPropertyName("has_more")] + public bool HasMore { get; set; } +} + diff --git a/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobListResponse.cs b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobListResponse.cs new file mode 100644 index 00000000..3f13cd78 --- /dev/null +++ b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobListResponse.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; + +public record FineTuningJobListResponse : BaseResponse +{ + [JsonPropertyName("data")] + public List Data { get; set; } + + [JsonPropertyName("has_more")] + public bool HasMore { get; set; } +} + diff --git a/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobResponse.cs b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobResponse.cs new file mode 100644 index 00000000..dd50aaa6 --- /dev/null +++ b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/FineTuningJobResponse.cs @@ -0,0 +1,97 @@ +using System.Text.Json.Serialization; +using OpenAI.ObjectModels.SharedModels; + +namespace OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; + +public record FineTuningJobResponse : BaseResponse, IOpenAiModels.IId, IOpenAiModels.IModel, IOpenAiModels.ICreatedAt +{ + /// + /// The Unix timestamp (in seconds) for when the fine-tuning job was finished. The value will be null if the + /// fine-tuning job is still running. + /// + [JsonPropertyName("finished_at")] + public int? FinishedAt { get; set; } + + /// + /// The name of the fine-tuned model that is being created. The value will be null if the fine-tuning job is still + /// running. + /// + [JsonPropertyName("fine_tuned_model")] + public string? FineTunedModel { get; set; } + + /// + /// The organization that owns the fine-tuning job. + /// + [JsonPropertyName("organization_id")] + public string? OrganizationId { get; set; } + + /// + /// The current status of the fine-tuning job, which can be either created, pending, running, succeeded, failed, or + /// cancelled. + /// + [JsonPropertyName("status")] + public string Status { get; set; } + + /// + /// The hyperparameters used for the fine-tuning job. See the fine-tuning guide for more details. + /// + [JsonPropertyName("hyperparameters")] + public Hyperparameters HyperparametersData { get; set; } + + /// + /// The file ID used for training. You can retrieve the training data with the Files API. + /// + [JsonPropertyName("training_file")] + public string TrainingFile { get; set; } + + /// + /// The file ID used for validation. You can retrieve the validation results with the Files API. + /// + [JsonPropertyName("validation_file")] + public string? ValidationFile { get; set; } + + /// + /// The compiled results file ID(s) for the fine-tuning job. You can retrieve the results with the Files API. + /// + [JsonPropertyName("result_files")] + public List ResultFiles { get; set; } + + /// + /// The total number of billable tokens processed by this fine-tuning job. The value will be null if the fine-tuning + /// job is still running. + /// + [JsonPropertyName("trained_tokens")] + public int? TrainedTokens { get; set; } + + /// + /// The Unix timestamp (in seconds) for when the fine-tuning job was created. + /// + [JsonPropertyName("created_at")] + public int CreatedAt { get; set; } + + /// + /// The object identifier, which can be referenced in the API endpoints. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// The base model that is being fine-tuned. + /// + [JsonPropertyName("model")] + public string Model { get; set; } + + + 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. + /// "Auto" == -1 + /// + [JsonPropertyName("n_epochs")] + [JsonConverter(typeof(NEpochsConverter))] + public int? NEpochs { get; set; } + } +} \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/NEpochsConverter.cs b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/NEpochsConverter.cs new file mode 100644 index 00000000..ae88f22a --- /dev/null +++ b/OpenAI.SDK/ObjectModels/ResponseModels/FineTuningJobResponseModels/NEpochsConverter.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; + +public class NEpochsConverter : JsonConverter +{ + public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + { + var stringValue = reader.GetString(); + if (stringValue?.ToLower() == "auto") + return -1; + break; + } + case JsonTokenType.Number: + return reader.GetInt32(); + case JsonTokenType.Null: + return null; + } + + throw new JsonException($"Unexpected token type: {reader.TokenType}"); + } + + public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options) + { + if (value.HasValue) + { + if (value.Value == -1) + writer.WriteStringValue("auto"); + else + writer.WriteNumberValue(value.Value); + } + else + { + writer.WriteNullValue(); + } + } +} \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/ResponseModels/ModelResponseModels/ModelDeleteResponse.cs b/OpenAI.SDK/ObjectModels/ResponseModels/ModelResponseModels/ModelDeleteResponse.cs new file mode 100644 index 00000000..4bc146bb --- /dev/null +++ b/OpenAI.SDK/ObjectModels/ResponseModels/ModelResponseModels/ModelDeleteResponse.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace OpenAI.ObjectModels.ResponseModels.ModelResponseModels; + +public record ModelDeleteResponse : BaseResponse +{ + [JsonPropertyName("id")] public string Id { get; set; } + + [JsonPropertyName("deleted")] public bool Deleted { get; set; } +} \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/SharedModels/EventResponse.cs b/OpenAI.SDK/ObjectModels/SharedModels/EventResponse.cs index a6e1bbf4..9d74f2e6 100644 --- a/OpenAI.SDK/ObjectModels/SharedModels/EventResponse.cs +++ b/OpenAI.SDK/ObjectModels/SharedModels/EventResponse.cs @@ -4,11 +4,13 @@ namespace OpenAI.ObjectModels.SharedModels; public record EventResponse { - [JsonPropertyName("object")] public string Object { get; set; } + [JsonPropertyName("object")] public string? ObjectTypeName { get; set; } + [JsonPropertyName("id")] public string? Id { get; set; } [JsonPropertyName("created_at")] public int? CreatedAt { get; set; } [JsonPropertyName("level")] public string Level { get; set; } [JsonPropertyName("message")] public string Message { get; set; } + [JsonPropertyName("type")] public string Type { get; set; } } \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/SharedModels/HyperParametersResponse.cs b/OpenAI.SDK/ObjectModels/SharedModels/HyperParametersResponse.cs index ef9f0784..f64ca5a5 100644 --- a/OpenAI.SDK/ObjectModels/SharedModels/HyperParametersResponse.cs +++ b/OpenAI.SDK/ObjectModels/SharedModels/HyperParametersResponse.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using OpenAI.ObjectModels.ResponseModels.FineTuningJobResponseModels; namespace OpenAI.ObjectModels.SharedModels; @@ -9,7 +10,9 @@ public record HyperParametersResponse [JsonPropertyName("learning_rate_multiplier")] public float? LearningRateMultiplier { get; set; } - [JsonPropertyName("n_epochs")] public int? NEpochs { get; set; } + [JsonPropertyName("n_epochs")] + [JsonConverter(typeof(NEpochsConverter))] + public int? NEpochs { get; set; } [JsonPropertyName("prompt_loss_weight")] public float? PromptLossWeight { get; set; } diff --git a/OpenAI.sln.DotSettings b/OpenAI.sln.DotSettings index cfc5f601..1bc445ce 100644 --- a/OpenAI.sln.DotSettings +++ b/OpenAI.sln.DotSettings @@ -5,6 +5,7 @@ True True True + True True True