From f9ed115514bc5e8916cc50c4060f8fbaec006398 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 29 Nov 2023 12:09:10 -0500 Subject: [PATCH] com.openai.unity 6.0.0 (#133) - Updated FilesEndpoint.ListFilesAsync with optional purpose filter query parameter - Refactored list responses with a more generic ListQuery and ListResponse pattern - EventList -> ListResponse - FineTuneJobList -> ListResponse - Standardized names for timestamps to have suffix: UnixTimeSeconds - Standardized response class names (existing classes depreciated) - FileData -> FileResponse - CompletionResult -> CompletonResponse - Event -> EventResponse - FineTuneJob -> FineTuneJobResponse - Added detail parameter to ImageURL - Added GetModerationChunkedAsync method in ModerationsEndpoint - Fixed streaming function tool serialization - Fixed multiple function tool usages - Added additional implicit operators for Chat.Content - Added warnings to default auth and setting constructors to improve init speed - Updated authentication - Updated unit tests - Updated docs --- .../com.openai.unity/Documentation~/README.md | 338 +++++----- .../Editor/FineTuning/FineTuningWindow.cs | 14 +- .../Runtime/Audio/AudioEndpoint.cs | 1 - .../Runtime/Authentication.meta | 8 + .../{ => Authentication}/OpenAIAuthInfo.cs | 0 .../OpenAIAuthInfo.cs.meta | 0 .../OpenAIAuthentication.cs | 80 ++- .../OpenAIAuthentication.cs.meta | 0 .../OpenAIConfiguration.cs | 0 .../OpenAIConfiguration.cs.meta | 0 .../{ => Authentication}/OpenAISettings.cs | 65 +- .../OpenAISettings.cs.meta | 0 .../OpenAISettingsInfo.cs | 0 .../OpenAISettingsInfo.cs.meta | 0 .../com.openai.unity/Runtime/BaseResponse.cs | 24 - .../Runtime/Chat/ChatEndpoint.cs | 4 +- .../com.openai.unity/Runtime/Chat/Choice.cs | 2 +- .../com.openai.unity/Runtime/Chat/Content.cs | 19 +- .../Runtime/Chat/Conversation.cs | 2 +- .../com.openai.unity/Runtime/Chat/Delta.cs | 4 +- .../Runtime/Chat/FinishDetails.cs | 2 +- .../com.openai.unity/Runtime/Chat/Function.cs | 2 +- .../com.openai.unity/Runtime/Chat/Message.cs | 16 +- .../com.openai.unity/Runtime/Chat/Role.cs | 2 +- .../Runtime/Common/BaseResponse.cs | 76 +++ .../Runtime/{ => Common}/BaseResponse.cs.meta | 0 .../Runtime/{Chat => Common}/ContentType.cs | 4 +- .../{Chat => Common}/ContentType.cs.meta | 0 .../Runtime/Common/DeletedResponse.cs | 35 ++ .../DeletedResponse.cs.meta} | 2 +- .../com.openai.unity/Runtime/Common/Event.cs | 5 +- .../Runtime/Common/EventResponse.cs | 42 ++ .../Runtime/Common/EventResponse.cs.meta | 11 + .../Runtime/Common/IListResponse.cs | 15 + .../Runtime/Common/IListResponse.cs.meta | 11 + .../Runtime/Common/ImageDetail.cs | 16 + .../Runtime/Common/ImageDetail.cs.meta | 11 + .../Runtime/{Chat => Common}/ImageUrl.cs | 9 +- .../Runtime/{Chat => Common}/ImageUrl.cs.meta | 0 .../Runtime/Common/ListQuery.cs | 77 +++ .../Runtime/Common/ListQuery.cs.meta | 11 + .../Runtime/Common/ListResponse.cs | 33 + .../Runtime/Common/ListResponse.cs.meta | 11 + .../{ => Common}/OpenAIBaseEndpoint.cs | 0 .../{ => Common}/OpenAIBaseEndpoint.cs.meta | 0 .../Runtime/Common/SortOrder.cs | 12 + .../Runtime/Common/SortOrder.cs.meta | 11 + .../Runtime/Completions/CompletionResponse.cs | 73 +++ .../Completions/CompletionResponse.cs.meta | 11 + .../Runtime/Completions/CompletionResult.cs | 1 + .../Completions/CompletionsEndpoint.cs | 20 +- .../Runtime/Edits/EditsEndpoint.cs | 2 +- .../Runtime/Embeddings/EmbeddingsEndpoint.cs | 2 +- .../Runtime/Extensions/ResponseExtensions.cs | 63 +- .../Runtime/Files/FileData.cs | 1 + .../Runtime/Files/FileResponse.cs | 65 ++ .../Runtime/Files/FileResponse.cs.meta | 11 + .../Runtime/Files/FilesEndpoint.cs | 42 +- .../Runtime/FineTuning/EventList.cs | 2 + .../Runtime/FineTuning/FineTuneJob.cs | 9 +- .../Runtime/FineTuning/FineTuneJobList.cs | 2 + .../Runtime/FineTuning/FineTuneJobResponse.cs | 126 ++++ .../FineTuning/FineTuneJobResponse.cs.meta | 11 + .../Runtime/FineTuning/FineTuningEndpoint.cs | 73 ++- .../FineTuning/FineTuningTrainingDataSet.cs | 4 +- .../Runtime/Images/ImageResult.cs | 2 +- .../Runtime/Images/ImagesEndpoint.cs | 8 +- .../Runtime/Models/ModelsEndpoint.cs | 33 +- .../Moderations/ModerationsEndpoint.cs | 59 ++ .../com.openai.unity/Runtime/OpenAIClient.cs | 7 +- .../Runtime/OpenAIClientSettings.cs | 1 - .../Tests/AbstractTestFixture.cs | 17 + .../Tests/AbstractTestFixture.cs.meta | 11 + .../Tests/TestFixture_00_Authentication.cs | 34 +- .../Tests/TestFixture_01_Models.cs | 15 +- .../Tests/TestFixture_02_Completions.cs | 16 +- .../Tests/TestFixture_03_Chat.cs | 581 ++++++------------ .../Tests/TestFixture_04_Edits.cs | 10 +- .../Tests/TestFixture_05_Images.cs | 118 ++-- .../Tests/TestFixture_06_Embeddings.cs | 20 +- .../Tests/TestFixture_07_Audio.cs | 49 +- .../Tests/TestFixture_08_Files.cs | 60 +- .../Tests/TestFixture_09_FineTuning.cs | 109 ++-- .../Tests/TestFixture_10_Moderations.cs | 25 +- .../Tests/Weather/WeatherArgs.cs | 15 + .../Tests/Weather/WeatherArgs.cs.meta | 11 + .../Tests/Weather/WeatherService.cs | 11 - OpenAI/Packages/com.openai.unity/package.json | 4 +- OpenAI/ProjectSettings/ProjectVersion.txt | 4 +- README.md | 338 +++++----- 90 files changed, 1860 insertions(+), 1191 deletions(-) create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Authentication.meta rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAIAuthInfo.cs (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAIAuthInfo.cs.meta (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAIAuthentication.cs (77%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAIAuthentication.cs.meta (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAIConfiguration.cs (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAIConfiguration.cs.meta (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAISettings.cs (54%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAISettings.cs.meta (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAISettingsInfo.cs (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Authentication}/OpenAISettingsInfo.cs.meta (100%) delete mode 100644 OpenAI/Packages/com.openai.unity/Runtime/BaseResponse.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/BaseResponse.cs rename OpenAI/Packages/com.openai.unity/Runtime/{ => Common}/BaseResponse.cs.meta (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{Chat => Common}/ContentType.cs (92%) rename OpenAI/Packages/com.openai.unity/Runtime/{Chat => Common}/ContentType.cs.meta (100%) create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/DeletedResponse.cs rename OpenAI/Packages/com.openai.unity/Runtime/{OpenAIClientSettings.cs.meta => Common/DeletedResponse.cs.meta} (86%) create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs.meta create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs.meta create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs.meta rename OpenAI/Packages/com.openai.unity/Runtime/{Chat => Common}/ImageUrl.cs (63%) rename OpenAI/Packages/com.openai.unity/Runtime/{Chat => Common}/ImageUrl.cs.meta (100%) create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs.meta create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs.meta rename OpenAI/Packages/com.openai.unity/Runtime/{ => Common}/OpenAIBaseEndpoint.cs (100%) rename OpenAI/Packages/com.openai.unity/Runtime/{ => Common}/OpenAIBaseEndpoint.cs.meta (100%) create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs.meta create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs.meta create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs.meta create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs create mode 100644 OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs.meta delete mode 100644 OpenAI/Packages/com.openai.unity/Runtime/OpenAIClientSettings.cs create mode 100644 OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs create mode 100644 OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs.meta create mode 100644 OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs create mode 100644 OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Documentation~/README.md b/OpenAI/Packages/com.openai.unity/Documentation~/README.md index 37e941b1..6eabe370 100644 --- a/OpenAI/Packages/com.openai.unity/Documentation~/README.md +++ b/OpenAI/Packages/com.openai.unity/Documentation~/README.md @@ -50,7 +50,7 @@ The recommended installation method is though the unity package manager and [Ope ### Table of Contents -- [Authentication](#authentication) +- [Authentication](#authentication) :construction: - [Azure OpenAI](#azure-openai) - [Azure Active Directory Authentication](#azure-active-directory-authentication) - [OpenAI API Proxy](#openai-api-proxy) @@ -58,46 +58,49 @@ The recommended installation method is though the unity package manager and [Ope - [List Models](#list-models) - [Retrieve Models](#retrieve-model) - [Delete Fine Tuned Model](#delete-fine-tuned-model) -- [Completions](#completions) - - [Streaming](#completion-streaming) - [Chat](#chat) - [Chat Completions](#chat-completions) - [Streaming](#chat-streaming) - [Tools](#chat-tools) :new: - [Vision](#chat-vision) :new: -- [Edits](#edits) - - [Create Edit](#create-edit) -- [Embeddings](#embeddings) - - [Create Embedding](#create-embeddings) + - [Json Mode](#chat-json-mode) :new: - [Audio](#audio) - [Create Speech](#create-speech) - [Create Transcription](#create-transcription) - [Create Translation](#create-translation) -- [Images](#images) - - [Create Image](#create-image) - - [Edit Image](#edit-image) - - [Create Image Variation](#create-image-variation) -- [Files](#files) - - [List Files](#list-files) +- [Images](#images) :construction: + - [Create Image](#create-image) :construction: + - [Edit Image](#edit-image) :construction: + - [Create Image Variation](#create-image-variation) :construction: +- [Files](#files) :construction: + - [List Files](#list-files) :construction: - [Upload File](#upload-file) - [Delete File](#delete-file) - - [Retrieve File Info](#retrieve-file-info) + - [Retrieve File](#retrieve-file-info) :construction: - [Download File Content](#download-file-content) -- [Fine Tuning](#fine-tuning) - - [Create Fine Tune Job](#create-fine-tune-job) - - [List Fine Tune Jobs](#list-fine-tune-jobs) - - [Retrieve Fine Tune Job Info](#retrieve-fine-tune-job-info) +- [Fine Tuning](#fine-tuning) :construction: + - [Create Fine Tune Job](#create-fine-tune-job) :construction: + - [List Fine Tune Jobs](#list-fine-tune-jobs) :construction: + - [Retrieve Fine Tune Job Info](#retrieve-fine-tune-job-info) :construction: - [Cancel Fine Tune Job](#cancel-fine-tune-job) - - [List Fine Tune Job Events](#list-fine-tune-job-events) + - [List Fine Tune Job Events](#list-fine-tune-job-events) :construction: +- [Embeddings](#embeddings) + - [Create Embedding](#create-embeddings) +- [Completions](#completions) :construction: + - [Streaming](#completion-streaming) :construction: - [Moderations](#moderations) - [Create Moderation](#create-moderation) +- ~~[Edits](#edits)~~ :warning: Deprecated + - ~~[Create Edit](#create-edit)~~ :warning: Deprecated -### Authentication +### [Authentication](https://platform.openai.com/docs/api-reference/authentication) There are 4 ways to provide your API keys, in order of precedence: -1. [Pass keys directly with constructor](#pass-keys-directly-with-constructor) -2. [Unity Scriptable Object](#unity-scriptable-object) +:warning: We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios. + +1. [Pass keys directly with constructor](#pass-keys-directly-with-constructor) :warning: +2. [Unity Scriptable Object](#unity-scriptable-object) :warning: 3. [Load key from configuration file](#load-key-from-configuration-file) 4. [Use System Environment Variables](#use-system-environment-variables) @@ -105,8 +108,6 @@ You use the `OpenAIAuthentication` when you initialize the API as shown: #### Pass keys directly with constructor -:warning: We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios. - ```csharp var api = new OpenAIClient("sk-apiKey"); ``` @@ -156,13 +157,13 @@ You can also load the configuration file directly with known path by calling sta - Loads the default `.openai` config in the specified directory: ```csharp -var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromDirectory("path/to/your/directory")); +var api = new OpenAIClient(new OpenAIAuthentication().LoadFromDirectory("path/to/your/directory")); ``` - Loads the configuration file from a specific path. File does not need to be named `.openai` as long as it conforms to the json format: ```csharp -var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromPath("path/to/your/file.json")); +var api = new OpenAIClient(new OpenAIAuthentication().LoadFromPath("path/to/your/file.json")); ``` #### Use System Environment Variables @@ -173,10 +174,10 @@ Use your system's environment variables specify an api key and organization to u - Use `OPENAI_ORGANIZATION_ID` to specify an organization. ```csharp -var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); +var api = new OpenAIClient(new OpenAIAuthentication().LoadFromEnvironment()); ``` -### [Azure OpenAI](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/) +### [Azure OpenAI](https://learn.microsoft.com/en-us/azure/cognitive-services/openai) You can also choose to use Microsoft's Azure OpenAI deployments as well. @@ -326,38 +327,8 @@ Delete a fine-tuned model. You must have the Owner role in your organization. ```csharp var api = new OpenAIClient(); -var result = await api.ModelsEndpoint.DeleteFineTuneModelAsync("your-fine-tuned-model"); -Assert.IsTrue(result); -``` - -### [Completions](https://platform.openai.com/docs/api-reference/completions) - -Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. - -The Completions API is accessed via `OpenAIClient.CompletionsEndpoint` - -```csharp -var api = new OpenAIClient(); -var result = await api.CompletionsEndpoint.CreateCompletionAsync("One Two Three One Two", temperature: 0.1, model: Model.Davinci); -Debug.Log(result); -``` - -> To get the `CompletionResult` (which is mostly metadata), use its implicit string operator to get the text if all you want is the completion choice. - -#### Completion Streaming - -Streaming allows you to get results are they are generated, which can help your application feel more responsive, especially on slow models like Davinci. - -```csharp -var api = new OpenAIClient(); - -await api.CompletionsEndpoint.StreamCompletionAsync(result => -{ - foreach (var choice in result.Completions) - { - Debug.Log(choice); - } -}, "My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci); +var isDeleted = await api.ModelsEndpoint.DeleteFineTuneModelAsync("your-fine-tuned-model"); +Assert.IsTrue(isDeleted); ``` ### [Chat](https://platform.openai.com/docs/api-reference/chat) @@ -379,12 +350,13 @@ var messages = new List new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."), new Message(Role.User, "Where was it played?"), }; -var chatRequest = new ChatRequest(messages, Model.GPT3_5_Turbo); -var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content}"); +var chatRequest = new ChatRequest(messages, Model.GPT4); +var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +var choice = response.FirstChoice; +Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}"); ``` -##### [Chat Streaming](https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream) +#### [Chat Streaming](https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream) ```csharp var api = new OpenAIClient(); @@ -395,24 +367,16 @@ var messages = new List new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."), new Message(Role.User, "Where was it played?"), }; -var chatRequest = new ChatRequest(messages, Model.GPT3_5_Turbo, number: 2); -await api.ChatEndpoint.StreamCompletionAsync(chatRequest, result => +var chatRequest = new ChatRequest(messages); +var response = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { - foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Delta?.Content))) - { - // Partial response content - Debug.Log(choice.Delta.Content); - } - - foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Message?.Content))) - { - // Completed response content - Debug.Log($"{choice.Message.Role}: {choice.Message.Content}"); - } + Console.Write(choice.Delta.ToString()); }); +var choice = response.FirstChoice; +Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}"); ``` -##### [Chat Tools](https://platform.openai.com/docs/guides/function-calling) +#### [Chat Tools](https://platform.openai.com/docs/guides/function-calling) > Only available with the latest 0613 model series! @@ -426,7 +390,7 @@ var messages = new List foreach (var message in messages) { - Debug.Log($"{message.Role}: {message.Content}"); + Debug.Log($"{message.Role}: {message}"); } // Define the tools that the assistant is able to use: @@ -456,32 +420,32 @@ var tools = new List }; var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); -var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); -messages.Add(result.FirstChoice.Message); +var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +messages.Add(response.FirstChoice.Message); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); +Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); messages.Add(locationMessage); Debug.Log($"{locationMessage.Role}: {locationMessage.Content}"); chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); -result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); -messages.Add(result.FirstChoice.Message); +messages.Add(response.FirstChoice.Message); -if (!string.IsNullOrEmpty(result.FirstChoice.Message.Content)) +if (!string.IsNullOrEmpty(response.ToString())) { - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var unitMessage = new Message(Role.User, "celsius"); messages.Add(unitMessage); Debug.Log($"{unitMessage.Role}: {unitMessage.Content}"); chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); + response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); } -var usedTool = result.FirstChoice.Message.ToolCalls[0]; -Debug.Log($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); +var usedTool = response.FirstChoice.Message.ToolCalls[0]; +Debug.Log($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); Debug.Log($"{usedTool.Function.Arguments}"); var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); @@ -499,11 +463,10 @@ Debug.Log($"{Role.Tool}: {functionResult}"); // Tool: The current weather in Glasgow, Scotland is 20 celsius ``` -##### [Chat Vision](https://platform.openai.com/docs/guides/vision) +#### [Chat Vision](https://platform.openai.com/docs/guides/vision) -:construction: This feature is in beta! - -> Currently, GPT-4 with vision does not support the message.name parameter, functions/tools, nor the response_format parameter. +> :warning: Beta Feature +> Currently, GPT-4 with vision does not support the `message.name` parameter, functions/tools, nor the `response_format` parameter. ```csharp var api = new OpenAIClient(); @@ -512,13 +475,13 @@ var messages = new List new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), - new Content(ContentType.ImageUrl, "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg") + "What's in this image?", + new ImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", ImageDetail.Low) }) }; var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview"); -var result = await apiChatEndpoint.GetCompletionAsync(chatRequest); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}"); +var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishDetails}"); ``` You can even pass in a `Texture2D`! @@ -530,50 +493,40 @@ var messages = new List new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), - new Content(texture) + "What's in this image?", + texture }) }; var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview"); var result = await apiChatEndpoint.GetCompletionAsync(chatRequest); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}"); +Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice} | Finish Reason: {result.FirstChoice.FinishDetails}"); ``` -### [Edits](https://platform.openai.com/docs/api-reference/edits) - -> Deprecated, and soon to be removed. - -Given a prompt and an instruction, the model will return an edited version of the prompt. +#### [Chat Json Mode](https://platform.openai.com/docs/guides/text-generation/json-mode) -The Edits API is accessed via `OpenAIClient.EditsEndpoint` +> :warning: Beta Feature -#### [Create Edit](https://platform.openai.com/docs/api-reference/edits/create) +Important notes: -Creates a new edit for the provided input, instruction, and parameters using the provided input and instruction. +- When using JSON mode, always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don't include an explicit instruction to generate JSON, the model may generate an unending stream of whitespace and the request may run continually until it reaches the token limit. To help ensure you don't forget, the API will throw an error if the string "JSON" does not appear somewhere in the context. +- The JSON in the message the model returns may be partial (i.e. cut off) if `finish_reason` is length, which indicates the generation exceeded max_tokens or the conversation exceeded the token limit. To guard against this, check `finish_reason` before parsing the response. +- JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors. ```csharp -var api = new OpenAIClient(); -var request = new EditRequest("What day of the wek is it?", "Fix the spelling mistakes"); -var result = await api.EditsEndpoint.CreateEditAsync(request); -Debug.Log(result); -``` - -### [Embeddings](https://platform.openai.com/docs/api-reference/embeddings) - -Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. - -Related guide: [Embeddings](https://platform.openai.com/docs/guides/embeddings) - -The Edits API is accessed via `OpenAIClient.EmbeddingsEndpoint` - -#### [Create Embeddings](https://platform.openai.com/docs/api-reference/embeddings/create) +var messages = new List +{ + new Message(Role.System, "You are a helpful assistant designed to output JSON."), + new Message(Role.User, "Who won the world series in 2020?"), +}; +var chatRequest = new ChatRequest(messages, "gpt-4-1106-preview", responseFormat: ChatResponseFormat.Json); +var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); -Creates an embedding vector representing the input text. +foreach (var choice in response.Choices) +{ + Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); +} -```csharp -var api = new OpenAIClient(); -var result = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...", Models.Embedding_Ada_002); -Debug.Log(result); +response.GetUsage(); ``` ### [Audio](https://platform.openai.com/docs/api-reference/audio) @@ -627,8 +580,8 @@ Creates an image given a prompt. ```csharp var api = new OpenAIClient(); -var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_3); -var results = await api.ImagesEndPoint.GenerateImageAsync(request); +var request = new ImageGenerationRequest("A house riding a velociraptor", Models.Model.DallE_3); +var imageResults = await api.ImagesEndPoint.GenerateImageAsync(request); foreach (var (path, texture) in results) { @@ -646,9 +599,9 @@ Creates an edited or extended image given an original image and a prompt. ```csharp var api = new OpenAIClient(); var request = new ImageEditRequest(Path.GetFullPath(imageAssetPath), Path.GetFullPath(maskAssetPath), "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small); -var results = await api.ImagesEndPoint.CreateImageEditAsync(request); +var imageResults = await api.ImagesEndPoint.CreateImageEditAsync(request); -foreach (var (path, texture) in results) +foreach (var (path, texture) in imageResults) { Debug.Log(path); // path == file://path/to/image.png @@ -664,9 +617,9 @@ Creates a variation of a given image. ```csharp var api = new OpenAIClient(); var request = new ImageVariationRequest(imageTexture, size: ImageSize.Small); -var results = await api.ImagesEndPoint.CreateImageVariationAsync(request); +var imageResults = await api.ImagesEndPoint.CreateImageVariationAsync(request); -foreach (var (path, texture) in results) +foreach (var (path, texture) in imageResults) { Debug.Log(path); // path == file://path/to/image.png @@ -703,17 +656,19 @@ Returns a list of files that belong to the user's organization. ```csharp var api = new OpenAIClient(); -var files = await api.FilesEndpoint.ListFilesAsync(); +var fileList = await api.FilesEndpoint.ListFilesAsync(); -foreach (var file in files) +foreach (var file in fileList) { Debug.Log($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes"); } ``` -#### [Upload File](https://platform.openai.com/docs/api-reference/files/upload) +#### [Upload File](https://platform.openai.com/docs/api-reference/files/create) -Upload a file that contains document(s) to be used across various endpoints/features. Currently, the size of all the files uploaded by one organization can be up to 1 GB. Please contact us if you need to increase the storage limit. +Upload a file that can be used across various endpoints. The size of all the files uploaded by one organization can be up to 100 GB. + +The size of individual files can be a maximum of 512 MB. See the Assistants Tools guide to learn more about the types of files supported. The Fine-tuning API only supports .jsonl files. ```csharp var api = new OpenAIClient(); @@ -727,8 +682,8 @@ Delete a file. ```csharp var api = new OpenAIClient(); -var result = await api.FilesEndpoint.DeleteFileAsync(fileData); -Assert.IsTrue(result); +var isDeleted = await api.FilesEndpoint.DeleteFileAsync(fileId); +Assert.IsTrue(isDeleted); ``` #### [Retrieve File Info](https://platform.openai.com/docs/api-reference/files/retrieve) @@ -737,13 +692,13 @@ Returns information about a specific file. ```csharp var api = new OpenAIClient(); -var fileData = await GetFileInfoAsync(fileId); -Debug.Log($"{fileData.Id} -> {fileData.Object}: {fileData.FileName} | {fileData.Size} bytes"); +var file = await GetFileInfoAsync(fileId); +Debug.Log($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes"); ``` #### [Download File Content](https://platform.openai.com/docs/api-reference/files/retrieve-content) -Downloads the specified file. +Downloads the file content to the specified directory. ```csharp var api = new OpenAIClient(); @@ -780,11 +735,11 @@ List your organization's fine-tuning jobs. ```csharp var api = new OpenAIClient(); -var list = await api.FineTuningEndpoint.ListJobsAsync(); +var jobList = await api.FineTuningEndpoint.ListJobsAsync(); -foreach (var job in list.Jobs) +foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt))) { - Debug.Log($"{job.Id} -> {job.Status}"); + Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}"); } ``` @@ -795,7 +750,7 @@ Gets info about the fine-tune job. ```csharp var api = new OpenAIClient(); var job = await api.FineTuningEndpoint.GetJobInfoAsync(fineTuneJob); -Debug.Log($"{job.Id} -> {job.Status}"); +Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}"); ``` #### [Cancel Fine Tune Job](https://platform.openai.com/docs/api-reference/fine-tuning/cancel) @@ -804,8 +759,8 @@ Immediately cancel a fine-tune job. ```csharp var api = new OpenAIClient(); -var result = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob); -Assert.IsTrue(result); +var isCancelled = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob); +Assert.IsTrue(isCancelled); ``` #### [List Fine Tune Job Events](https://platform.openai.com/docs/api-reference/fine-tuning/list-events) @@ -817,12 +772,60 @@ var api = new OpenAIClient(); var eventList = await api.FineTuningEndpoint.ListJobEventsAsync(fineTuneJob); Debug.Log($"{fineTuneJob.Id} -> status: {fineTuneJob.Status} | event count: {eventList.Events.Count}"); -foreach (var @event in eventList.Events.OrderByDescending(@event => @event.CreatedAt)) +foreach (var @event in eventList.Items.OrderByDescending(@event => @event.CreatedAt)) { - Debug.Log($" {@event.CreatedAt} [{@event.Level}] {@event.Message.Replace("\n", " ")}"); + Debug.Log($" {@event.CreatedAt} [{@event.Level}] {@event.Message}"); } ``` +### [Embeddings](https://platform.openai.com/docs/api-reference/embeddings) + +Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + +Related guide: [Embeddings](https://platform.openai.com/docs/guides/embeddings) + +The Edits API is accessed via `OpenAIClient.EmbeddingsEndpoint` + +#### [Create Embeddings](https://platform.openai.com/docs/api-reference/embeddings/create) + +Creates an embedding vector representing the input text. + +```csharp +var api = new OpenAIClient(); +var response = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...", Models.Embedding_Ada_002); +Debug.Log(response); +``` + +### [Completions](https://platform.openai.com/docs/api-reference/completions) + +Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + +The Completions API is accessed via `OpenAIClient.CompletionsEndpoint` + +```csharp +var api = new OpenAIClient(); +var response = await api.CompletionsEndpoint.CreateCompletionAsync("One Two Three One Two", temperature: 0.1, model: Model.Davinci); +Debug.Log(response); +``` + +> To get the `CompletionResponse` (which is mostly metadata), use its implicit string operator to get the text if all you want is the completion choice. + +#### Completion Streaming + +Streaming allows you to get results are they are generated, which can help your application feel more responsive, especially on slow models like Davinci. + +```csharp +var api = new OpenAIClient(); + +await api.CompletionsEndpoint.StreamCompletionAsync(response => +{ + foreach (var choice in response.Completions) + { + Debug.Log(choice); + } +}, "My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci); +``` + ### [Moderations](https://platform.openai.com/docs/api-reference/moderations) Given a input text, outputs if the model classifies it as violating OpenAI's content policy. @@ -837,6 +840,35 @@ Classifies if text violates OpenAI's Content Policy. ```csharp var api = new OpenAIClient(); -var response = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them."); -Assert.IsTrue(response); +var isViolation = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them."); +Assert.IsTrue(isViolation); +``` + +Additionally you can also get the scores of a given input. + +```csharp +var response = await OpenAIClient.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest("I love you")); +Assert.IsNotNull(response); +Debug.Log(response.Results?[0]?.Scores?.ToString()); +``` + +--- + +### [Edits](https://platform.openai.com/docs/api-reference/edits) + +> Deprecated, and soon to be removed. + +Given a prompt and an instruction, the model will return an edited version of the prompt. + +The Edits API is accessed via `OpenAIClient.EditsEndpoint` + +#### [Create Edit](https://platform.openai.com/docs/api-reference/edits/create) + +Creates a new edit for the provided input, instruction, and parameters using the provided input and instruction. + +```csharp +var api = new OpenAIClient(); +var request = new EditRequest("What day of the wek is it?", "Fix the spelling mistakes"); +var response = await api.EditsEndpoint.CreateEditAsync(request); +Debug.Log(response); ``` diff --git a/OpenAI/Packages/com.openai.unity/Editor/FineTuning/FineTuningWindow.cs b/OpenAI/Packages/com.openai.unity/Editor/FineTuning/FineTuningWindow.cs index 7c08b346..77d8c7cc 100644 --- a/OpenAI/Packages/com.openai.unity/Editor/FineTuning/FineTuningWindow.cs +++ b/OpenAI/Packages/com.openai.unity/Editor/FineTuning/FineTuningWindow.cs @@ -33,7 +33,7 @@ public class FineTuningWindow : EditorWindow private static readonly List organizationModels = new List(); - private static readonly ConcurrentDictionary fineTuneJobs = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary fineTuneJobs = new ConcurrentDictionary(); private static OpenAIClient openAI; @@ -298,7 +298,7 @@ private static void RenderTrainingDataSets() (int)JobStatus.Cancelled or (int)JobStatus.Succeeded) { - FineTuneJob fineTuneJob = null; + FineTuneJobResponse fineTuneJob = null; if (jobStatus.intValue == (int)JobStatus.Succeeded) { @@ -506,7 +506,7 @@ private static void RenderTrainingJobQueue() if (fineTuneJobList is { HasMore: true } && GUILayout.Button("Next Page", defaultColumnWidthOption)) { - EditorApplication.delayCall += () => FetchTrainingJobs(fineTuneJobList.Jobs.LastOrDefault()); + EditorApplication.delayCall += () => FetchTrainingJobs(fineTuneJobList.Items.LastOrDefault()); } if (GUILayout.Button(refreshContent, defaultColumnWidthOption)) @@ -588,7 +588,7 @@ private static void RenderTrainingJobQueue() EditorGUI.indentLevel--; } - private static FineTuneJobList fineTuneJobList; + private static ListResponse fineTuneJobList; private static int trainingJobCount = 25; private static readonly Stack trainingJobIds = new Stack(); @@ -622,12 +622,12 @@ private static async void FetchTrainingJobs(string trainingJobId = null) } fineTuneJobList = null; - var list = await openAI.FineTuningEndpoint.ListJobsAsync(limit: trainingJobCount, after: trainingJobId); + var list = await openAI.FineTuningEndpoint.ListJobsAsync(new ListQuery(limit: trainingJobCount, after: trainingJobId)); fineTuneJobs.Clear(); - await Task.WhenAll(list.Jobs.Select(SyncJobDataAsync)); + await Task.WhenAll(list.Items.Select(SyncJobDataAsync)); fineTuneJobList = list; - static async Task SyncJobDataAsync(FineTuneJob job) + static async Task SyncJobDataAsync(FineTuneJobResponse job) { var jobDetails = await openAI.FineTuningEndpoint.GetJobInfoAsync(job); fineTuneJobs.TryAdd(jobDetails.Id, jobDetails); diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Audio/AudioEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Audio/AudioEndpoint.cs index 38d75956..92ba8f08 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Audio/AudioEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Audio/AudioEndpoint.cs @@ -1,7 +1,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Newtonsoft.Json; -using OpenAI.Extensions; using System; using System.IO; using System.Threading; diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Authentication.meta b/OpenAI/Packages/com.openai.unity/Runtime/Authentication.meta new file mode 100644 index 00000000..ba31b5ac --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Authentication.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 79105af56b952bb4d984ef98d5aa4ef8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthInfo.cs b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthInfo.cs similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthInfo.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthInfo.cs diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthInfo.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthInfo.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthInfo.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthInfo.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthentication.cs b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthentication.cs similarity index 77% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthentication.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthentication.cs index 9125d785..cb05af3c 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthentication.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthentication.cs @@ -11,7 +11,7 @@ namespace OpenAI /// /// Represents authentication for OpenAI /// - public sealed class OpenAIAuthentication : AbstractAuthentication + public sealed class OpenAIAuthentication : AbstractAuthentication { internal const string CONFIG_FILE = ".openai"; private const string OPENAI_KEY = nameof(OPENAI_KEY); @@ -29,24 +29,19 @@ public sealed class OpenAIAuthentication : AbstractAuthentication new OpenAIAuthentication(key); /// - /// Instantiates a new Authentication object that will load the default config. + /// Instantiates an empty Authentication object. /// - public OpenAIAuthentication() - { - if (cachedDefault != null) { return; } - - cachedDefault = (LoadFromAsset() ?? - LoadFromDirectory()) ?? - LoadFromDirectory(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) ?? - LoadFromEnvironment(); - Info = cachedDefault?.Info; - } + public OpenAIAuthentication() { } /// /// Instantiates a new Authentication object with the given , which may be . /// /// The API key, required to access the API endpoint. - public OpenAIAuthentication(string apiKey) => Info = new OpenAIAuthInfo(apiKey); + public OpenAIAuthentication(string apiKey) + { + Info = new OpenAIAuthInfo(apiKey); + cachedDefault = this; + } /// /// Instantiates a new Authentication object with the given , which may be . @@ -56,13 +51,27 @@ public OpenAIAuthentication() /// For users who belong to multiple organizations, you can pass a header to specify which organization is used for an API request. /// Usage from these API requests will count against the specified organization's subscription quota. /// - public OpenAIAuthentication(string apiKey, string organization) => Info = new OpenAIAuthInfo(apiKey, organization); + public OpenAIAuthentication(string apiKey, string organization) + { + Info = new OpenAIAuthInfo(apiKey, organization); + cachedDefault = this; + } /// /// Instantiates a new Authentication object with the given , which may be . /// /// - public OpenAIAuthentication(OpenAIAuthInfo authInfo) => Info = authInfo; + public OpenAIAuthentication(OpenAIAuthInfo authInfo) + { + Info = authInfo; + cachedDefault = this; + } + + /// + /// Instantiates a new Authentication object with the given . + /// + /// . + public OpenAIAuthentication(OpenAIConfiguration configuration) : this(configuration.ApiKey, configuration.organizationId) { } /// public override OpenAIAuthInfo Info { get; } @@ -76,20 +85,21 @@ public OpenAIAuthentication() /// public static OpenAIAuthentication Default { - get => cachedDefault ?? new OpenAIAuthentication(); + get => cachedDefault ??= new OpenAIAuthentication().LoadDefault(); internal set => cachedDefault = value; } /// - public override OpenAIAuthentication LoadFromAsset() - => Resources.LoadAll(string.Empty) - .Where(asset => asset != null) - .Where(asset => asset is OpenAIConfiguration config && - !string.IsNullOrWhiteSpace(config.ApiKey)) - .Select(asset => asset is OpenAIConfiguration config - ? new OpenAIAuthentication(config.ApiKey, config.OrganizationId) - : null) - .FirstOrDefault(); + public override OpenAIAuthentication LoadFromAsset(OpenAIConfiguration configuration = null) + { + if (configuration == null) + { + Debug.LogWarning($"You can speed this up by passing a {nameof(OpenAIConfiguration)} to the {nameof(OpenAIAuthentication)}.ctr"); + configuration = Resources.LoadAll(string.Empty).FirstOrDefault(o => o != null); + } + + return configuration != null ? new OpenAIAuthentication(configuration) : null; + } /// public override OpenAIAuthentication LoadFromEnvironment() @@ -130,13 +140,21 @@ public override OpenAIAuthentication LoadFromEnvironment() /// ReSharper disable once OptionalParameterHierarchyMismatch public override OpenAIAuthentication LoadFromDirectory(string directory = null, string filename = CONFIG_FILE, bool searchUp = true) { - directory ??= Environment.CurrentDirectory; + if (string.IsNullOrWhiteSpace(directory)) + { + directory = Environment.CurrentDirectory; + } + + if (string.IsNullOrWhiteSpace(filename)) + { + filename = CONFIG_FILE; + } OpenAIAuthInfo tempAuth = null; var currentDirectory = new DirectoryInfo(directory); - while (tempAuth == null && currentDirectory.Parent != null) + while (tempAuth == null && currentDirectory?.Parent != null) { var filePath = Path.Combine(currentDirectory.FullName, filename); @@ -195,13 +213,7 @@ public override OpenAIAuthentication LoadFromDirectory(string directory = null, } } - if (tempAuth == null || - string.IsNullOrEmpty(tempAuth.ApiKey)) - { - return null; - } - - return new OpenAIAuthentication(tempAuth); + return string.IsNullOrEmpty(tempAuth?.ApiKey) ? null : new OpenAIAuthentication(tempAuth); } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthentication.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthentication.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIAuthentication.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIAuthentication.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIConfiguration.cs b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIConfiguration.cs similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIConfiguration.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIConfiguration.cs diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIConfiguration.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIConfiguration.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIConfiguration.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAIConfiguration.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAISettings.cs b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettings.cs similarity index 54% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAISettings.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettings.cs index aae6eae2..60dacae2 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/OpenAISettings.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettings.cs @@ -9,49 +9,63 @@ namespace OpenAI public sealed class OpenAISettings : ISettings { /// - /// Creates a new instance of for use with OpenAI. + /// Creates a new instance of with default . /// public OpenAISettings() { - if (cachedDefault != null) { return; } + Info = new OpenAISettingsInfo(); + cachedDefault = this; + } - var config = Resources.LoadAll(string.Empty) - .FirstOrDefault(asset => asset != null); + /// + /// Creates a new instance of with provided . + /// + /// . + public OpenAISettings(OpenAIConfiguration configuration) + { + if (configuration == null) + { + Debug.LogWarning($"You can speed this up by passing a {nameof(OpenAIConfiguration)} to the {nameof(OpenAISettings)}.ctr"); + configuration = Resources.LoadAll(string.Empty).FirstOrDefault(asset => asset != null); + } - if (config != null) + if (configuration == null) { - if (config.UseAzureOpenAI) - { - Info = new OpenAISettingsInfo(config.ResourceName, config.DeploymentId, config.ApiVersion, config.UseAzureActiveDirectory); - cachedDefault = new OpenAISettings(Info); - } - else - { - Info = new OpenAISettingsInfo(domain: config.ProxyDomain, apiVersion: config.ApiVersion); - cachedDefault = new OpenAISettings(Info); - } + throw new MissingReferenceException($"Failed to find a valid {nameof(OpenAIConfiguration)}!"); + } + + if (configuration.UseAzureOpenAI) + { + Info = new OpenAISettingsInfo(configuration.ResourceName, configuration.DeploymentId, configuration.ApiVersion, configuration.UseAzureActiveDirectory); + cachedDefault = this; } else { - Info = new OpenAISettingsInfo(); - cachedDefault = new OpenAISettings(Info); + Info = new OpenAISettingsInfo(domain: configuration.ProxyDomain, apiVersion: configuration.ApiVersion); + cachedDefault = this; } } /// - /// Creates a new instance of with the provided . + /// Creates a new instance of with the provided . /// - /// + /// . public OpenAISettings(OpenAISettingsInfo settingsInfo) - => Info = settingsInfo; + { + Info = settingsInfo; + cachedDefault = this; + } /// - /// Creates a new instance of for use with OpenAI. + /// Creates a new instance of . /// /// Base api domain. /// The version of the OpenAI api you want to use. public OpenAISettings(string domain, string apiVersion = OpenAISettingsInfo.DefaultOpenAIApiVersion) - => Info = new OpenAISettingsInfo(domain, apiVersion); + { + Info = new OpenAISettingsInfo(domain, apiVersion); + cachedDefault = this; + } /// /// Creates a new instance of the for use with Azure OpenAI.
@@ -70,13 +84,16 @@ public OpenAISettings(string domain, string apiVersion = OpenAISettingsInfo.Defa /// Optional, set to true if you want to use Azure Active Directory for Authentication. /// public OpenAISettings(string resourceName, string deploymentId, string apiVersion = OpenAISettingsInfo.DefaultAzureApiVersion, bool useActiveDirectoryAuthentication = false) - => Info = new OpenAISettingsInfo(resourceName, deploymentId, apiVersion, useActiveDirectoryAuthentication); + { + Info = new OpenAISettingsInfo(resourceName, deploymentId, apiVersion, useActiveDirectoryAuthentication); + cachedDefault = this; + } private static OpenAISettings cachedDefault; public static OpenAISettings Default { - get => cachedDefault ?? new OpenAISettings(); + get => cachedDefault ??= new OpenAISettings(configuration: null); internal set => cachedDefault = value; } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAISettings.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettings.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAISettings.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettings.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAISettingsInfo.cs b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettingsInfo.cs similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAISettingsInfo.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettingsInfo.cs diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAISettingsInfo.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettingsInfo.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAISettingsInfo.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Authentication/OpenAISettingsInfo.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/BaseResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/BaseResponse.cs deleted file mode 100644 index 93b90be5..00000000 --- a/OpenAI/Packages/com.openai.unity/Runtime/BaseResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; - -namespace OpenAI -{ - public abstract class BaseResponse - { - /// - /// The server-side processing time as reported by the API. This can be useful for debugging where a delay occurs. - /// - public TimeSpan ProcessingTime { get; internal set; } - - /// - /// The organization associated with the API request, as reported by the API. - /// - public string Organization { get; internal set; } - - /// - /// The request id of this API call, as reported in the response headers. This may be useful for troubleshooting or when contacting OpenAI support in reference to a specific request. - /// - public string RequestId { get; internal set; } - } -} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ChatEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/ChatEndpoint.cs index 6d53558a..8e559f4b 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ChatEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/ChatEndpoint.cs @@ -39,7 +39,7 @@ public async Task GetCompletionAsync(ChatRequest chatRequest, Canc var response = await Rest.PostAsync(GetUrl("/completions"), payload, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - return response.DeserializeResponse(response.Body); + return response.Deserialize(response.Body, client); } /// @@ -83,7 +83,7 @@ public async Task StreamCompletionAsync(ChatRequest chatRequest, A }, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response?.Validate(EnableDebug); if (chatResponse == null) { return null; } - chatResponse.SetResponseData(response); + chatResponse.SetResponseData(response, client); resultHandler?.Invoke(chatResponse); return chatResponse; } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Choice.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Choice.cs index d12a1d50..ca74a367 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Choice.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Choice.cs @@ -35,7 +35,7 @@ public Choice() { } public override string ToString() => Message?.Content?.ToString() ?? Delta?.Content ?? string.Empty; [Preserve] - public static implicit operator string(Choice choice) => choice.ToString(); + public static implicit operator string(Choice choice) => choice?.ToString(); [Preserve] internal void CopyFrom(Choice other) diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Content.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Content.cs index a7bf5f14..b3c7530e 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Content.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Content.cs @@ -10,9 +10,6 @@ namespace OpenAI.Chat [Preserve] public sealed class Content { - [Preserve] - public static implicit operator Content(string content) => new Content(content); - [Preserve] public Content(string text) : this(ContentType.Text, text) @@ -25,6 +22,13 @@ public Content(Texture2D texture) { } + [Preserve] + public Content(ImageUrl imageUrl) + { + Type = ContentType.ImageUrl; + ImageUrl = imageUrl; + } + [Preserve] public Content(ContentType type, string input) { @@ -52,5 +56,14 @@ public Content(ContentType type, string input) [Preserve] [JsonProperty("image_url")] public ImageUrl ImageUrl { get; private set; } + + [Preserve] + public static implicit operator Content(string input) => new Content(ContentType.Text, input); + + [Preserve] + public static implicit operator Content(ImageUrl imageUrl) => new Content(imageUrl); + + [Preserve] + public static implicit operator Content(Texture2D texture) => new Content(texture); } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Conversation.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Conversation.cs index 93e72941..67743364 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Conversation.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Conversation.cs @@ -35,6 +35,6 @@ public Conversation([JsonProperty("messages")] List messages) public override string ToString() => JsonConvert.SerializeObject(this, OpenAIClient.JsonSerializationOptions); [Preserve] - public static implicit operator string(Conversation conversation) => conversation.ToString(); + public static implicit operator string(Conversation conversation) => conversation?.ToString(); } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Delta.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Delta.cs index a845adb6..ba31e9a3 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Delta.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Delta.cs @@ -41,7 +41,7 @@ public Delta( } /// - /// The of the author of this message. + /// The of the author of this message. /// [Preserve] [JsonProperty("role")] @@ -79,6 +79,6 @@ public Delta( public override string ToString() => Content ?? string.Empty; - public static implicit operator string(Delta delta) => delta.ToString(); + public static implicit operator string(Delta delta) => delta?.ToString(); } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/FinishDetails.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/FinishDetails.cs index 0855029a..7d6d6edb 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/FinishDetails.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/FinishDetails.cs @@ -19,6 +19,6 @@ public FinishDetails() { } public override string ToString() => Type; [Preserve] - public static implicit operator string(FinishDetails details) => details.ToString(); + public static implicit operator string(FinishDetails details) => details?.ToString(); } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Function.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Function.cs index 389d7c6d..2d63e4d8 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Function.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Function.cs @@ -102,7 +102,7 @@ public JToken Arguments if (arguments == null && !string.IsNullOrWhiteSpace(argumentsString)) { - arguments = JToken.Parse(argumentsString); + arguments = JToken.FromObject(argumentsString); } return arguments; diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Message.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Message.cs index 2a8e4776..55e403c4 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Message.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Message.cs @@ -32,7 +32,7 @@ public Message(Role role, string content, string name, Function function) /// Creates a new message to insert into a chat conversation. /// /// - /// The of the author of this message. + /// The of the author of this message. /// /// /// The contents of the message. @@ -50,7 +50,7 @@ public Message(Role role, IEnumerable content, string name = null) /// Creates a new message to insert into a chat conversation. ///
/// - /// The of the author of this message. + /// The of the author of this message. /// /// /// The contents of the message. @@ -104,7 +104,7 @@ public string Name private Role role; /// - /// The of the author of this message. + /// The of the author of this message. /// [Preserve] [JsonProperty("role")] @@ -170,7 +170,7 @@ public IReadOnlyList ToolCalls public override string ToString() => Content?.ToString() ?? string.Empty; [Preserve] - public static implicit operator string(Message message) => message.ToString(); + public static implicit operator string(Message message) => message?.ToString(); [Preserve] internal void CopyFrom(Delta other) @@ -201,14 +201,12 @@ internal void CopyFrom(Delta other) if (otherToolCall.Index.HasValue) { - if (toolCalls.Count == 0) + if (otherToolCall.Index + 1 > toolCalls.Count) { toolCalls.Insert(otherToolCall.Index.Value, new Tool(otherToolCall)); } - else - { - toolCalls[otherToolCall.Index.Value].CopyFrom(otherToolCall); - } + + toolCalls[otherToolCall.Index.Value].CopyFrom(otherToolCall); } else { diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Role.cs b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Role.cs index 560ca19f..db414147 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/Role.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Chat/Role.cs @@ -2,7 +2,7 @@ using System; -namespace OpenAI.Chat +namespace OpenAI { public enum Role { diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/BaseResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/BaseResponse.cs new file mode 100644 index 00000000..3d74f215 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/BaseResponse.cs @@ -0,0 +1,76 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using System; + +namespace OpenAI +{ + public abstract class BaseResponse + { + /// + /// The this response was generated from. + /// + [JsonIgnore] + public OpenAIClient Client { get; internal set; } + + /// + /// The server-side processing time as reported by the API. This can be useful for debugging where a delay occurs. + /// + [JsonIgnore] + public TimeSpan ProcessingTime { get; internal set; } + + /// + /// The organization associated with the API request, as reported by the API. + /// + [JsonIgnore] + public string Organization { get; internal set; } + + /// + /// The request id of this API call, as reported in the response headers. This may be useful for troubleshooting or when contacting OpenAI support in reference to a specific request. + /// + [JsonIgnore] + public string RequestId { get; internal set; } + + /// + /// The version of the API used to generate this response, as reported in the response headers. + /// + [JsonIgnore] + public string OpenAIVersion { get; internal set; } + + /// + /// The maximum number of requests that are permitted before exhausting the rate limit. + /// + [JsonIgnore] + public int? LimitRequests { get; internal set; } + + /// + /// The maximum number of tokens that are permitted before exhausting the rate limit. + /// + [JsonIgnore] + public int? LimitTokens { get; internal set; } + + /// + /// The remaining number of requests that are permitted before exhausting the rate limit. + /// + [JsonIgnore] + public int? RemainingRequests { get; internal set; } + + /// + /// The remaining number of tokens that are permitted before exhausting the rate limit. + /// + [JsonIgnore] + public int? RemainingTokens { get; internal set; } + + /// + /// The time until the rate limit (based on requests) resets to its initial state. + /// + [JsonIgnore] + public string ResetRequests { get; internal set; } + + /// + /// The time until the rate limit (based on tokens) resets to its initial state. + /// + [JsonIgnore] + public string ResetTokens { get; internal set; } + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/BaseResponse.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/BaseResponse.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/BaseResponse.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Common/BaseResponse.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ContentType.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/ContentType.cs similarity index 92% rename from OpenAI/Packages/com.openai.unity/Runtime/Chat/ContentType.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Common/ContentType.cs index fe008e60..f0e19b73 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ContentType.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ContentType.cs @@ -2,7 +2,7 @@ using System.Runtime.Serialization; -namespace OpenAI.Chat +namespace OpenAI { public enum ContentType { @@ -11,4 +11,4 @@ public enum ContentType [EnumMember(Value = "image_url")] ImageUrl } -} \ No newline at end of file +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ContentType.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/ContentType.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/Chat/ContentType.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Common/ContentType.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/DeletedResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/DeletedResponse.cs new file mode 100644 index 00000000..46feb004 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/DeletedResponse.cs @@ -0,0 +1,35 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using UnityEngine.Scripting; + +namespace OpenAI +{ + [Preserve] + public sealed class DeletedResponse + { + [Preserve] + [JsonConstructor] + public DeletedResponse( + [JsonProperty("id")] string id, + [JsonProperty("object")] string @object, + [JsonProperty("deleted")] bool deleted) + { + Id = id; + Object = @object; + Deleted = deleted; + } + + [Preserve] + [JsonProperty("id")] + public string Id { get; } + + [Preserve] + [JsonProperty("object")] + public string Object { get; } + + [Preserve] + [JsonProperty("deleted")] + public bool Deleted { get; } + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClientSettings.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/DeletedResponse.cs.meta similarity index 86% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIClientSettings.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Common/DeletedResponse.cs.meta index 2c3dbae6..a7c106b2 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClientSettings.cs.meta +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/DeletedResponse.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c4d28850d4a3c334b8dd795af8bce9a9 +guid: c31936e40f865eb4eb54ce9696e85581 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/Event.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/Event.cs index f49539a6..b443b076 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Common/Event.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/Event.cs @@ -7,7 +7,8 @@ namespace OpenAI { [Preserve] - public sealed class Event + [Obsolete("use EventResponse")] + public sealed class Event : BaseResponse { [Preserve] [JsonConstructor] @@ -42,5 +43,7 @@ public Event( [Preserve] [JsonProperty("message")] public string Message { get; } + + public static implicit operator EventResponse(Event @event) => new EventResponse(@event); } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs new file mode 100644 index 00000000..a16d1754 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs @@ -0,0 +1,42 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using System; +using UnityEngine.Scripting; + +namespace OpenAI +{ + public sealed class EventResponse : BaseResponse + { + public EventResponse() { } + +#pragma warning disable CS0618 // Type or member is obsolete + internal EventResponse(Event @event) + { + Object = @event.Object; + CreatedAtUnixTimeSeconds = @event.CreatedAtUnixTime; + Level = @event.Level; + Message = @event.Message; + } +#pragma warning restore CS0618 // Type or member is obsolete + + [Preserve] + [JsonProperty("object")] + public string Object { get; private set; } + + [Preserve] + [JsonProperty("created_at")] + public int CreatedAtUnixTimeSeconds { get; private set; } + + [JsonIgnore] + public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedAtUnixTimeSeconds).DateTime; + + [Preserve] + [JsonProperty("level")] + public string Level { get; private set; } + + [Preserve] + [JsonProperty("message")] + public string Message { get; private set; } + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs.meta new file mode 100644 index 00000000..ba0e902e --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/EventResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbf74106576a2da4fb4efb06a7ea1c26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs new file mode 100644 index 00000000..bf28ca68 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs @@ -0,0 +1,15 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Collections.Generic; +using UnityEngine.Scripting; + +namespace OpenAI +{ + [Preserve] + public interface IListResponse + where TObject : BaseResponse + { + [Preserve] + IReadOnlyList Items { get; } + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs.meta new file mode 100644 index 00000000..8cadff86 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/IListResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9f2cd66b2e8f58419aca54ccc0eb44f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs new file mode 100644 index 00000000..d07031f1 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs @@ -0,0 +1,16 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Runtime.Serialization; + +namespace OpenAI +{ + public enum ImageDetail + { + [EnumMember(Value = "auto")] + Auto, + [EnumMember(Value = "low")] + Low, + [EnumMember(Value = "high")] + High + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs.meta new file mode 100644 index 00000000..7cbec659 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageDetail.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76aea32536afc314eb2b133353b2ea70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ImageUrl.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageUrl.cs similarity index 63% rename from OpenAI/Packages/com.openai.unity/Runtime/Chat/ImageUrl.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Common/ImageUrl.cs index f34b98cf..c8f247fd 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ImageUrl.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageUrl.cs @@ -3,20 +3,25 @@ using Newtonsoft.Json; using UnityEngine.Scripting; -namespace OpenAI.Chat +namespace OpenAI { [Preserve] public sealed class ImageUrl { [Preserve] [JsonConstructor] - public ImageUrl(string url) + public ImageUrl(string url, ImageDetail detail = ImageDetail.Auto) { Url = url; + Detail = detail; } [Preserve] [JsonProperty("url")] public string Url { get; private set; } + + [Preserve] + [JsonProperty("detail")] + public ImageDetail Detail { get; private set; } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Chat/ImageUrl.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/ImageUrl.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/Chat/ImageUrl.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Common/ImageUrl.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs new file mode 100644 index 00000000..54ec5c79 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; + +namespace OpenAI +{ + public sealed class ListQuery + { + /// + /// List Query. + /// + /// + /// A limit on the number of objects to be returned. + /// Limit can range between 1 and 100, and the default is 20. + /// + /// + /// Sort order by the 'created_at' timestamp of the objects. + /// + /// + /// 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. + /// + /// + /// 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. + /// + public ListQuery(int? limit = null, SortOrder order = SortOrder.Descending, string after = null, string before = null) + { + Limit = limit; + Order = order; + After = after; + Before = before; + } + + public int? Limit { get; set; } + + public SortOrder Order { get; set; } + + public string After { get; set; } + + public string Before { get; set; } + + public static implicit operator Dictionary(ListQuery query) + { + if (query == null) { return null; } + var parameters = new Dictionary(); + + if (query.Limit.HasValue) + { + parameters.Add("limit", query.Limit.ToString()); + } + + switch (query.Order) + { + case SortOrder.Descending: + parameters.Add("order", "desc"); + break; + case SortOrder.Ascending: + parameters.Add("order", "asc"); + break; + } + + if (!string.IsNullOrEmpty(query.After)) + { + parameters.Add("after", query.After); + } + + if (!string.IsNullOrEmpty(query.Before)) + { + parameters.Add("before", query.Before); + } + + return parameters; + } + } +} \ No newline at end of file diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs.meta new file mode 100644 index 00000000..bfd8cf2d --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListQuery.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0febfabc787966542a2d763866c4a7b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs new file mode 100644 index 00000000..ed2893da --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs @@ -0,0 +1,33 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using System.Collections.Generic; +using UnityEngine.Scripting; + +namespace OpenAI +{ + [Preserve] + public sealed class ListResponse : BaseResponse, IListResponse + where TObject : BaseResponse + { + [Preserve] + [JsonProperty("object")] + public string Object { get; private set; } + + [Preserve] + [JsonProperty("data")] + public IReadOnlyList Items { get; private set; } + + [Preserve] + [JsonProperty("has_more")] + public bool HasMore { get; private set; } + + [Preserve] + [JsonProperty("first_id")] + public string FirstId { get; private set; } + + [Preserve] + [JsonProperty("last_id")] + public string LastId { get; private set; } + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs.meta new file mode 100644 index 00000000..c42f1424 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/ListResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 447bd1caa72d99c4d8e36918d31c175d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIBaseEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/OpenAIBaseEndpoint.cs similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIBaseEndpoint.cs rename to OpenAI/Packages/com.openai.unity/Runtime/Common/OpenAIBaseEndpoint.cs diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIBaseEndpoint.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/OpenAIBaseEndpoint.cs.meta similarity index 100% rename from OpenAI/Packages/com.openai.unity/Runtime/OpenAIBaseEndpoint.cs.meta rename to OpenAI/Packages/com.openai.unity/Runtime/Common/OpenAIBaseEndpoint.cs.meta diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs b/OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs new file mode 100644 index 00000000..8f92dede --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace OpenAI +{ + public enum SortOrder + { + [EnumMember(Value = "desc")] + Descending, + [EnumMember(Value = "asc")] + Ascending, + } +} \ No newline at end of file diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs.meta new file mode 100644 index 00000000..6b20f72b --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Common/SortOrder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f34437069bcd0ff45b6797db0d3a7f40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs new file mode 100644 index 00000000..88be63ca --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs @@ -0,0 +1,73 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.Scripting; + +namespace OpenAI.Completions +{ + /// + /// Represents a result from calling the . + /// + public sealed class CompletionResponse : BaseResponse + { + public CompletionResponse() + { + } + +#pragma warning disable CS0618 // Type or member is obsolete + internal CompletionResponse(CompletionResult result) + { + Id = result.Id; + Object = result.Object; + CreatedUnixTimeSeconds = result.CreatedUnixTime; + Model = result.Model; + Completions = result.Completions; + } +#pragma warning restore CS0618 // Type or member is obsolete + + /// + /// The identifier of the result, which may be used during troubleshooting + /// + [Preserve] + [JsonProperty("id")] + public string Id { get; private set; } + + [Preserve] + [JsonProperty("object")] + public string Object { get; private set; } + + /// + /// The time when the result was generated in unix epoch format + /// + [Preserve] + [JsonProperty("created")] + public int CreatedUnixTimeSeconds { get; private set; } + + /// + /// The time when the result was generated. + /// + [JsonIgnore] + public DateTime Created => DateTimeOffset.FromUnixTimeSeconds(CreatedUnixTimeSeconds).DateTime; + + [Preserve] + [JsonProperty("model")] + public string Model { get; private set; } + + /// + /// The completions returned by the API. Depending on your request, there may be 1 or many choices. + /// + [Preserve] + [JsonProperty("choices")] + public IReadOnlyList Completions { get; private set; } + + [JsonIgnore] + public Choice FirstChoice => Completions?.FirstOrDefault(choice => choice.Index == 0); + + public override string ToString() => FirstChoice?.ToString() ?? string.Empty; + + public static implicit operator string(CompletionResponse response) => response?.ToString(); + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs.meta new file mode 100644 index 00000000..ec256f39 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f0ce8b7122ea904f9356fd6ab7d7728 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResult.cs b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResult.cs index 957206fc..5cdd36d5 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResult.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionResult.cs @@ -12,6 +12,7 @@ namespace OpenAI.Completions /// Represents a result from calling the . /// [Preserve] + [Obsolete("use CompletionResponse")] public sealed class CompletionResult : BaseResponse { [Preserve] diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionsEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionsEndpoint.cs index bf9f4502..d9fb148b 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionsEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Completions/CompletionsEndpoint.cs @@ -50,7 +50,7 @@ internal CompletionsEndpoint(OpenAIClient client) : base(client) { } /// The scale of the penalty for how often a token is used. /// Should generally be between 0 and 1, although negative numbers are allowed to encourage token reuse. /// Include the log probabilities on the logprobs most likely tokens, which can be found - /// in -> . So for example, if logprobs is 10, + /// in -> . So for example, if logprobs is 10, /// the API will return a list of the 10 most likely tokens. If logprobs is supplied, the API will always return the logprob /// of the sampled token, so there may be up to logprobs+1 elements in the response. /// Echo back the prompt in addition to the completion. @@ -61,9 +61,9 @@ internal CompletionsEndpoint(OpenAIClient client) : base(client) { } /// Optional, . /// /// Asynchronously returns the completion result. - /// Look in its property for the completions. + /// Look in its property for the completions. /// - public async Task CreateCompletionAsync( + public async Task CreateCompletionAsync( string prompt = null, IEnumerable prompts = null, string suffix = null, @@ -105,15 +105,15 @@ public async Task CreateCompletionAsync( /// Optional, . /// /// Asynchronously returns the completion result. - /// Look in its property for the completions. + /// Look in its property for the completions. /// - public async Task CreateCompletionAsync(CompletionRequest completionRequest, CancellationToken cancellationToken = default) + public async Task CreateCompletionAsync(CompletionRequest completionRequest, CancellationToken cancellationToken = default) { completionRequest.Stream = false; var payload = JsonConvert.SerializeObject(completionRequest, OpenAIClient.JsonSerializationOptions); var response = await Rest.PostAsync(GetUrl(), payload, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - return response.DeserializeResponse(response.Body); + return response.Deserialize(response.Body, client); } #endregion Non-Streaming @@ -142,7 +142,7 @@ public async Task CreateCompletionAsync(CompletionRequest comp /// The scale of the penalty for how often a token is used. /// Should generally be between 0 and 1, although negative numbers are allowed to encourage token reuse. /// Include the log probabilities on the logProbabilities most likely tokens, - /// which can be found in -> . + /// which can be found in -> . /// So for example, if logProbabilities is 10, the API will return a list of the 10 most likely tokens. /// If logProbabilities is supplied, the API will always return the logProbabilities of the sampled token, /// so there may be up to logProbabilities+1 elements in the response. @@ -153,7 +153,7 @@ public async Task CreateCompletionAsync(CompletionRequest comp /// Defaults to . /// Optional, . public async Task StreamCompletionAsync( - Action resultHandler, + Action resultHandler, string prompt = null, IEnumerable prompts = null, string suffix = null, @@ -194,13 +194,13 @@ public async Task StreamCompletionAsync( /// The request to send to the API. /// An action to be called as each new result arrives. /// Optional, . - public async Task StreamCompletionAsync(CompletionRequest completionRequest, Action resultHandler, CancellationToken cancellationToken = default) + public async Task StreamCompletionAsync(CompletionRequest completionRequest, Action resultHandler, CancellationToken cancellationToken = default) { completionRequest.Stream = true; var payload = JsonConvert.SerializeObject(completionRequest, OpenAIClient.JsonSerializationOptions); var response = await Rest.PostAsync(GetUrl(), payload, eventData => { - resultHandler(JsonConvert.DeserializeObject(eventData, OpenAIClient.JsonSerializationOptions)); + resultHandler(JsonConvert.DeserializeObject(eventData, OpenAIClient.JsonSerializationOptions)); }, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Edits/EditsEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Edits/EditsEndpoint.cs index f2ea7976..8cc05ec5 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Edits/EditsEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Edits/EditsEndpoint.cs @@ -68,7 +68,7 @@ public async Task CreateEditAsync(EditRequest request, Cancellatio var payload = JsonConvert.SerializeObject(request, OpenAIClient.JsonSerializationOptions); var response = await Rest.PostAsync(GetUrl(), payload, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - return response.DeserializeResponse(response.Body); + return response.Deserialize(response.Body, client); } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Embeddings/EmbeddingsEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Embeddings/EmbeddingsEndpoint.cs index 2d2623c9..abc5d634 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Embeddings/EmbeddingsEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Embeddings/EmbeddingsEndpoint.cs @@ -72,7 +72,7 @@ public async Task CreateEmbeddingAsync(EmbeddingsRequest req var payload = JsonConvert.SerializeObject(request, OpenAIClient.JsonSerializationOptions); var response = await Rest.PostAsync(GetUrl(), payload, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - return response.DeserializeResponse(response.Body); + return response.Deserialize(response.Body, client); } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Extensions/ResponseExtensions.cs b/OpenAI/Packages/com.openai.unity/Runtime/Extensions/ResponseExtensions.cs index 2ba2f52d..583a5cfe 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Extensions/ResponseExtensions.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Extensions/ResponseExtensions.cs @@ -12,6 +12,13 @@ internal static class ResponseExtensions private const string RequestId = "X-Request-ID"; private const string Organization = "Openai-Organization"; private const string ProcessingTime = "Openai-Processing-Ms"; + private const string OpenAIVersion = "openai-version"; + private const string XRateLimitLimitRequests = "x-ratelimit-limit-requests"; + private const string XRateLimitLimitTokens = "x-ratelimit-limit-tokens"; + private const string XRateLimitRemainingRequests = "x-ratelimit-remaining-requests"; + private const string XRateLimitRemainingTokens = "x-ratelimit-remaining-tokens"; + private const string XRateLimitResetRequests = "x-ratelimit-reset-requests"; + private const string XRateLimitResetTokens = "x-ratelimit-reset-tokens"; private static readonly NumberFormatInfo numberFormatInfo = new NumberFormatInfo { @@ -19,8 +26,18 @@ internal static class ResponseExtensions NumberDecimalSeparator = "." }; - internal static void SetResponseData(this BaseResponse response, Response restResponse) + internal static void SetResponseData(this BaseResponse response, Response restResponse, OpenAIClient client) { + if (response is IListResponse listResponse) + { + foreach (var item in listResponse.Items) + { + SetResponseData(item, restResponse, client); + } + } + + response.Client = client; + if (restResponse is not { Headers: not null }) { return; } if (restResponse.Headers.TryGetValue(RequestId, out var requestId)) @@ -38,12 +55,52 @@ internal static void SetResponseData(this BaseResponse response, Response restRe { response.ProcessingTime = TimeSpan.FromMilliseconds(processingTime); } + + if (restResponse.Headers.TryGetValue(OpenAIVersion, out var version)) + { + response.OpenAIVersion = version; + } + + if (restResponse.Headers.TryGetValue(XRateLimitLimitRequests, out var limitRequests) && + int.TryParse(limitRequests, out var limitRequestsValue) + ) + { + response.LimitRequests = limitRequestsValue; + } + + if (restResponse.Headers.TryGetValue(XRateLimitLimitTokens, out var limitTokens) && + int.TryParse(limitTokens, out var limitTokensValue)) + { + response.LimitTokens = limitTokensValue; + } + + if (restResponse.Headers.TryGetValue(XRateLimitRemainingRequests, out var remainingRequests) && + int.TryParse(remainingRequests, out var remainingRequestsValue)) + { + response.RemainingRequests = remainingRequestsValue; + } + + if (restResponse.Headers.TryGetValue(XRateLimitRemainingTokens, out var remainingTokens) && + int.TryParse(remainingTokens, out var remainingTokensValue)) + { + response.RemainingTokens = remainingTokensValue; + } + + if (restResponse.Headers.TryGetValue(XRateLimitResetRequests, out var resetRequests)) + { + response.ResetRequests = resetRequests; + } + + if (restResponse.Headers.TryGetValue(XRateLimitResetTokens, out var resetTokens)) + { + response.ResetTokens = resetTokens; + } } - internal static T DeserializeResponse(this Response response, string json) where T : BaseResponse + internal static T Deserialize(this Response response, string json, OpenAIClient client) where T : BaseResponse { var result = JsonConvert.DeserializeObject(json, OpenAIClient.JsonSerializationOptions); - result.SetResponseData(response); + result.SetResponseData(response, client); return result; } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Files/FileData.cs b/OpenAI/Packages/com.openai.unity/Runtime/Files/FileData.cs index 80366d6f..128b862a 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Files/FileData.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Files/FileData.cs @@ -7,6 +7,7 @@ namespace OpenAI.Files { [Preserve] + [Obsolete("Use FileResponse")] public sealed class FileData { [Preserve] diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs new file mode 100644 index 00000000..b4f1ec8d --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs @@ -0,0 +1,65 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using System; +using UnityEngine.Scripting; + +namespace OpenAI.Files +{ + public sealed class FileResponse + { + [Preserve] + [JsonConstructor] + public FileResponse( + [JsonProperty("id")] string id, + [JsonProperty("object")] string @object, + [JsonProperty("bytes")] int size, + [JsonProperty("created_at")] int createdUnixTimeSeconds, + [JsonProperty("filename")] string fileName, + [JsonProperty("purpose")] string purpose, + [JsonProperty("status")] string status) + { + Id = id; + Object = @object; + Size = size; + CreatedUnixTimeSeconds = createdUnixTimeSeconds; + FileName = fileName; + Purpose = purpose; + Status = status; + } + + [Preserve] + [JsonProperty("id")] + public string Id { get; } + + [Preserve] + [JsonProperty("object")] + public string Object { get; } + + [Preserve] + [JsonProperty("bytes")] + public int Size { get; } + + [Preserve] + [JsonProperty("created_at")] + public int CreatedUnixTimeSeconds { get; } + + [Preserve] + [JsonIgnore] + public DateTime CreatedAt => DateTimeOffset.FromUnixTimeSeconds(CreatedUnixTimeSeconds).DateTime; + + [Preserve] + [JsonProperty("filename")] + public string FileName { get; } + + [Preserve] + [JsonProperty("purpose")] + public string Purpose { get; } + + [Preserve] + [JsonProperty("status")] + public string Status { get; } + + public static implicit operator string(FileResponse fileData) => fileData.Id; + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs.meta new file mode 100644 index 00000000..752fcc39 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/Files/FileResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21de2a1744ebe8d408b9ed7043fb9e4b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Files/FilesEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Files/FilesEndpoint.cs index 1bf3df4d..3fee6621 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Files/FilesEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Files/FilesEndpoint.cs @@ -23,14 +23,14 @@ private class FilesList { [Preserve] [JsonConstructor] - public FilesList([JsonProperty("data")] List data) + public FilesList([JsonProperty("data")] List data) { - Data = data; + Files = data; } [Preserve] [JsonProperty("data")] - public List Data { get; } + public List Files { get; } } /// @@ -42,12 +42,12 @@ public FilesEndpoint(OpenAIClient client) : base(client) { } /// /// Returns a list of files that belong to the user's organization. /// - /// List of . - public async Task> ListFilesAsync(CancellationToken cancellationToken = default) + /// List of . + public async Task> ListFilesAsync(CancellationToken cancellationToken = default) { var response = await Rest.GetAsync(GetUrl(), new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions)?.Data; + return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions)?.Files; } /// @@ -65,8 +65,8 @@ public async Task> ListFilesAsync(CancellationToken canc /// /// Optional, . /// Optional, . - /// . - public async Task UploadFileAsync(string filePath, string purpose, IProgress uploadProgress = null, CancellationToken cancellationToken = default) + /// . + public async Task UploadFileAsync(string filePath, string purpose, IProgress uploadProgress = null, CancellationToken cancellationToken = default) => await UploadFileAsync(new FileUploadRequest(filePath, purpose), uploadProgress, cancellationToken); /// @@ -77,18 +77,18 @@ public async Task UploadFileAsync(string filePath, string purpose, IPr /// . /// Optional, . /// Optional, . - /// . - public async Task UploadFileAsync(FileUploadRequest request, IProgress uploadProgress = null, CancellationToken cancellationToken = default) + /// . + public async Task UploadFileAsync(FileUploadRequest request, IProgress uploadProgress = null, CancellationToken cancellationToken = default) { - var form = new WWWForm(); using var fileData = new MemoryStream(); + var content = new WWWForm(); await request.File.CopyToAsync(fileData, cancellationToken); - form.AddField("purpose", request.Purpose); - form.AddBinaryData("file", fileData.ToArray(), request.FileName); + content.AddField("purpose", request.Purpose); + content.AddBinaryData("file", fileData.ToArray(), request.FileName); request.Dispose(); - var response = await Rest.PostAsync(GetUrl(), form, new RestParameters(client.DefaultRequestHeaders, uploadProgress), cancellationToken); + var response = await Rest.PostAsync(GetUrl(), content, new RestParameters(client.DefaultRequestHeaders, uploadProgress), cancellationToken); response.Validate(EnableDebug); - return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); + return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); } /// @@ -121,7 +121,7 @@ async Task InternalDeleteFileAsync(int attempt) } response.Validate(EnableDebug); - return response.Successful; + return JsonConvert.DeserializeObject(response.Body!, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; } } @@ -130,12 +130,12 @@ async Task InternalDeleteFileAsync(int attempt) /// /// The ID of the file to use for this request. /// Optional, . - /// . - public async Task GetFileInfoAsync(string fileId, CancellationToken cancellationToken = default) + /// . + public async Task GetFileInfoAsync(string fileId, CancellationToken cancellationToken = default) { var response = await Rest.GetAsync(GetUrl($"/{fileId}"), new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); + return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); } /// @@ -147,8 +147,8 @@ public async Task GetFileInfoAsync(string fileId, CancellationToken ca /// The path to the downloaded file. public async Task DownloadFileAsync(string fileId, IProgress progress = null, CancellationToken cancellationToken = default) { - var fileData = await GetFileInfoAsync(fileId, cancellationToken); - return await Rest.DownloadFileAsync(GetUrl($"/{fileData.Id}/content"), fileData.FileName, new RestParameters(client.DefaultRequestHeaders, progress), cancellationToken: cancellationToken); + var file = await GetFileInfoAsync(fileId, cancellationToken); + return await Rest.DownloadFileAsync(GetUrl($"/{file.Id}/content"), file.FileName, new RestParameters(client.DefaultRequestHeaders, progress), cancellationToken: cancellationToken); } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/EventList.cs b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/EventList.cs index c62eb173..f8864797 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/EventList.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/EventList.cs @@ -1,12 +1,14 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Newtonsoft.Json; +using System; using System.Collections.Generic; using UnityEngine.Scripting; namespace OpenAI.FineTuning { [Preserve] + [Obsolete("Use ListResponse")] public sealed class EventList { [Preserve] diff --git a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJob.cs b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJob.cs index 7d8eafa3..3126c7c1 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJob.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJob.cs @@ -8,7 +8,8 @@ namespace OpenAI.FineTuning { [Preserve] - public sealed class FineTuneJob + [Obsolete("use FineTuneJobResponse")] + public sealed class FineTuneJob : BaseResponse { [Preserve] [JsonConstructor] @@ -105,6 +106,10 @@ public FineTuneJob( [JsonIgnore] public IReadOnlyList Events { get; internal set; } = new List(); - public static implicit operator string(FineTuneJob job) => job.Id; + public static implicit operator FineTuneJobResponse(FineTuneJob job) => new FineTuneJobResponse(job); + + public static implicit operator string(FineTuneJob job) => job?.ToString(); + + public override string ToString() => Id; } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobList.cs b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobList.cs index f7d21b1b..236ead13 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobList.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobList.cs @@ -1,12 +1,14 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Newtonsoft.Json; +using System; using System.Collections.Generic; using UnityEngine.Scripting; namespace OpenAI.FineTuning { [Preserve] + [Obsolete("Use ListResponse")] public sealed class FineTuneJobList { [Preserve] diff --git a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs new file mode 100644 index 00000000..fe45f441 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs @@ -0,0 +1,126 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.Scripting; + +namespace OpenAI.FineTuning +{ + public sealed class FineTuneJobResponse : BaseResponse + { + public FineTuneJobResponse() { } + +#pragma warning disable CS0618 // Type or member is obsolete + internal FineTuneJobResponse(FineTuneJob job) + { + Object = job.Object; + Id = job.Id; + Model = job.Model; + CreateAtUnixTimeSeconds = job.CreatedAtUnixTime; + FinishedAtUnixTimeSeconds = job.FinishedAtUnixTime; + FineTunedModel = job.FineTunedModel; + OrganizationId = job.OrganizationId; + ResultFiles = job.ResultFiles; + Status = job.Status; + ValidationFile = job.ValidationFile; + TrainingFile = job.TrainingFile; + HyperParameters = job.HyperParameters; + TrainedTokens = job.TrainedTokens; + events = new List(job.Events.Count); + + foreach (var jobEvent in job.Events) + { + jobEvent.Client = Client; + events.Add(jobEvent); + } + } +#pragma warning restore CS0618 // Type or member is obsolete + + [Preserve] + [JsonProperty("object")] + public string Object { get; private set; } + + [Preserve] + [JsonProperty("id")] + public string Id { get; private set; } + + [Preserve] + [JsonProperty("model")] + public string Model { get; private set; } + + [Preserve] + [JsonProperty("created_at")] + public int? CreateAtUnixTimeSeconds { get; private set; } + + [JsonIgnore] + public DateTime? CreatedAt + => CreateAtUnixTimeSeconds.HasValue + ? DateTimeOffset.FromUnixTimeSeconds(CreateAtUnixTimeSeconds.Value).DateTime + : null; + + [Preserve] + [JsonProperty("finished_at")] + public int? FinishedAtUnixTimeSeconds { get; private set; } + + [JsonIgnore] + public DateTime? FinishedAt + => FinishedAtUnixTimeSeconds.HasValue + ? DateTimeOffset.FromUnixTimeSeconds(FinishedAtUnixTimeSeconds.Value).DateTime + : null; + + [Preserve] + [JsonProperty("fine_tuned_model")] + public string FineTunedModel { get; private set; } + + [Preserve] + [JsonProperty("organization_id")] + public string OrganizationId { get; private set; } + + [Preserve] + [JsonProperty("result_files")] + public IReadOnlyList ResultFiles { get; private set; } + + [Preserve] + [JsonProperty("status")] + public JobStatus Status { get; private set; } + + [Preserve] + [JsonProperty("validation_file")] + public string ValidationFile { get; private set; } + + [Preserve] + [JsonProperty("training_file")] + public string TrainingFile { get; private set; } + + [Preserve] + [JsonProperty("hyperparameters")] + public HyperParams HyperParameters { get; private set; } + + [Preserve] + [JsonProperty("trained_tokens")] + public int? TrainedTokens { get; private set; } + + private List events = new List(); + + [JsonIgnore] + public IReadOnlyList Events + { + get => events; + internal set + { + events = value?.ToList() ?? new List(); + + foreach (var @event in events) + { + @event.Client = Client; + } + } + } + + public static implicit operator string(FineTuneJobResponse job) => job?.ToString(); + + public override string ToString() => Id; + } +} diff --git a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs.meta b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs.meta new file mode 100644 index 00000000..1a5768ad --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuneJobResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce4bcca38ef73104aaa666a61b1a8482 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningEndpoint.cs index a80024ad..f655c191 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningEndpoint.cs @@ -1,6 +1,8 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Newtonsoft.Json; +using OpenAI.Extensions; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -10,7 +12,8 @@ namespace OpenAI.FineTuning { /// /// Manage fine-tuning jobs to tailor a model to your specific training data.
- /// + ///
+ /// ///
public sealed class FineTuningEndpoint : OpenAIBaseEndpoint { @@ -27,23 +30,17 @@ public FineTuningEndpoint(OpenAIClient client) : base(client) { } ///
/// . /// Optional, . - /// . - public async Task CreateJobAsync(CreateFineTuneJobRequest jobRequest, CancellationToken cancellationToken = default) + /// . + public async Task CreateJobAsync(CreateFineTuneJobRequest jobRequest, CancellationToken cancellationToken = default) { var payload = JsonConvert.SerializeObject(jobRequest, OpenAIClient.JsonSerializationOptions); var response = await Rest.PostAsync(GetUrl("/jobs"), payload, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); + return response.Deserialize(response.Body, client); } - /// - /// List your organization's fine-tuning jobs. - /// - /// Number of fine-tuning jobs to retrieve (Default 20). - /// Identifier for the last job from the previous pagination request. - /// Optional, . - /// List of s. - public async Task ListJobsAsync(int? limit = null, string after = null, CancellationToken cancellationToken = default) + [Obsolete("Use new overload")] + public async Task ListJobsAsync(int? limit, string after, CancellationToken cancellationToken = default) { var parameters = new Dictionary(); @@ -62,18 +59,31 @@ public async Task ListJobsAsync(int? limit = null, string after return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); } + /// + /// List your organization's fine-tuning jobs. + /// + /// . + /// Optional, . + /// List of s. + public async Task> ListJobsAsync(ListQuery query = null, CancellationToken cancellationToken = default) + { + var response = await Rest.GetAsync(GetUrl("/jobs", query), new RestParameters(client.DefaultRequestHeaders), cancellationToken); + response.Validate(EnableDebug); + return response.Deserialize>(response.Body, client); + } + /// /// Gets info about the fine-tune job. /// /// . /// Optional, . - /// . - public async Task GetJobInfoAsync(string jobId, CancellationToken cancellationToken = default) + /// . + public async Task GetJobInfoAsync(string jobId, CancellationToken cancellationToken = default) { var response = await Rest.GetAsync(GetUrl($"/jobs/{jobId}"), new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - var job = JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); - job.Events = (await ListJobEventsAsync(job, cancellationToken: cancellationToken)).Events; + var job = response.Deserialize(response.Body, client); + job.Events = (await ListJobEventsAsync(job, query: null, cancellationToken: cancellationToken).ConfigureAwait(false))?.Items; return job; } @@ -82,24 +92,17 @@ public async Task GetJobInfoAsync(string jobId, CancellationToken c ///
/// to cancel. /// Optional, . - /// . + /// . public async Task CancelJobAsync(string jobId, CancellationToken cancellationToken = default) { - var response = await Rest.PostAsync(GetUrl($"/jobs/{jobId}/cancel"), new RestParameters(client.DefaultRequestHeaders), cancellationToken); + var response = await Rest.PostAsync(GetUrl($"/jobs/{jobId}/cancel"), jsonData: string.Empty, new RestParameters(client.DefaultRequestHeaders), cancellationToken); response.Validate(EnableDebug); - var result = JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); + var result = JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); return result.Status == JobStatus.Cancelled; } - /// - /// Get fine-grained status updates for a fine-tune job. - /// - /// . - /// Number of fine-tuning jobs to retrieve (Default 20). - /// Identifier for the last from the previous pagination request. - /// Optional, . - /// List of events for . - public async Task ListJobEventsAsync(string jobId, int? limit = null, string after = null, CancellationToken cancellationToken = default) + [Obsolete("use new overload")] + public async Task ListJobEventsAsync(string jobId, int? limit, string after, CancellationToken cancellationToken = default) { var parameters = new Dictionary(); @@ -117,5 +120,19 @@ public async Task ListJobEventsAsync(string jobId, int? limit = null, response.Validate(EnableDebug); return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); } + + /// + /// Get fine-grained status updates for a fine-tune job. + /// + /// . + /// . + /// Optional, . + /// List of events for . + public async Task> ListJobEventsAsync(string jobId, ListQuery query = null, CancellationToken cancellationToken = default) + { + var response = await Rest.GetAsync(GetUrl($"/jobs/{jobId}/events", query), new RestParameters(client.DefaultRequestHeaders), cancellationToken); + response.Validate(EnableDebug); + return response.Deserialize>(response.Body, client); + } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningTrainingDataSet.cs b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningTrainingDataSet.cs index 4ab79d78..e087b805 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningTrainingDataSet.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/FineTuning/FineTuningTrainingDataSet.cs @@ -133,9 +133,9 @@ public List ConversationTrainingData } [NonSerialized] - private FineTuneJob fineTuneJob; + private FineTuneJobResponse fineTuneJob; - internal FineTuneJob FineTuneJob + internal FineTuneJobResponse FineTuneJob { get => fineTuneJob; set diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs index 8e2034cc..ebc36feb 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImageResult.cs @@ -6,7 +6,7 @@ namespace OpenAI.Images { [Preserve] - internal class ImageResult + internal sealed class ImageResult { [Preserve] [JsonConstructor] diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs index 826d109a..2e6f3028 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Images/ImagesEndpoint.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Unity.Collections; using UnityEngine; using Utilities.Async; using Utilities.WebRequestRest; @@ -69,9 +68,8 @@ public async Task> GenerateImageAsync( /// A dictionary of file urls and the preloaded that were downloaded. public async Task> GenerateImageAsync(ImageGenerationRequest request, CancellationToken cancellationToken = default) { - var payload = JsonConvert.SerializeObject(request, OpenAIClient.JsonSerializationOptions); - var endpoint = GetUrl($"/generations{(client.Settings.Info.IsAzureDeployment ? ":submit" : string.Empty)}"); - var response = await Rest.PostAsync(endpoint, payload, new RestParameters(client.DefaultRequestHeaders), cancellationToken); + var jsonContent = JsonConvert.SerializeObject(request, OpenAIClient.JsonSerializationOptions); + var response = await Rest.PostAsync(GetUrl("/generations"), jsonContent, new RestParameters(client.DefaultRequestHeaders), cancellationToken); return await DeserializeResponseAsync(response, cancellationToken); } @@ -297,7 +295,7 @@ private async Task> DeserializeResponseAs { response.Validate(EnableDebug); - var imagesResponse = response.DeserializeResponse(response.Body); + var imagesResponse = response.Deserialize(response.Body, client); if (imagesResponse?.Results is not { Count: not 0 }) { diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Models/ModelsEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Models/ModelsEndpoint.cs index 68ef8527..cc7ee2ae 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Models/ModelsEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Models/ModelsEndpoint.cs @@ -32,34 +32,6 @@ public ModelsList([JsonProperty("data")] List data) public List Data { get; } } - [Preserve] - private class DeleteModelResponse - { - [Preserve] - [JsonConstructor] - public DeleteModelResponse( - [JsonProperty("id")] string id, - [JsonProperty("object")] string @object, - [JsonProperty("deleted")] bool deleted) - { - Id = id; - Object = @object; - Deleted = deleted; - } - - [Preserve] - [JsonProperty("id")] - public string Id { get; } - - [Preserve] - [JsonProperty("object")] - public string Object { get; } - - [Preserve] - [JsonProperty("deleted")] - public bool Deleted { get; } - } - /// public ModelsEndpoint(OpenAIClient client) : base(client) { } @@ -113,12 +85,11 @@ public async Task DeleteFineTuneModelAsync(string modelId, CancellationTok { var response = await Rest.DeleteAsync(GetUrl($"/{model.Id}"), new RestParameters(client.DefaultRequestHeaders), cancellationToken: cancellationToken); response.Validate(EnableDebug); - return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; + return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; } catch (RestException e) { - if (e.Response.Code == 403 || - e.Message.Contains("You have insufficient permissions for this operation. You need to be this role: Owner.")) + if (e.Message.Contains("You have insufficient permissions for this operation. You need to be this role: Owner.")) { throw new UnauthorizedAccessException("You have insufficient permissions for this operation. You need to be this role: Owner."); } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/Moderations/ModerationsEndpoint.cs b/OpenAI/Packages/com.openai.unity/Runtime/Moderations/ModerationsEndpoint.cs index 7d32d3b8..30a56c4f 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/Moderations/ModerationsEndpoint.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/Moderations/ModerationsEndpoint.cs @@ -1,6 +1,7 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Newtonsoft.Json; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -54,5 +55,63 @@ public async Task CreateModerationAsync(ModerationsRequest response.Validate(EnableDebug); return JsonConvert.DeserializeObject(response.Body, OpenAIClient.JsonSerializationOptions); } + + + /// + /// Classifies if text violates OpenAI's Content Policy. + /// + /// + /// This version splits into chunks and makes multiple moderation requests, + /// which should provide better results when dealing with a large . + ///

On the first flagged chunk, the method returns. + ///
+ /// + /// The input text to classify. + /// + /// The default is text-moderation-latest which will be automatically upgraded over time. + /// This ensures you are always using our most accurate model. + /// If you use text-moderation-stable, we will provide advanced notice before updating the model. + /// Accuracy of text-moderation-stable may be slightly lower than for text-moderation-latest. + /// + /// Maximum size each chunk can be. + /// How many characters a chunk should contain from the previous chunk. + /// Optional, . + /// + /// True, if the text has been flagged by the model as violating OpenAI's content policy. + /// + public async Task GetModerationChunkedAsync( + string input, + string model = null, + int chunkSize = 1000, + int chunkOverlap = 100, + CancellationToken cancellationToken = default) + { + if (chunkSize <= 0) + { + throw new ArgumentException($"{nameof(chunkSize)} must be greater than 0"); + } + + if (chunkOverlap <= 0) + { + throw new ArgumentException($"{nameof(chunkOverlap)} must be greater than 0"); + } + + if (chunkOverlap >= chunkSize) + { + throw new ArgumentException($"{nameof(chunkOverlap)} must be smaller than {nameof(chunkSize)}"); + } + + for (int i = 0; i < input.Length; i += chunkSize - chunkOverlap) + { + var result = await GetModerationAsync(input[i..(i + chunkSize > input.Length ? ^1 : (i + chunkSize))], model, cancellationToken); + + if (result) + { + return true; + } + } + + return false; + } } } diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClient.cs b/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClient.cs index 4853dd33..9d9645e0 100644 --- a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClient.cs +++ b/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClient.cs @@ -1,6 +1,5 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. -using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; @@ -14,6 +13,7 @@ using OpenAI.Images; using OpenAI.Models; using OpenAI.Moderations; +using System; using System.Collections.Generic; using System.Security.Authentication; using Utilities.Rest.Extensions; @@ -88,6 +88,11 @@ protected override void SetupDefaultRequestHeaders() protected override void ValidateAuthentication() { + if (Authentication?.Info == null) + { + throw new InvalidCredentialException($"Invalid {nameof(OpenAIAuthentication)}"); + } + if (!HasValidAuthentication) { throw new InvalidCredentialException($"Missing API key for {nameof(OpenAIClient)}"); diff --git a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClientSettings.cs b/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClientSettings.cs deleted file mode 100644 index 5f282702..00000000 --- a/OpenAI/Packages/com.openai.unity/Runtime/OpenAIClientSettings.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs b/OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs new file mode 100644 index 00000000..bf2a5082 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs @@ -0,0 +1,17 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace OpenAI.Tests +{ + internal abstract class AbstractTestFixture + { + protected readonly OpenAIClient OpenAIClient; + + protected AbstractTestFixture() + { + var auth = new OpenAIAuthentication().LoadDefaultsReversed(); + var settings = new OpenAISettings(); + OpenAIClient = new OpenAIClient(auth, settings); + //OpenAIClient.EnableDebug = true; + } + } +} diff --git a/OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs.meta b/OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs.meta new file mode 100644 index 00000000..4baaf3f1 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Tests/AbstractTestFixture.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c18dfcd978c74704aacd2fe200ef5be7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_00_Authentication.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_00_Authentication.cs index e18e17aa..57c84ce6 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_00_Authentication.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_00_Authentication.cs @@ -22,7 +22,7 @@ public void Setup() [Test] public void Test_01_GetAuthFromEnv() { - var auth = OpenAIAuthentication.Default.LoadFromEnvironment(); + var auth = new OpenAIAuthentication().LoadFromEnvironment(); Assert.IsNotNull(auth); Assert.IsNotNull(auth.Info.ApiKey); Assert.IsNotEmpty(auth.Info.ApiKey); @@ -33,7 +33,7 @@ public void Test_01_GetAuthFromEnv() [Test] public void Test_02_GetAuthFromFile() { - var auth = OpenAIAuthentication.Default.LoadFromPath(Path.GetFullPath(OpenAIAuthentication.CONFIG_FILE)); + var auth = new OpenAIAuthentication().LoadFromPath(Path.GetFullPath(OpenAIAuthentication.CONFIG_FILE)); Assert.IsNotNull(auth); Assert.IsNotNull(auth.Info.ApiKey); Assert.AreEqual("sk-test12", auth.Info.ApiKey); @@ -44,7 +44,7 @@ public void Test_02_GetAuthFromFile() [Test] public void Test_03_GetAuthFromNonExistentFile() { - var auth = OpenAIAuthentication.Default.LoadFromDirectory(filename: "bad.config"); + var auth = new OpenAIAuthentication().LoadFromDirectory(filename: "bad.config"); Assert.IsNull(auth); } @@ -68,14 +68,14 @@ public void Test_04_GetAuthFromConfiguration() cleanup = true; } - var config = AssetDatabase.LoadAssetAtPath(configPath); - var auth = OpenAIAuthentication.Default.LoadFromAsset(); + var configuration = AssetDatabase.LoadAssetAtPath(configPath); + var auth = new OpenAIAuthentication(configuration); Assert.IsNotNull(auth); Assert.IsNotNull(auth.Info.ApiKey); Assert.IsNotEmpty(auth.Info.ApiKey); - Assert.AreEqual(auth.Info.ApiKey, config.ApiKey); - Assert.AreEqual(auth.Info.OrganizationId, config.OrganizationId); + Assert.AreEqual(auth.Info.ApiKey, configuration.ApiKey); + Assert.AreEqual(auth.Info.OrganizationId, configuration.OrganizationId); if (cleanup) { @@ -87,17 +87,18 @@ public void Test_04_GetAuthFromConfiguration() [Test] public void Test_05_Authentication() { - var defaultAuth = OpenAIAuthentication.Default; - var manualAuth = new OpenAIAuthentication("sk-testAA", "org-testAA"); + var defaultAuth = OpenAIAuthentication.Default = new OpenAIAuthentication().LoadDefault(); Assert.IsNotNull(defaultAuth); + Assert.IsNotNull(defaultAuth.Info); Assert.IsNotNull(defaultAuth.Info.ApiKey); Assert.IsNotNull(defaultAuth.Info.OrganizationId); Assert.AreEqual(defaultAuth.Info.ApiKey, OpenAIAuthentication.Default.Info.ApiKey); Assert.AreEqual(defaultAuth.Info.OrganizationId, OpenAIAuthentication.Default.Info.OrganizationId); - OpenAIAuthentication.Default = new OpenAIAuthentication("sk-testAA", "org-testAA"); + var manualAuth = new OpenAIAuthentication("sk-testAA", "org-testAA"); Assert.IsNotNull(manualAuth); + Assert.IsNotNull(manualAuth.Info); Assert.IsNotNull(manualAuth.Info.ApiKey); Assert.IsNotNull(manualAuth.Info.OrganizationId); Assert.AreEqual(manualAuth.Info.ApiKey, OpenAIAuthentication.Default.Info.ApiKey); @@ -181,18 +182,18 @@ public void Test_11_AzureConfigurationSettings() var auth = new OpenAIAuthentication("testKeyAaBbCcDd"); var settings = new OpenAISettings(resourceName: "test-resource", deploymentId: "deployment-id-test"); var api = new OpenAIClient(auth, settings); - Console.WriteLine(api.Settings.Info.BaseRequest); - Console.WriteLine(api.Settings.Info.BaseRequestUrlFormat); + Debug.Log(api.Settings.Info.BaseRequest); + Debug.Log(api.Settings.Info.BaseRequestUrlFormat); } [Test] public void Test_12_CustomDomainConfigurationSettings() { var auth = new OpenAIAuthentication("sess-customIssuedToken"); - var settings = new OpenAISettings(domain: "api.your-custom-domain.com"); + var settings = new OpenAISettings(domain: "OpenAIClient.your-custom-domain.com"); var api = new OpenAIClient(auth, settings); - Console.WriteLine(api.Settings.Info.BaseRequest); - Console.WriteLine(api.Settings.Info.BaseRequestUrlFormat); + Debug.Log(api.Settings.Info.BaseRequest); + Debug.Log(api.Settings.Info.BaseRequestUrlFormat); } [TearDown] @@ -202,6 +203,9 @@ public void TearDown() { File.Delete(OpenAIAuthentication.CONFIG_FILE); } + + OpenAIAuthentication.Default = null; + OpenAISettings.Default = null; } } } diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_01_Models.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_01_Models.cs index f1d02a62..743ccf86 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_01_Models.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_01_Models.cs @@ -8,14 +8,13 @@ namespace OpenAI.Tests { - internal class TestFixture_01_Models + internal class TestFixture_01_Models : AbstractTestFixture { [Test] public async Task Test_1_GetModels() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ModelsEndpoint); - var results = await api.ModelsEndpoint.GetModelsAsync(); + Assert.IsNotNull(OpenAIClient.ModelsEndpoint); + var results = await OpenAIClient.ModelsEndpoint.GetModelsAsync(); Assert.IsNotNull(results); Assert.NotZero(results.Count); } @@ -23,10 +22,10 @@ public async Task Test_1_GetModels() [Test] public async Task Test_2_RetrieveModelDetails() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ModelsEndpoint); - var models = await api.ModelsEndpoint.GetModelsAsync(); + Assert.IsNotNull(OpenAIClient.ModelsEndpoint); + var models = await OpenAIClient.ModelsEndpoint.GetModelsAsync(); Assert.IsNotEmpty(models); + Debug.Log($"Found {models.Count} models!"); foreach (var model in models.OrderBy(model => model.Id)) { @@ -34,7 +33,7 @@ public async Task Test_2_RetrieveModelDetails() try { - var result = await api.ModelsEndpoint.GetModelDetailsAsync(model.Id); + var result = await OpenAIClient.ModelsEndpoint.GetModelDetailsAsync(model.Id); Assert.IsNotNull(result); } catch (Exception e) diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_02_Completions.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_02_Completions.cs index f6916ee5..4dbd2505 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_02_Completions.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_02_Completions.cs @@ -10,16 +10,15 @@ namespace OpenAI.Tests { - internal class TestFixture_02_Completions + internal class TestFixture_02_Completions : AbstractTestFixture { private const string CompletionPrompts = "One Two Three Four Five Six Seven Eight Nine One Two Three Four Five Six Seven Eight"; [Test] - public async Task Test_1_GetBasicCompletion() + public async Task Test_01_GetBasicCompletion() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.CompletionsEndpoint); - var result = await api.CompletionsEndpoint.CreateCompletionAsync( + Assert.IsNotNull(OpenAIClient.CompletionsEndpoint); + var result = await OpenAIClient.CompletionsEndpoint.CreateCompletionAsync( CompletionPrompts, temperature: 0.1, maxTokens: 5, @@ -33,13 +32,12 @@ public async Task Test_1_GetBasicCompletion() } [Test] - public async Task Test_2_GetStreamingCompletion() + public async Task Test_02_GetStreamingCompletion() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.CompletionsEndpoint); + Assert.IsNotNull(OpenAIClient.CompletionsEndpoint); var allCompletions = new List(); - await api.CompletionsEndpoint.StreamCompletionAsync(result => + await OpenAIClient.CompletionsEndpoint.StreamCompletionAsync(result => { Assert.IsNotNull(result); Assert.NotNull(result.Completions); diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_03_Chat.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_03_Chat.cs index c890fca7..3feab744 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_03_Chat.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_03_Chat.cs @@ -4,8 +4,8 @@ using Newtonsoft.Json.Linq; using NUnit.Framework; using OpenAI.Chat; +using OpenAI.Models; using OpenAI.Tests.Weather; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -15,13 +15,12 @@ namespace OpenAI.Tests { - internal class TestFixture_03_Chat + internal class TestFixture_03_Chat : AbstractTestFixture { [Test] - public async Task Test_01_GetChatCompletion() + public async Task Test_01_01_GetChatCompletion() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful assistant."), @@ -29,26 +28,24 @@ public async Task Test_01_GetChatCompletion() new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."), new Message(Role.User, "Where was it played?"), }; - var chatRequest = new ChatRequest(messages, number: 2); - var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 2); + var chatRequest = new ChatRequest(messages, Model.GPT4); + var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsNotEmpty(response.Choices); - foreach (var choice in result.Choices) + foreach (var choice in response.Choices) { - Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}"); + Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); } - result.GetUsage(); + response.GetUsage(); } [Test] - public async Task Test_02_GetChatStreamingCompletion() + public async Task Test_01_02_GetChatStreamingCompletion() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); - const int choiceCount = 2; + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful assistant."), @@ -56,15 +53,9 @@ public async Task Test_02_GetChatStreamingCompletion() new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."), new Message(Role.User, "Where was it played?"), }; - var chatRequest = new ChatRequest(messages, number: choiceCount); - var cumulativeDelta = new List(); - - for (var i = 0; i < choiceCount; i++) - { - cumulativeDelta.Add(string.Empty); - } - - var response = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => + var chatRequest = new ChatRequest(messages); + var cumulativeDelta = string.Empty; + var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); Assert.NotNull(partialResponse.Choices); @@ -72,34 +63,50 @@ public async Task Test_02_GetChatStreamingCompletion() foreach (var choice in partialResponse.Choices.Where(choice => choice.Delta?.Content != null)) { - Debug.Log($"[{choice.Index}] {choice.Delta.Content}"); - cumulativeDelta[choice.Index] += choice.Delta.Content; + cumulativeDelta += choice.Delta.Content; } }); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + var choice = response.FirstChoice; + Assert.IsNotNull(choice); + Assert.IsNotNull(choice.Message); + Assert.IsFalse(string.IsNullOrEmpty(choice.ToString())); + Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); + Assert.IsTrue(choice.Message.Role == Role.Assistant); + Assert.IsTrue(choice.Message.Content!.Equals(cumulativeDelta)); + Debug.Log(response.ToString()); + response.GetUsage(); + } + [Test] + public async Task Test_01_03_JsonMode() + { + Assert.IsNotNull(OpenAIClient.ChatEndpoint); + var messages = new List + { + new Message(Role.System, "You are a helpful assistant designed to output JSON."), + new Message(Role.User, "Who won the world series in 2020?"), + }; + var chatRequest = new ChatRequest(messages, "gpt-4-1106-preview", responseFormat: ChatResponseFormat.Json); + var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); Assert.IsNotNull(response); Assert.IsNotNull(response.Choices); - Assert.IsTrue(response.Choices.Count == choiceCount); + Assert.IsNotEmpty(response.Choices); - for (var i = 0; i < choiceCount; i++) + foreach (var choice in response.Choices) { - var choice = response.Choices[i]; - Assert.IsFalse(string.IsNullOrEmpty(choice?.Message?.ToString())); - Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}"); - Assert.IsTrue(choice.Message.Role == Role.Assistant); - var deltaContent = cumulativeDelta[i]; - Assert.IsTrue(choice.Message.Content.Equals(deltaContent)); + Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); } response.GetUsage(); } [Test] - [Obsolete] - public async Task Test_04_GetChatFunctionCompletion() + public async Task Test_02_01_GetChatToolCompletion() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); + var messages = new List { new Message(Role.System, "You are a helpful weather assistant."), @@ -111,7 +118,7 @@ public async Task Test_04_GetChatFunctionCompletion() Debug.Log($"{message.Role}: {message.Content}"); } - var functions = new List + var tools = new List { new Function( nameof(WeatherService.GetCurrentWeather), @@ -135,61 +142,60 @@ public async Task Test_04_GetChatFunctionCompletion() ["required"] = new JArray { "location", "unit" } }) }; + var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); + messages.Add(response.FirstChoice.Message); - var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); - var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); - - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); messages.Add(locationMessage); Debug.Log($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); + messages.Add(response.FirstChoice.Message); - if (!string.IsNullOrEmpty(result.FirstChoice.Message.ToString())) + if (!string.IsNullOrEmpty(response.ToString())) { - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var unitMessage = new Message(Role.User, "celsius"); messages.Add(unitMessage); Debug.Log($"{unitMessage.Role}: {unitMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); } - Assert.IsTrue(result.FirstChoice.FinishReason == "function_call"); - Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); - Debug.Log($"{result.FirstChoice.Message.Function.Arguments}"); - - var functionArgs = JsonConvert.DeserializeObject(result.FirstChoice.Message.Function.Arguments.ToString()); + Assert.IsTrue(response.FirstChoice.FinishReason == "tool_calls"); + var usedTool = response.FirstChoice.Message.ToolCalls[0]; + Assert.IsNotNull(usedTool); + Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather)); + Debug.Log($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); + Debug.Log($"{usedTool.Function.Arguments}"); + var functionArgs = JsonConvert.DeserializeObject(usedTool.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); Assert.IsNotNull(functionResult); - messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather))); - Debug.Log($"{Role.Function}: {functionResult}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Debug.Log(result); + messages.Add(new Message(usedTool, functionResult)); + Debug.Log($"{Role.Tool}: {functionResult}"); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Debug.Log(response); } [Test] - [Obsolete] - public async Task Test_05_GetChatFunctionCompletion_Streaming() + public async Task Test_02_02_GetChatToolCompletion_Streaming() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful weather assistant."), @@ -201,7 +207,7 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming() Debug.Log($"{message.Role}: {message.Content}"); } - var functions = new List + var tools = new List { new Function( nameof(WeatherService.GetCurrentWeather), @@ -225,280 +231,85 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming() ["required"] = new JArray { "location", "unit" } }) }; - - var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); - var result = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => + var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); Assert.NotNull(partialResponse.Choices); Assert.NotZero(partialResponse.Choices.Count); - - foreach (var choice in partialResponse.Choices.Where(choice => !string.IsNullOrEmpty(choice.Delta?.Content))) - { - Debug.Log($"{choice.Delta.Content}"); - } - - foreach (var choice in partialResponse.Choices.Where(choice => !string.IsNullOrEmpty(choice.Message?.Content?.ToString()))) - { - Debug.Log($"{choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}"); - } }); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); + messages.Add(response.FirstChoice.Message); var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); messages.Add(locationMessage); Debug.Log($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); - result = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); Assert.NotNull(partialResponse.Choices); Assert.NotZero(partialResponse.Choices.Count); - - foreach (var choice in partialResponse.Choices.Where(choice => !string.IsNullOrEmpty(choice.Delta?.Content))) - { - Debug.Log($"[{choice.Index}] {choice.Delta.Content}"); - } - - foreach (var choice in partialResponse.Choices.Where(choice => !string.IsNullOrEmpty(choice.Message?.Content?.ToString()))) - { - Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}"); - } }); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); + messages.Add(response.FirstChoice.Message); - if (!string.IsNullOrEmpty(result.FirstChoice.Message?.ToString())) + if (!string.IsNullOrEmpty(response.ToString())) { - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var unitMessage = new Message(Role.User, "celsius"); messages.Add(unitMessage); Debug.Log($"{unitMessage.Role}: {unitMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); - result = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); Assert.NotNull(partialResponse.Choices); Assert.NotZero(partialResponse.Choices.Count); - - foreach (var choice in partialResponse.Choices.Where(choice => !string.IsNullOrEmpty(choice.Delta?.Content))) - { - Debug.Log($"{choice.Delta.Content}"); - } - - foreach (var choice in partialResponse.Choices.Where(choice => !string.IsNullOrEmpty(choice.Message?.ToString()))) - { - Debug.Log($"{choice.Message.Role}: {choice.Message.Content} | Finish Reason: {choice.FinishReason}"); - } }); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); } - Assert.IsTrue(result.FirstChoice.FinishReason == "function_call"); - Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); - Debug.Log($"{result.FirstChoice.Message.Function.Arguments}"); - - var functionArgs = JsonConvert.DeserializeObject(result.FirstChoice.Message.Function.Arguments.ToString()); - var functionResult = WeatherService.GetCurrentWeather(functionArgs); - Assert.IsNotNull(functionResult); - messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather))); - Debug.Log($"{Role.Function}: {functionResult}"); - } - - [Test] - [Obsolete] - public async Task Test_06_GetChatFunctionForceCompletion() - { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); - var messages = new List - { - new Message(Role.System, "You are a helpful weather assistant."), - new Message(Role.User, "What's the weather like today?"), - }; - - foreach (var message in messages) - { - Debug.Log($"{message.Role}: {message.Content}"); - } - - var functions = new List - { - new Function( - nameof(WeatherService.GetCurrentWeather), - "Get the current weather in a given location", - new JObject - { - ["type"] = "object", - ["properties"] = new JObject - { - ["location"] = new JObject - { - ["type"] = "string", - ["description"] = "The city and state, e.g. San Francisco, CA" - }, - ["unit"] = new JObject - { - ["type"] = "string", - ["enum"] = new JArray {"celsius", "fahrenheit"} - } - }, - ["required"] = new JArray { "location", "unit" } - }) - }; - - var chatRequest = new ChatRequest(messages, functions: functions); - var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); - - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); - - var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); - messages.Add(locationMessage); - Debug.Log($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest( - messages, - functions: functions, - functionCall: nameof(WeatherService.GetCurrentWeather), - model: "gpt-3.5-turbo"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); - - Assert.IsTrue(result.FirstChoice.FinishReason == "stop"); - Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); - Debug.Log($"{result.FirstChoice.Message.Function.Arguments}"); - - var functionArgs = JsonConvert.DeserializeObject(result.FirstChoice.Message.Function.Arguments.ToString()); - var functionResult = WeatherService.GetCurrentWeather(functionArgs); - Assert.IsNotNull(functionResult); - messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather))); - Debug.Log($"{Role.Function}: {functionResult}"); - } - - [Test] - public async Task Test_07_GetChatToolCompletion() - { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); - - var messages = new List - { - new Message(Role.System, "You are a helpful weather assistant."), - new Message(Role.User, "What's the weather like today?"), - }; - - foreach (var message in messages) - { - Debug.Log($"{message.Role}: {message.Content}"); - } - - var tools = new List - { - new Function( - nameof(WeatherService.GetCurrentWeather), - "Get the current weather in a given location", - new JObject - { - ["type"] = "object", - ["properties"] = new JObject - { - ["location"] = new JObject - { - ["type"] = "string", - ["description"] = "The city and state, e.g. San Francisco, CA" - }, - ["unit"] = new JObject - { - ["type"] = "string", - ["enum"] = new JArray {"celsius", "fahrenheit"} - } - }, - ["required"] = new JArray { "location", "unit" } - }) - }; - - var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); - - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); - - var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); - messages.Add(locationMessage); - Debug.Log($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); - - if (!string.IsNullOrEmpty(result.FirstChoice.Message.ToString())) - { - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); - - var unitMessage = new Message(Role.User, "celsius"); - messages.Add(unitMessage); - Debug.Log($"{unitMessage.Role}: {unitMessage.Content}"); - chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - } - - var usedTool = result.FirstChoice.Message.ToolCalls[0]; - Assert.IsTrue(result.FirstChoice.FinishReason == "tool_calls"); + Assert.IsTrue(response.FirstChoice.FinishReason == "tool_calls"); + var usedTool = response.FirstChoice.Message.ToolCalls[0]; + Assert.IsNotNull(usedTool); Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Debug.Log($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); Debug.Log($"{usedTool.Function.Arguments}"); + var functionArgs = JsonConvert.DeserializeObject(usedTool.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); Assert.IsNotNull(functionResult); messages.Add(new Message(usedTool, functionResult)); Debug.Log($"{Role.Tool}: {functionResult}"); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Debug.Log(result); + response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => + { + Assert.IsNotNull(partialResponse); + Assert.NotNull(partialResponse.Choices); + Assert.NotZero(partialResponse.Choices.Count); + }); + Assert.IsNotNull(response); } [Test] - public async Task Test_08_GetChatToolCompletion_Streaming() + public async Task Test_02_03_ChatCompletion_Multiple_Tools_Streaming() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful weather assistant."), - new Message(Role.User, "What's the weather like today?"), + new Message(Role.User, "What's the weather like today in San Diego and LA?"), }; - foreach (var message in messages) - { - Debug.Log($"{message.Role}: {message.Content}"); - } - var tools = new List { new Function( @@ -517,77 +328,43 @@ public async Task Test_08_GetChatToolCompletion_Streaming() ["unit"] = new JObject { ["type"] = "string", - ["enum"] = new JArray {"celsius", "fahrenheit"} + ["enum"] = new JArray { "celsius", "fahrenheit" } } }, ["required"] = new JArray { "location", "unit" } }) }; - var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - var result = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => + var chatRequest = new ChatRequest(messages, model: "gpt-4-1106-preview", tools: tools, toolChoice: "auto"); + var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); Assert.NotNull(partialResponse.Choices); Assert.NotZero(partialResponse.Choices.Count); }); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); - var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); - messages.Add(locationMessage); - Debug.Log($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - result = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => - { - Assert.IsNotNull(partialResponse); - Assert.NotNull(partialResponse.Choices); - Assert.NotZero(partialResponse.Choices.Count); - }); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); + messages.Add(response.FirstChoice.Message); - if (!string.IsNullOrEmpty(result.FirstChoice.Message.ToString())) - { - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + var toolCalls = response.FirstChoice.Message.ToolCalls; - var unitMessage = new Message(Role.User, "celsius"); - messages.Add(unitMessage); - Debug.Log($"{unitMessage.Role}: {unitMessage.Content}"); - chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - result = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => - { - Assert.IsNotNull(partialResponse); - Assert.NotNull(partialResponse.Choices); - Assert.NotZero(partialResponse.Choices.Count); - }); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); + Assert.NotNull(toolCalls); + Assert.AreEqual(2, toolCalls.Count); + + foreach (var toolCall in toolCalls) + { + messages.Add(new Message(toolCall, "Sunny!")); } - Assert.IsTrue(result.FirstChoice.FinishReason == "tool_calls"); - var usedTool = result.FirstChoice.Message.ToolCalls[0]; - Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Debug.Log($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); - Debug.Log($"{usedTool.Function.Arguments}"); + chatRequest = new ChatRequest(messages, model: "gpt-4-1106-preview", tools: tools, toolChoice: "auto"); + response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); - var functionArgs = JsonConvert.DeserializeObject(usedTool.Function.Arguments.ToString()); - var functionResult = WeatherService.GetCurrentWeather(functionArgs); - Assert.IsNotNull(functionResult); - messages.Add(new Message(usedTool, functionResult)); - Debug.Log($"{Role.Tool}: {functionResult}"); + Assert.IsNotNull(response); } [Test] - public async Task Test_09_GetChatToolForceCompletion() + public async Task Test_02_04_GetChatToolForceCompletion() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful weather assistant."), @@ -623,15 +400,14 @@ public async Task Test_09_GetChatToolForceCompletion() ["required"] = new JArray { "location", "unit" } }) }; - var chatRequest = new ChatRequest(messages, tools: tools); - var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); + var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); + messages.Add(response.FirstChoice.Message); - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); messages.Add(locationMessage); @@ -640,19 +416,20 @@ public async Task Test_09_GetChatToolForceCompletion() messages, tools: tools, toolChoice: nameof(WeatherService.GetCurrentWeather)); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); + response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Assert.IsTrue(result.Choices.Count == 1); - messages.Add(result.FirstChoice.Message); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Assert.IsTrue(response.Choices.Count == 1); + messages.Add(response.FirstChoice.Message); - var usedTool = result.FirstChoice.Message.ToolCalls[0]; - Assert.IsTrue(result.FirstChoice.FinishReason == "stop"); + Assert.IsTrue(response.FirstChoice.FinishReason == "stop"); + var usedTool = response.FirstChoice.Message.ToolCalls[0]; + Assert.IsNotNull(usedTool); Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Debug.Log($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); Debug.Log($"{usedTool.Function.Arguments}"); - var functionArgs = JsonConvert.DeserializeObject(result.FirstChoice.Message.ToolCalls[0].Function.Arguments.ToString()); + var functionArgs = JsonConvert.DeserializeObject(usedTool.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); Assert.IsNotNull(functionResult); messages.Add(new Message(usedTool, functionResult)); @@ -660,59 +437,56 @@ public async Task Test_09_GetChatToolForceCompletion() } [Test] - public async Task Test_10_GetChatVision() + public async Task Test_03_01_GetChatVision() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), - new Content(ContentType.ImageUrl, "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg") + "What's in this image?", + new ImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", ImageDetail.Low) }) }; var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview"); - var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}"); - result.GetUsage(); + var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishDetails}"); + response.GetUsage(); } [Test] - public async Task Test_11_GetChatVisionStreaming() + public async Task Test_03_02_GetChatVisionStreaming() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), - new Content(ContentType.ImageUrl, "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg") + "What's in this image?", + new ImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", ImageDetail.Low) }) }; var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview"); - var result = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => + var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); Assert.NotNull(partialResponse.Choices); Assert.NotZero(partialResponse.Choices.Count); }); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}"); - result.GetUsage(); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishDetails}"); + response.GetUsage(); } [Test] - public async Task Test_12_GetChatVision_Texture() + public async Task Test_03_03_GetChatVision_Texture() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ChatEndpoint); + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var imageAssetPath = AssetDatabase.GUIDToAssetPath("230fd778637d3d84d81355c8c13b1999"); var image = AssetDatabase.LoadAssetAtPath(imageAssetPath); var messages = new List @@ -720,17 +494,16 @@ public async Task Test_12_GetChatVision_Texture() new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), - new Content(image) + "What's in this image?", + image }) }; var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview"); - var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); - Assert.IsNotNull(result); - Assert.IsNotNull(result.Choices); - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}"); - result.GetUsage(); + var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Choices); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishDetails}"); + response.GetUsage(); } - } } diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_04_Edits.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_04_Edits.cs index f2fb027d..aa73535e 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_04_Edits.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_04_Edits.cs @@ -1,23 +1,21 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. -using System; using NUnit.Framework; using OpenAI.Edits; +using System; using System.Threading.Tasks; using UnityEngine; namespace OpenAI.Tests { [Obsolete] - internal class TestFixture_04_Edits + internal class TestFixture_04_Edits : AbstractTestFixture { - [Test] public async Task Test_1_GetBasicEdit() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.EditsEndpoint); + Assert.IsNotNull(OpenAIClient.EditsEndpoint); var request = new EditRequest("What day of the wek is it?", "Fix the spelling mistakes"); - var result = await api.EditsEndpoint.CreateEditAsync(request); + var result = await OpenAIClient.EditsEndpoint.CreateEditAsync(request); Assert.IsNotNull(result); Assert.NotNull(result.Choices); Assert.NotZero(result.Choices.Count); diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs index 86d77d51..b1e16d62 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_05_Images.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using OpenAI.Images; using OpenAI.Models; -using System; using System.IO; using System.Threading.Tasks; using UnityEditor; @@ -11,19 +10,19 @@ namespace OpenAI.Tests { - internal class TestFixture_05_Images + internal class TestFixture_05_Images : AbstractTestFixture { [Test] - public async Task Test_1_GenerateImages() + public async Task Test_01_01_GenerateImages() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_3); - var results = await api.ImagesEndPoint.GenerateImageAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + var imageResults = await OpenAIClient.ImagesEndPoint.GenerateImageAsync(request); - foreach (var (path, texture) in results) + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); + + foreach (var (path, texture) in imageResults) { Debug.Log(path); Assert.IsNotNull(texture); @@ -31,37 +30,36 @@ public async Task Test_1_GenerateImages() } [Test] - public async Task Test_2_GenerateImages_B64_Json() + public async Task Test_01_02_GenerateImages_B64_Json() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_2, responseFormat: ResponseFormat.B64_Json); - var results = await api.ImagesEndPoint.GenerateImageAsync(request); + var imageResults = await OpenAIClient.ImagesEndPoint.GenerateImageAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); - foreach (var result in results) + foreach (var image in imageResults) { - Console.WriteLine(result); + Assert.IsNotNull(image); + Debug.Log(image); } } [Test] - public async Task Test_3_CreateImageEdit_Path() + public async Task Test_02_01_CreateImageEdit_Path() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var imageAssetPath = AssetDatabase.GUIDToAssetPath("230fd778637d3d84d81355c8c13b1999"); var maskAssetPath = AssetDatabase.GUIDToAssetPath("0be6be2fad590cc47930495d2ca37dd6"); var request = new ImageEditRequest(Path.GetFullPath(imageAssetPath), Path.GetFullPath(maskAssetPath), "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small); - var results = await api.ImagesEndPoint.CreateImageEditAsync(request); + var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); - foreach (var (path, texture) in results) + foreach (var (path, texture) in imageResults) { Debug.Log(path); Assert.IsNotNull(texture); @@ -69,16 +67,15 @@ public async Task Test_3_CreateImageEdit_Path() } [Test] - public async Task Test_4_CreateImageEdit_Texture() + public async Task Test_02_03_CreateImageEdit_Texture() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var imageAssetPath = AssetDatabase.GUIDToAssetPath("230fd778637d3d84d81355c8c13b1999"); var image = AssetDatabase.LoadAssetAtPath(imageAssetPath); var maskAssetPath = AssetDatabase.GUIDToAssetPath("0be6be2fad590cc47930495d2ca37dd6"); var mask = AssetDatabase.LoadAssetAtPath(maskAssetPath); var request = new ImageEditRequest(image, mask, "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small); - var results = await api.ImagesEndPoint.CreateImageEditAsync(request); + var results = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request); Assert.IsNotNull(results); Assert.NotZero(results.Count); @@ -91,21 +88,20 @@ public async Task Test_4_CreateImageEdit_Texture() } [Test] - public async Task Test_5_CreateImageEdit_Texture_B64_Json() + public async Task Test_02_04_CreateImageEdit_Texture_B64_Json() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var imageAssetPath = AssetDatabase.GUIDToAssetPath("230fd778637d3d84d81355c8c13b1999"); var image = AssetDatabase.LoadAssetAtPath(imageAssetPath); var maskAssetPath = AssetDatabase.GUIDToAssetPath("0be6be2fad590cc47930495d2ca37dd6"); var mask = AssetDatabase.LoadAssetAtPath(maskAssetPath); var request = new ImageEditRequest(image, mask, "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small, responseFormat: ResponseFormat.B64_Json); - var results = await api.ImagesEndPoint.CreateImageEditAsync(request); + var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); - foreach (var (path, texture) in results) + foreach (var (path, texture) in imageResults) { Debug.Log(path); Assert.IsNotNull(texture); @@ -113,19 +109,18 @@ public async Task Test_5_CreateImageEdit_Texture_B64_Json() } [Test] - public async Task Test_6_CreateImageEdit_MaskAsTransparency() + public async Task Test_02_05_CreateImageEdit_MaskAsTransparency() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var maskAssetPath = AssetDatabase.GUIDToAssetPath("0be6be2fad590cc47930495d2ca37dd6"); var mask = AssetDatabase.LoadAssetAtPath(maskAssetPath); var request = new ImageEditRequest(mask, null, "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small); - var results = await api.ImagesEndPoint.CreateImageEditAsync(request); + var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageEditAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); - foreach (var (path, texture) in results) + foreach (var (path, texture) in imageResults) { Debug.Log(path); Assert.IsNotNull(texture); @@ -133,18 +128,17 @@ public async Task Test_6_CreateImageEdit_MaskAsTransparency() } [Test] - public async Task Test_7_CreateImageVariation_Path() + public async Task Test_03_01_CreateImageVariation_Path() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var imageAssetPath = AssetDatabase.GUIDToAssetPath("230fd778637d3d84d81355c8c13b1999"); var request = new ImageVariationRequest(Path.GetFullPath(imageAssetPath), size: ImageSize.Small); - var results = await api.ImagesEndPoint.CreateImageVariationAsync(request); + var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageVariationAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); - foreach (var (path, texture) in results) + foreach (var (path, texture) in imageResults) { Debug.Log(path); Assert.IsNotNull(texture); @@ -152,19 +146,18 @@ public async Task Test_7_CreateImageVariation_Path() } [Test] - public async Task Test_8_CreateImageVariation_Texture() + public async Task Test_03_02_CreateImageVariation_Texture() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var imageAssetPath = AssetDatabase.GUIDToAssetPath("230fd778637d3d84d81355c8c13b1999"); var image = AssetDatabase.LoadAssetAtPath(imageAssetPath); var request = new ImageVariationRequest(image, size: ImageSize.Small); - var results = await api.ImagesEndPoint.CreateImageVariationAsync(request); + var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageVariationAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); - foreach (var (path, texture) in results) + foreach (var (path, texture) in imageResults) { Debug.Log(path); Assert.IsNotNull(texture); @@ -172,19 +165,18 @@ public async Task Test_8_CreateImageVariation_Texture() } [Test] - public async Task Test_9_CreateImageVariation_Texture_B64_Json() + public async Task Test_03_04_CreateImageVariation_Texture_B64_Json() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ImagesEndPoint); + Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var imageAssetPath = AssetDatabase.GUIDToAssetPath("230fd778637d3d84d81355c8c13b1999"); var image = AssetDatabase.LoadAssetAtPath(imageAssetPath); var request = new ImageVariationRequest(image, size: ImageSize.Small, responseFormat: ResponseFormat.B64_Json); - var results = await api.ImagesEndPoint.CreateImageVariationAsync(request); + var imageResults = await OpenAIClient.ImagesEndPoint.CreateImageVariationAsync(request); - Assert.IsNotNull(results); - Assert.NotZero(results.Count); + Assert.IsNotNull(imageResults); + Assert.NotZero(imageResults.Count); - foreach (var (path, texture) in results) + foreach (var (path, texture) in imageResults) { Debug.Log(path); Assert.IsNotNull(texture); diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_06_Embeddings.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_06_Embeddings.cs index b1eeaec0..0255a989 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_06_Embeddings.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_06_Embeddings.cs @@ -5,31 +5,29 @@ namespace OpenAI.Tests { - internal class TestFixture_06_Embeddings + internal class TestFixture_06_Embeddings : AbstractTestFixture { [Test] public async Task Test_1_CreateEmbedding() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.EmbeddingsEndpoint); - var result = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter..."); - Assert.IsNotNull(result); - Assert.IsNotEmpty(result.Data); + Assert.IsNotNull(OpenAIClient.EmbeddingsEndpoint); + var embedding = await OpenAIClient.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter..."); + Assert.IsNotNull(embedding); + Assert.IsNotEmpty(embedding.Data); } [Test] public async Task Test_2_CreateEmbeddingsWithMultipleInputs() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.EmbeddingsEndpoint); + Assert.IsNotNull(OpenAIClient.EmbeddingsEndpoint); var embeddings = new[] { "The food was delicious and the waiter...", "The food was terrible and the waiter..." }; - var result = await api.EmbeddingsEndpoint.CreateEmbeddingAsync(embeddings); - Assert.IsNotNull(result); - Assert.AreEqual(result.Data.Count, 2); + var embedding = await OpenAIClient.EmbeddingsEndpoint.CreateEmbeddingAsync(embeddings); + Assert.IsNotNull(embedding); + Assert.AreEqual(embedding.Data.Count, 2); } } } diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_07_Audio.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_07_Audio.cs index 92fc960e..bc0877ea 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_07_Audio.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_07_Audio.cs @@ -1,8 +1,8 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. -using System; using NUnit.Framework; using OpenAI.Audio; +using System; using System.IO; using System.Threading.Tasks; using UnityEditor; @@ -10,54 +10,50 @@ namespace OpenAI.Tests { - internal class TestFixture_07_Audio + internal class TestFixture_07_Audio : AbstractTestFixture { [Test] - public async Task Test_1_Transcription_AudioClip() + public async Task Test_01_01_Transcription_Path() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.AudioEndpoint); + Assert.IsNotNull(OpenAIClient.AudioEndpoint); var audioPath = AssetDatabase.GUIDToAssetPath("259eaa73cab84284eac307d3134c3ade"); - var audioClip = AssetDatabase.LoadAssetAtPath(audioPath); - using var request = new AudioTranscriptionRequest(audioClip, temperature: 0.1f, language: "en"); - var result = await api.AudioEndpoint.CreateTranscriptionAsync(request); + using var request = new AudioTranscriptionRequest(Path.GetFullPath(audioPath), temperature: 0.1f, language: "en"); + var result = await OpenAIClient.AudioEndpoint.CreateTranscriptionAsync(request); Assert.IsNotNull(result); Debug.Log(result); } [Test] - public async Task Test_1_Transcription_Path() + public async Task Test_01_02_Transcription_AudioClip() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.AudioEndpoint); + Assert.IsNotNull(OpenAIClient.AudioEndpoint); var audioPath = AssetDatabase.GUIDToAssetPath("259eaa73cab84284eac307d3134c3ade"); - using var request = new AudioTranscriptionRequest(Path.GetFullPath(audioPath), temperature: 0.1f, language: "en"); - var result = await api.AudioEndpoint.CreateTranscriptionAsync(request); + var audioClip = AssetDatabase.LoadAssetAtPath(audioPath); + using var request = new AudioTranscriptionRequest(audioClip, temperature: 0.1f, language: "en"); + var result = await OpenAIClient.AudioEndpoint.CreateTranscriptionAsync(request); Assert.IsNotNull(result); Debug.Log(result); } [Test] - public async Task Test_2_Translation_AudioClip() + public async Task Test_02_01_Translation_Path() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.AudioEndpoint); + Assert.IsNotNull(OpenAIClient.AudioEndpoint); var audioPath = AssetDatabase.GUIDToAssetPath("3ab176222366dc241894506c315c6fa4"); - var audioClip = AssetDatabase.LoadAssetAtPath(audioPath); - using var request = new AudioTranslationRequest(audioClip); - var result = await api.AudioEndpoint.CreateTranslationAsync(request); + using var request = new AudioTranslationRequest(Path.GetFullPath(audioPath)); + var result = await OpenAIClient.AudioEndpoint.CreateTranslationAsync(request); Assert.IsNotNull(result); Debug.Log(result); } [Test] - public async Task Test_2_Translation_Path() + public async Task Test_02_02_Translation_AudioClip() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.AudioEndpoint); + Assert.IsNotNull(OpenAIClient.AudioEndpoint); var audioPath = AssetDatabase.GUIDToAssetPath("3ab176222366dc241894506c315c6fa4"); - using var request = new AudioTranslationRequest(Path.GetFullPath(audioPath)); - var result = await api.AudioEndpoint.CreateTranslationAsync(request); + var audioClip = AssetDatabase.LoadAssetAtPath(audioPath); + using var request = new AudioTranslationRequest(audioClip); + var result = await OpenAIClient.AudioEndpoint.CreateTranslationAsync(request); Assert.IsNotNull(result); Debug.Log(result); } @@ -65,10 +61,9 @@ public async Task Test_2_Translation_Path() [Test] public async Task Test_3_Speech() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.AudioEndpoint); + Assert.IsNotNull(OpenAIClient.AudioEndpoint); var request = new SpeechRequest("Hello world!"); - var (path, clip) = await api.AudioEndpoint.CreateSpeechAsync(request); + var (path, clip) = await OpenAIClient.AudioEndpoint.CreateSpeechAsync(request); Debug.Log(path); Assert.IsNotNull(clip); } diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_08_Files.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_08_Files.cs index 381eb9aa..487f1ae0 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_08_Files.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_08_Files.cs @@ -9,22 +9,19 @@ namespace OpenAI.Tests { - internal class TestFixture_08_Files + internal class TestFixture_08_Files : AbstractTestFixture { [Test] public async Task Test_01_UploadFile() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FilesEndpoint); + Assert.IsNotNull(OpenAIClient.FilesEndpoint); var testData = new Conversation(new List { new Message(Role.Assistant, "I'm a learning language model") }); await File.WriteAllTextAsync("test.jsonl", testData); Assert.IsTrue(File.Exists("test.jsonl")); - var result = await api.FilesEndpoint.UploadFileAsync("test.jsonl", "fine-tune"); - + var result = await OpenAIClient.FilesEndpoint.UploadFileAsync("test.jsonl", "fine-tune"); Assert.IsNotNull(result); Assert.IsTrue(result.FileName == "test.jsonl"); Debug.Log($"{result.Id} -> {result.Object}"); - File.Delete("test.jsonl"); Assert.IsFalse(File.Exists("test.jsonl")); } @@ -32,19 +29,16 @@ public async Task Test_01_UploadFile() [Test] public async Task Test_02_ListFiles() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FilesEndpoint); + Assert.IsNotNull(OpenAIClient.FilesEndpoint); + var fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync(); - var result = await api.FilesEndpoint.ListFilesAsync(); - - Assert.IsNotNull(result); - Assert.IsNotEmpty(result); + Assert.IsNotNull(fileList); + Assert.IsNotEmpty(fileList); - foreach (var file in result) + foreach (var file in fileList) { - var fileInfo = await api.FilesEndpoint.GetFileInfoAsync(file); + var fileInfo = await OpenAIClient.FilesEndpoint.GetFileInfoAsync(file); Assert.IsNotNull(fileInfo); - Debug.Log($"{fileInfo.Id} -> {fileInfo.Object}: {fileInfo.FileName} | {fileInfo.Size} bytes"); } } @@ -52,16 +46,14 @@ public async Task Test_02_ListFiles() [Test] public async Task Test_03_DownloadFile() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FilesEndpoint); - - var files = await api.FilesEndpoint.ListFilesAsync(); + Assert.IsNotNull(OpenAIClient.FilesEndpoint); + var fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync(); - Assert.IsNotNull(files); - Assert.IsNotEmpty(files); + Assert.IsNotNull(fileList); + Assert.IsNotEmpty(fileList); - var testFileData = files[0]; - var result = await api.FilesEndpoint.DownloadFileAsync(testFileData); + var testFileData = fileList[0]; + var result = await OpenAIClient.FilesEndpoint.DownloadFileAsync(testFileData); Assert.IsNotNull(result); Debug.Log(result); @@ -74,23 +66,21 @@ public async Task Test_03_DownloadFile() [Test] public async Task Test_04_DeleteFiles() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FilesEndpoint); - - var files = await api.FilesEndpoint.ListFilesAsync(); - Assert.IsNotNull(files); - Assert.IsNotEmpty(files); + Assert.IsNotNull(OpenAIClient.FilesEndpoint); + var fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync(); + Assert.IsNotNull(fileList); + Assert.IsNotEmpty(fileList); - foreach (var file in files) + foreach (var file in fileList) { - var result = await api.FilesEndpoint.DeleteFileAsync(file); - Assert.IsTrue(result); + var isDeleted = await OpenAIClient.FilesEndpoint.DeleteFileAsync(file); + Assert.IsTrue(isDeleted); Debug.Log($"{file.Id} -> deleted"); } - files = await api.FilesEndpoint.ListFilesAsync(); - Assert.IsNotNull(files); - Assert.IsEmpty(files); + fileList = await OpenAIClient.FilesEndpoint.ListFilesAsync(); + Assert.IsNotNull(fileList); + Assert.IsEmpty(fileList); } } } diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_09_FineTuning.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_09_FineTuning.cs index 1de89834..5d3ffe74 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_09_FineTuning.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_09_FineTuning.cs @@ -14,12 +14,11 @@ namespace OpenAI.Tests { - internal class TestFixture_09_FineTuning + internal class TestFixture_09_FineTuning : AbstractTestFixture { - private async Task CreateTestTrainingDataAsync(OpenAIClient api) + private async Task CreateTestTrainingDataAsync() { - var fineTuningTrainingData = ScriptableObject.CreateInstance(); - fineTuningTrainingData.ConversationTrainingData = new List + var conversations = new List { new Conversation(new List { @@ -82,11 +81,10 @@ private async Task CreateTestTrainingDataAsync(OpenAIClient api) new Message(Role.Assistant, "Around 384,400 kilometers. Give or take a few, like that really matters.") }) }; - const string localTrainingDataPath = "fineTunesTestTrainingData.jsonl"; - await File.WriteAllLinesAsync(localTrainingDataPath, fineTuningTrainingData.ConversationTrainingToJsonl()); + await File.WriteAllLinesAsync(localTrainingDataPath, conversations.Select(conversation => conversation.ToString())); - var fileData = await api.FilesEndpoint.UploadFileAsync(localTrainingDataPath, "fine-tune"); + var fileData = await OpenAIClient.FilesEndpoint.UploadFileAsync(localTrainingDataPath, "fine-tune"); File.Delete(localTrainingDataPath); Assert.IsFalse(File.Exists(localTrainingDataPath)); return fileData; @@ -95,14 +93,11 @@ private async Task CreateTestTrainingDataAsync(OpenAIClient api) [Test] public async Task Test_01_CreateFineTuneJob() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FineTuningEndpoint); - - var fileData = await CreateTestTrainingDataAsync(api); + Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); + var fileData = await CreateTestTrainingDataAsync(); Assert.IsNotNull(fileData); var request = new CreateFineTuneJobRequest(Model.GPT3_5_Turbo, fileData); - api.FineTuningEndpoint.EnableDebug = true; - var job = await api.FineTuningEndpoint.CreateJobAsync(request); + var job = await OpenAIClient.FineTuningEndpoint.CreateJobAsync(request); Assert.IsNotNull(job); Debug.Log($"Started {job.Id} | Status: {job.Status}"); @@ -111,15 +106,15 @@ public async Task Test_01_CreateFineTuneJob() [Test] public async Task Test_02_ListFineTuneJobs() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FineTuningEndpoint); - - var list = await api.FineTuningEndpoint.ListJobsAsync(); - Assert.IsNotNull(list); - Assert.IsNotEmpty(list.Jobs); + Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); + var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); + Assert.IsNotNull(jobList); + Assert.IsNotEmpty(jobList.Items); - foreach (var job in list.Jobs.OrderByDescending(job => job.CreatedAt)) + foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt)) { + Assert.IsNotNull(job); + Assert.IsNotNull(job.Client); Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}"); } } @@ -127,47 +122,41 @@ public async Task Test_02_ListFineTuneJobs() [Test] public async Task Test_03_RetrieveFineTuneJobInfo() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FineTuningEndpoint); + Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); + var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); + Assert.IsNotNull(jobList); + Assert.IsNotEmpty(jobList.Items); - var list = await api.FineTuningEndpoint.ListJobsAsync(); - Assert.IsNotNull(list); - Assert.IsNotEmpty(list.Jobs); - - foreach (var job in list.Jobs.OrderByDescending(job => job.CreatedAt)) + foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt)) { - var result = await api.FineTuningEndpoint.GetJobInfoAsync(job); - Assert.IsNotNull(result); - Debug.Log($"{result.Id} -> {result.Status}"); + var response = await OpenAIClient.FineTuningEndpoint.GetJobInfoAsync(job); + Assert.IsNotNull(response); + Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}"); } } [Test] public async Task Test_04_ListFineTuneEvents() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FineTuningEndpoint); - - var list = await api.FineTuningEndpoint.ListJobsAsync(); - Assert.IsNotNull(list); - Assert.IsNotEmpty(list.Jobs); + Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); + var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); + Assert.IsNotNull(jobList); + Assert.IsNotEmpty(jobList.Items); - foreach (var job in list.Jobs) + foreach (var job in jobList.Items) { - if (job.Status == JobStatus.Cancelled) - { - continue; - } + if (job.Status == JobStatus.Cancelled) { continue; } - var eventList = await api.FineTuningEndpoint.ListJobEventsAsync(job); + var eventList = await OpenAIClient.FineTuningEndpoint.ListJobEventsAsync(job); Assert.IsNotNull(eventList); - Assert.IsNotEmpty(eventList.Events); - - Debug.Log($"{job.Id} -> status: {job.Status} | event count: {eventList.Events.Count} | date: {job.CreatedAt}"); + Assert.IsNotEmpty(eventList.Items); + Debug.Log($"{job.Id} -> status: {job.Status} | event count: {eventList.Items.Count}"); - foreach (var @event in eventList.Events.OrderByDescending(@event => @event.CreatedAt)) + foreach (var @event in eventList.Items.OrderByDescending(@event => @event.CreatedAt)) { - Debug.Log($" {@event.CreatedAt} [{@event.Level}] {@event.Message.Replace("\n", " ")}"); + Assert.IsNotNull(@event); + Assert.IsNotNull(@event.Client); + Debug.Log($" {@event.CreatedAt} [{@event.Level}] {@event.Message}"); } } } @@ -175,22 +164,20 @@ public async Task Test_04_ListFineTuneEvents() [Test] public async Task Test_05_CancelFineTuneJob() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.FineTuningEndpoint); + Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); + var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); + Assert.IsNotNull(jobList); + Assert.IsNotEmpty(jobList.Items); - var list = await api.FineTuningEndpoint.ListJobsAsync(); - Assert.IsNotNull(list); - Assert.IsNotEmpty(list.Jobs); - - foreach (var job in list.Jobs) + foreach (var job in jobList.Items) { if (job.Status is > JobStatus.NotStarted and < JobStatus.Succeeded) { - var result = await api.FineTuningEndpoint.CancelJobAsync(job); + var result = await OpenAIClient.FineTuningEndpoint.CancelJobAsync(job); Assert.IsNotNull(result); Assert.IsTrue(result); Debug.Log($"{job.Id} -> cancelled"); - result = await api.FilesEndpoint.DeleteFileAsync(job.TrainingFile); + result = await OpenAIClient.FilesEndpoint.DeleteFileAsync(job.TrainingFile); Assert.IsTrue(result); Debug.Log($"{job.TrainingFile} -> deleted"); } @@ -198,12 +185,10 @@ public async Task Test_05_CancelFineTuneJob() } [Test] - public async Task Test_07_DeleteFineTunedModel() + public async Task Test_06_DeleteFineTunedModel() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - Assert.IsNotNull(api.ModelsEndpoint); - - var models = await api.ModelsEndpoint.GetModelsAsync(); + Assert.IsNotNull(OpenAIClient.ModelsEndpoint); + var models = await OpenAIClient.ModelsEndpoint.GetModelsAsync(); Assert.IsNotNull(models); Assert.IsNotEmpty(models); @@ -218,7 +203,7 @@ public async Task Test_07_DeleteFineTunedModel() } Debug.Log(model); - var result = await api.ModelsEndpoint.DeleteFineTuneModelAsync(model); + var result = await OpenAIClient.ModelsEndpoint.DeleteFineTuneModelAsync(model); Assert.IsNotNull(result); Assert.IsTrue(result); Debug.Log($"{model.Id} -> deleted"); diff --git a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_10_Moderations.cs b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_10_Moderations.cs index e76a5575..dfea0366 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/TestFixture_10_Moderations.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/TestFixture_10_Moderations.cs @@ -7,18 +7,31 @@ namespace OpenAI.Tests { - internal class TestFixture_10_Moderations + internal class TestFixture_10_Moderations : AbstractTestFixture { [Test] - public async Task Test_1_Moderate() + public async Task Test_01_Moderate() { - var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); - var violationResponse = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them."); - Assert.IsTrue(violationResponse); + Assert.IsNotNull(OpenAIClient.ModerationsEndpoint); + var isViolation = await OpenAIClient.ModerationsEndpoint.GetModerationAsync("I want to kill them."); + Assert.IsTrue(isViolation); + } - var response = await api.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest("I love you")); + [Test] + public async Task Test_02_Moderate_Scores() + { + Assert.IsNotNull(OpenAIClient.ModerationsEndpoint); + var response = await OpenAIClient.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest("I love you")); Assert.IsNotNull(response); Debug.Log(response.Results?[0]?.Scores?.ToString()); } + + [Test] + public async Task Test_03_Moderation_Chunked() + { + Assert.IsNotNull(OpenAIClient.ModerationsEndpoint); + var isViolation = await OpenAIClient.ModerationsEndpoint.GetModerationChunkedAsync("I don't want to kill them. I want to kill them. I want to kill them.", chunkSize: "I don't want to kill them.".Length, chunkOverlap: 4); + Assert.IsTrue(isViolation); + } } } diff --git a/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs b/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs new file mode 100644 index 00000000..4edfa19b --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs @@ -0,0 +1,15 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; + +namespace OpenAI.Tests.Weather +{ + internal class WeatherArgs + { + [JsonProperty("location")] + public string Location { get; set; } + + [JsonProperty("unit")] + public string Unit { get; set; } + } +} diff --git a/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs.meta b/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs.meta new file mode 100644 index 00000000..45d146d8 --- /dev/null +++ b/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9bd909a8d32c962438458fb8d89611d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 84a7eb8fc6eba7540bf56cea8e12249c, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherService.cs b/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherService.cs index 5e7bf339..96fd671d 100644 --- a/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherService.cs +++ b/OpenAI/Packages/com.openai.unity/Tests/Weather/WeatherService.cs @@ -1,7 +1,5 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. -using Newtonsoft.Json; - namespace OpenAI.Tests.Weather { internal class WeatherService @@ -11,13 +9,4 @@ public static string GetCurrentWeather(WeatherArgs weatherArgs) return $"The current weather in {weatherArgs.Location} is 20 {weatherArgs.Unit}"; } } - - internal class WeatherArgs - { - [JsonProperty("location")] - public string Location { get; set; } - - [JsonProperty("unit")] - public string Unit { get; set; } - } } diff --git a/OpenAI/Packages/com.openai.unity/package.json b/OpenAI/Packages/com.openai.unity/package.json index 270a090e..902202d5 100644 --- a/OpenAI/Packages/com.openai.unity/package.json +++ b/OpenAI/Packages/com.openai.unity/package.json @@ -3,7 +3,7 @@ "displayName": "OpenAI", "description": "A OpenAI package for the Unity Game Engine to use GPT-4, GPT-3.5, GPT-3 and Dall-E though their RESTful API (currently in beta).\n\nIndependently developed, this is not an official library and I am not affiliated with OpenAI.\n\nAn OpenAI API account is required.", "keywords": [], - "version": "5.2.3", + "version": "6.0.0", "unity": "2021.3", "documentationUrl": "https://github.com/RageAgainstThePixel/com.openai.unity#documentation", "changelogUrl": "https://github.com/RageAgainstThePixel/com.openai.unity/releases", @@ -17,7 +17,7 @@ "url": "https://github.com/StephenHodgson" }, "dependencies": { - "com.utilities.rest": "2.2.5", + "com.utilities.rest": "2.3.1", "com.utilities.encoder.wav": "1.0.8", "com.utilities.encoder.ogg": "3.0.12" }, diff --git a/OpenAI/ProjectSettings/ProjectVersion.txt b/OpenAI/ProjectSettings/ProjectVersion.txt index e4eac159..da671891 100644 --- a/OpenAI/ProjectSettings/ProjectVersion.txt +++ b/OpenAI/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.12f1 -m_EditorVersionWithRevision: 2022.3.12f1 (4fe6e059c7ef) +m_EditorVersion: 2022.3.14f1 +m_EditorVersionWithRevision: 2022.3.14f1 (eff2de9070d8) diff --git a/README.md b/README.md index 4fd4cc50..5b22d88d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ The recommended installation method is though the unity package manager and [Ope ### Table of Contents -- [Authentication](#authentication) +- [Authentication](#authentication) :construction: - [Azure OpenAI](#azure-openai) - [Azure Active Directory Authentication](#azure-active-directory-authentication) - [OpenAI API Proxy](#openai-api-proxy) @@ -58,46 +58,49 @@ The recommended installation method is though the unity package manager and [Ope - [List Models](#list-models) - [Retrieve Models](#retrieve-model) - [Delete Fine Tuned Model](#delete-fine-tuned-model) -- [Completions](#completions) - - [Streaming](#completion-streaming) - [Chat](#chat) - [Chat Completions](#chat-completions) - [Streaming](#chat-streaming) - [Tools](#chat-tools) :new: - [Vision](#chat-vision) :new: -- [Edits](#edits) - - [Create Edit](#create-edit) -- [Embeddings](#embeddings) - - [Create Embedding](#create-embeddings) + - [Json Mode](#chat-json-mode) :new: - [Audio](#audio) - [Create Speech](#create-speech) - [Create Transcription](#create-transcription) - [Create Translation](#create-translation) -- [Images](#images) - - [Create Image](#create-image) - - [Edit Image](#edit-image) - - [Create Image Variation](#create-image-variation) -- [Files](#files) - - [List Files](#list-files) +- [Images](#images) :construction: + - [Create Image](#create-image) :construction: + - [Edit Image](#edit-image) :construction: + - [Create Image Variation](#create-image-variation) :construction: +- [Files](#files) :construction: + - [List Files](#list-files) :construction: - [Upload File](#upload-file) - [Delete File](#delete-file) - - [Retrieve File Info](#retrieve-file-info) + - [Retrieve File](#retrieve-file-info) :construction: - [Download File Content](#download-file-content) -- [Fine Tuning](#fine-tuning) - - [Create Fine Tune Job](#create-fine-tune-job) - - [List Fine Tune Jobs](#list-fine-tune-jobs) - - [Retrieve Fine Tune Job Info](#retrieve-fine-tune-job-info) +- [Fine Tuning](#fine-tuning) :construction: + - [Create Fine Tune Job](#create-fine-tune-job) :construction: + - [List Fine Tune Jobs](#list-fine-tune-jobs) :construction: + - [Retrieve Fine Tune Job Info](#retrieve-fine-tune-job-info) :construction: - [Cancel Fine Tune Job](#cancel-fine-tune-job) - - [List Fine Tune Job Events](#list-fine-tune-job-events) + - [List Fine Tune Job Events](#list-fine-tune-job-events) :construction: +- [Embeddings](#embeddings) + - [Create Embedding](#create-embeddings) +- [Completions](#completions) :construction: + - [Streaming](#completion-streaming) :construction: - [Moderations](#moderations) - [Create Moderation](#create-moderation) +- ~~[Edits](#edits)~~ :warning: Deprecated + - ~~[Create Edit](#create-edit)~~ :warning: Deprecated -### Authentication +### [Authentication](https://platform.openai.com/docs/api-reference/authentication) There are 4 ways to provide your API keys, in order of precedence: -1. [Pass keys directly with constructor](#pass-keys-directly-with-constructor) -2. [Unity Scriptable Object](#unity-scriptable-object) +:warning: We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios. + +1. [Pass keys directly with constructor](#pass-keys-directly-with-constructor) :warning: +2. [Unity Scriptable Object](#unity-scriptable-object) :warning: 3. [Load key from configuration file](#load-key-from-configuration-file) 4. [Use System Environment Variables](#use-system-environment-variables) @@ -105,8 +108,6 @@ You use the `OpenAIAuthentication` when you initialize the API as shown: #### Pass keys directly with constructor -:warning: We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios. - ```csharp var api = new OpenAIClient("sk-apiKey"); ``` @@ -156,13 +157,13 @@ You can also load the configuration file directly with known path by calling sta - Loads the default `.openai` config in the specified directory: ```csharp -var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromDirectory("path/to/your/directory")); +var api = new OpenAIClient(new OpenAIAuthentication().LoadFromDirectory("path/to/your/directory")); ``` - Loads the configuration file from a specific path. File does not need to be named `.openai` as long as it conforms to the json format: ```csharp -var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromPath("path/to/your/file.json")); +var api = new OpenAIClient(new OpenAIAuthentication().LoadFromPath("path/to/your/file.json")); ``` #### Use System Environment Variables @@ -173,10 +174,10 @@ Use your system's environment variables specify an api key and organization to u - Use `OPENAI_ORGANIZATION_ID` to specify an organization. ```csharp -var api = new OpenAIClient(OpenAIAuthentication.Default.LoadFromEnvironment()); +var api = new OpenAIClient(new OpenAIAuthentication().LoadFromEnvironment()); ``` -### [Azure OpenAI](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/) +### [Azure OpenAI](https://learn.microsoft.com/en-us/azure/cognitive-services/openai) You can also choose to use Microsoft's Azure OpenAI deployments as well. @@ -326,38 +327,8 @@ Delete a fine-tuned model. You must have the Owner role in your organization. ```csharp var api = new OpenAIClient(); -var result = await api.ModelsEndpoint.DeleteFineTuneModelAsync("your-fine-tuned-model"); -Assert.IsTrue(result); -``` - -### [Completions](https://platform.openai.com/docs/api-reference/completions) - -Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. - -The Completions API is accessed via `OpenAIClient.CompletionsEndpoint` - -```csharp -var api = new OpenAIClient(); -var result = await api.CompletionsEndpoint.CreateCompletionAsync("One Two Three One Two", temperature: 0.1, model: Model.Davinci); -Debug.Log(result); -``` - -> To get the `CompletionResult` (which is mostly metadata), use its implicit string operator to get the text if all you want is the completion choice. - -#### Completion Streaming - -Streaming allows you to get results are they are generated, which can help your application feel more responsive, especially on slow models like Davinci. - -```csharp -var api = new OpenAIClient(); - -await api.CompletionsEndpoint.StreamCompletionAsync(result => -{ - foreach (var choice in result.Completions) - { - Debug.Log(choice); - } -}, "My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci); +var isDeleted = await api.ModelsEndpoint.DeleteFineTuneModelAsync("your-fine-tuned-model"); +Assert.IsTrue(isDeleted); ``` ### [Chat](https://platform.openai.com/docs/api-reference/chat) @@ -379,12 +350,13 @@ var messages = new List new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."), new Message(Role.User, "Where was it played?"), }; -var chatRequest = new ChatRequest(messages, Model.GPT3_5_Turbo); -var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content}"); +var chatRequest = new ChatRequest(messages, Model.GPT4); +var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +var choice = response.FirstChoice; +Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}"); ``` -##### [Chat Streaming](https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream) +#### [Chat Streaming](https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream) ```csharp var api = new OpenAIClient(); @@ -395,24 +367,16 @@ var messages = new List new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."), new Message(Role.User, "Where was it played?"), }; -var chatRequest = new ChatRequest(messages, Model.GPT3_5_Turbo, number: 2); -await api.ChatEndpoint.StreamCompletionAsync(chatRequest, result => +var chatRequest = new ChatRequest(messages); +var response = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { - foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Delta?.Content))) - { - // Partial response content - Debug.Log(choice.Delta.Content); - } - - foreach (var choice in result.Choices.Where(choice => !string.IsNullOrEmpty(choice.Message?.Content))) - { - // Completed response content - Debug.Log($"{choice.Message.Role}: {choice.Message.Content}"); - } + Console.Write(choice.Delta.ToString()); }); +var choice = response.FirstChoice; +Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}"); ``` -##### [Chat Tools](https://platform.openai.com/docs/guides/function-calling) +#### [Chat Tools](https://platform.openai.com/docs/guides/function-calling) > Only available with the latest 0613 model series! @@ -426,7 +390,7 @@ var messages = new List foreach (var message in messages) { - Debug.Log($"{message.Role}: {message.Content}"); + Debug.Log($"{message.Role}: {message}"); } // Define the tools that the assistant is able to use: @@ -456,32 +420,32 @@ var tools = new List }; var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); -var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); -messages.Add(result.FirstChoice.Message); +var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +messages.Add(response.FirstChoice.Message); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); +Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); messages.Add(locationMessage); Debug.Log($"{locationMessage.Role}: {locationMessage.Content}"); chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); -result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); -messages.Add(result.FirstChoice.Message); +messages.Add(response.FirstChoice.Message); -if (!string.IsNullOrEmpty(result.FirstChoice.Message.Content)) +if (!string.IsNullOrEmpty(response.ToString())) { - Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}"); + Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var unitMessage = new Message(Role.User, "celsius"); messages.Add(unitMessage); Debug.Log($"{unitMessage.Role}: {unitMessage.Content}"); chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); + response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); } -var usedTool = result.FirstChoice.Message.ToolCalls[0]; -Debug.Log($"{result.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}"); +var usedTool = response.FirstChoice.Message.ToolCalls[0]; +Debug.Log($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); Debug.Log($"{usedTool.Function.Arguments}"); var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); @@ -499,11 +463,10 @@ Debug.Log($"{Role.Tool}: {functionResult}"); // Tool: The current weather in Glasgow, Scotland is 20 celsius ``` -##### [Chat Vision](https://platform.openai.com/docs/guides/vision) +#### [Chat Vision](https://platform.openai.com/docs/guides/vision) -:construction: This feature is in beta! - -> Currently, GPT-4 with vision does not support the message.name parameter, functions/tools, nor the response_format parameter. +> :warning: Beta Feature +> Currently, GPT-4 with vision does not support the `message.name` parameter, functions/tools, nor the `response_format` parameter. ```csharp var api = new OpenAIClient(); @@ -512,13 +475,13 @@ var messages = new List new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), - new Content(ContentType.ImageUrl, "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg") + "What's in this image?", + new ImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", ImageDetail.Low) }) }; var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview"); -var result = await apiChatEndpoint.GetCompletionAsync(chatRequest); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}"); +var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); +Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishDetails}"); ``` You can even pass in a `Texture2D`! @@ -530,50 +493,40 @@ var messages = new List new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), - new Content(texture) + "What's in this image?", + texture }) }; var chatRequest = new ChatRequest(messages, model: "gpt-4-vision-preview"); var result = await apiChatEndpoint.GetCompletionAsync(chatRequest); -Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishDetails}"); +Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice} | Finish Reason: {result.FirstChoice.FinishDetails}"); ``` -### [Edits](https://platform.openai.com/docs/api-reference/edits) - -> Deprecated, and soon to be removed. - -Given a prompt and an instruction, the model will return an edited version of the prompt. +#### [Chat Json Mode](https://platform.openai.com/docs/guides/text-generation/json-mode) -The Edits API is accessed via `OpenAIClient.EditsEndpoint` +> :warning: Beta Feature -#### [Create Edit](https://platform.openai.com/docs/api-reference/edits/create) +Important notes: -Creates a new edit for the provided input, instruction, and parameters using the provided input and instruction. +- When using JSON mode, always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don't include an explicit instruction to generate JSON, the model may generate an unending stream of whitespace and the request may run continually until it reaches the token limit. To help ensure you don't forget, the API will throw an error if the string "JSON" does not appear somewhere in the context. +- The JSON in the message the model returns may be partial (i.e. cut off) if `finish_reason` is length, which indicates the generation exceeded max_tokens or the conversation exceeded the token limit. To guard against this, check `finish_reason` before parsing the response. +- JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors. ```csharp -var api = new OpenAIClient(); -var request = new EditRequest("What day of the wek is it?", "Fix the spelling mistakes"); -var result = await api.EditsEndpoint.CreateEditAsync(request); -Debug.Log(result); -``` - -### [Embeddings](https://platform.openai.com/docs/api-reference/embeddings) - -Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. - -Related guide: [Embeddings](https://platform.openai.com/docs/guides/embeddings) - -The Edits API is accessed via `OpenAIClient.EmbeddingsEndpoint` - -#### [Create Embeddings](https://platform.openai.com/docs/api-reference/embeddings/create) +var messages = new List +{ + new Message(Role.System, "You are a helpful assistant designed to output JSON."), + new Message(Role.User, "Who won the world series in 2020?"), +}; +var chatRequest = new ChatRequest(messages, "gpt-4-1106-preview", responseFormat: ChatResponseFormat.Json); +var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); -Creates an embedding vector representing the input text. +foreach (var choice in response.Choices) +{ + Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); +} -```csharp -var api = new OpenAIClient(); -var result = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...", Models.Embedding_Ada_002); -Debug.Log(result); +response.GetUsage(); ``` ### [Audio](https://platform.openai.com/docs/api-reference/audio) @@ -627,8 +580,8 @@ Creates an image given a prompt. ```csharp var api = new OpenAIClient(); -var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_3); -var results = await api.ImagesEndPoint.GenerateImageAsync(request); +var request = new ImageGenerationRequest("A house riding a velociraptor", Models.Model.DallE_3); +var imageResults = await api.ImagesEndPoint.GenerateImageAsync(request); foreach (var (path, texture) in results) { @@ -646,9 +599,9 @@ Creates an edited or extended image given an original image and a prompt. ```csharp var api = new OpenAIClient(); var request = new ImageEditRequest(Path.GetFullPath(imageAssetPath), Path.GetFullPath(maskAssetPath), "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small); -var results = await api.ImagesEndPoint.CreateImageEditAsync(request); +var imageResults = await api.ImagesEndPoint.CreateImageEditAsync(request); -foreach (var (path, texture) in results) +foreach (var (path, texture) in imageResults) { Debug.Log(path); // path == file://path/to/image.png @@ -664,9 +617,9 @@ Creates a variation of a given image. ```csharp var api = new OpenAIClient(); var request = new ImageVariationRequest(imageTexture, size: ImageSize.Small); -var results = await api.ImagesEndPoint.CreateImageVariationAsync(request); +var imageResults = await api.ImagesEndPoint.CreateImageVariationAsync(request); -foreach (var (path, texture) in results) +foreach (var (path, texture) in imageResults) { Debug.Log(path); // path == file://path/to/image.png @@ -703,17 +656,19 @@ Returns a list of files that belong to the user's organization. ```csharp var api = new OpenAIClient(); -var files = await api.FilesEndpoint.ListFilesAsync(); +var fileList = await api.FilesEndpoint.ListFilesAsync(); -foreach (var file in files) +foreach (var file in fileList) { Debug.Log($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes"); } ``` -#### [Upload File](https://platform.openai.com/docs/api-reference/files/upload) +#### [Upload File](https://platform.openai.com/docs/api-reference/files/create) -Upload a file that contains document(s) to be used across various endpoints/features. Currently, the size of all the files uploaded by one organization can be up to 1 GB. Please contact us if you need to increase the storage limit. +Upload a file that can be used across various endpoints. The size of all the files uploaded by one organization can be up to 100 GB. + +The size of individual files can be a maximum of 512 MB. See the Assistants Tools guide to learn more about the types of files supported. The Fine-tuning API only supports .jsonl files. ```csharp var api = new OpenAIClient(); @@ -727,8 +682,8 @@ Delete a file. ```csharp var api = new OpenAIClient(); -var result = await api.FilesEndpoint.DeleteFileAsync(fileData); -Assert.IsTrue(result); +var isDeleted = await api.FilesEndpoint.DeleteFileAsync(fileId); +Assert.IsTrue(isDeleted); ``` #### [Retrieve File Info](https://platform.openai.com/docs/api-reference/files/retrieve) @@ -737,13 +692,13 @@ Returns information about a specific file. ```csharp var api = new OpenAIClient(); -var fileData = await GetFileInfoAsync(fileId); -Debug.Log($"{fileData.Id} -> {fileData.Object}: {fileData.FileName} | {fileData.Size} bytes"); +var file = await GetFileInfoAsync(fileId); +Debug.Log($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes"); ``` #### [Download File Content](https://platform.openai.com/docs/api-reference/files/retrieve-content) -Downloads the specified file. +Downloads the file content to the specified directory. ```csharp var api = new OpenAIClient(); @@ -780,11 +735,11 @@ List your organization's fine-tuning jobs. ```csharp var api = new OpenAIClient(); -var list = await api.FineTuningEndpoint.ListJobsAsync(); +var jobList = await api.FineTuningEndpoint.ListJobsAsync(); -foreach (var job in list.Jobs) +foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt))) { - Debug.Log($"{job.Id} -> {job.Status}"); + Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}"); } ``` @@ -795,7 +750,7 @@ Gets info about the fine-tune job. ```csharp var api = new OpenAIClient(); var job = await api.FineTuningEndpoint.GetJobInfoAsync(fineTuneJob); -Debug.Log($"{job.Id} -> {job.Status}"); +Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}"); ``` #### [Cancel Fine Tune Job](https://platform.openai.com/docs/api-reference/fine-tuning/cancel) @@ -804,8 +759,8 @@ Immediately cancel a fine-tune job. ```csharp var api = new OpenAIClient(); -var result = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob); -Assert.IsTrue(result); +var isCancelled = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob); +Assert.IsTrue(isCancelled); ``` #### [List Fine Tune Job Events](https://platform.openai.com/docs/api-reference/fine-tuning/list-events) @@ -817,12 +772,60 @@ var api = new OpenAIClient(); var eventList = await api.FineTuningEndpoint.ListJobEventsAsync(fineTuneJob); Debug.Log($"{fineTuneJob.Id} -> status: {fineTuneJob.Status} | event count: {eventList.Events.Count}"); -foreach (var @event in eventList.Events.OrderByDescending(@event => @event.CreatedAt)) +foreach (var @event in eventList.Items.OrderByDescending(@event => @event.CreatedAt)) { - Debug.Log($" {@event.CreatedAt} [{@event.Level}] {@event.Message.Replace("\n", " ")}"); + Debug.Log($" {@event.CreatedAt} [{@event.Level}] {@event.Message}"); } ``` +### [Embeddings](https://platform.openai.com/docs/api-reference/embeddings) + +Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + +Related guide: [Embeddings](https://platform.openai.com/docs/guides/embeddings) + +The Edits API is accessed via `OpenAIClient.EmbeddingsEndpoint` + +#### [Create Embeddings](https://platform.openai.com/docs/api-reference/embeddings/create) + +Creates an embedding vector representing the input text. + +```csharp +var api = new OpenAIClient(); +var response = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...", Models.Embedding_Ada_002); +Debug.Log(response); +``` + +### [Completions](https://platform.openai.com/docs/api-reference/completions) + +Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + +The Completions API is accessed via `OpenAIClient.CompletionsEndpoint` + +```csharp +var api = new OpenAIClient(); +var response = await api.CompletionsEndpoint.CreateCompletionAsync("One Two Three One Two", temperature: 0.1, model: Model.Davinci); +Debug.Log(response); +``` + +> To get the `CompletionResponse` (which is mostly metadata), use its implicit string operator to get the text if all you want is the completion choice. + +#### Completion Streaming + +Streaming allows you to get results are they are generated, which can help your application feel more responsive, especially on slow models like Davinci. + +```csharp +var api = new OpenAIClient(); + +await api.CompletionsEndpoint.StreamCompletionAsync(response => +{ + foreach (var choice in response.Completions) + { + Debug.Log(choice); + } +}, "My name is Roger and I am a principal software engineer at Salesforce. This is my resume:", maxTokens: 200, temperature: 0.5, presencePenalty: 0.1, frequencyPenalty: 0.1, model: Model.Davinci); +``` + ### [Moderations](https://platform.openai.com/docs/api-reference/moderations) Given a input text, outputs if the model classifies it as violating OpenAI's content policy. @@ -837,6 +840,35 @@ Classifies if text violates OpenAI's Content Policy. ```csharp var api = new OpenAIClient(); -var response = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them."); -Assert.IsTrue(response); +var isViolation = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them."); +Assert.IsTrue(isViolation); +``` + +Additionally you can also get the scores of a given input. + +```csharp +var response = await OpenAIClient.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest("I love you")); +Assert.IsNotNull(response); +Debug.Log(response.Results?[0]?.Scores?.ToString()); +``` + +--- + +### [Edits](https://platform.openai.com/docs/api-reference/edits) + +> Deprecated, and soon to be removed. + +Given a prompt and an instruction, the model will return an edited version of the prompt. + +The Edits API is accessed via `OpenAIClient.EditsEndpoint` + +#### [Create Edit](https://platform.openai.com/docs/api-reference/edits/create) + +Creates a new edit for the provided input, instruction, and parameters using the provided input and instruction. + +```csharp +var api = new OpenAIClient(); +var request = new EditRequest("What day of the wek is it?", "Fix the spelling mistakes"); +var response = await api.EditsEndpoint.CreateEditAsync(request); +Debug.Log(response); ```