From 38b6f692e99e05a6d442de6cea50c812a4a33573 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Tue, 28 Nov 2023 16:46:32 -0500 Subject: [PATCH 1/3] OpenAI-DotNet 7.3.8 - Added Chat.Content.ctr overloads and implicit casting for easier useage - Internal refactoring of FilesEndpoint.DeleteFileAsync (better status checking) - Internal refactoring of FineTuningEndpoint to ensure we're properly setting response data - Updated unit tests - Updated docs --- OpenAI-DotNet-Tests/TestFixture_01_Models.cs | 3 + OpenAI-DotNet-Tests/TestFixture_03_Chat.cs | 426 ++++-------------- OpenAI-DotNet-Tests/TestFixture_05_Images.cs | 3 +- OpenAI-DotNet-Tests/TestFixture_08_Files.cs | 4 +- .../TestFixture_09_FineTuning.cs | 26 +- .../Assistants/AssistantsEndpoint.cs | 34 +- OpenAI-DotNet/Audio/AudioEndpoint.cs | 8 +- OpenAI-DotNet/Chat/ChatEndpoint.cs | 18 +- OpenAI-DotNet/Chat/Content.cs | 20 +- OpenAI-DotNet/Chat/Conversation.cs | 2 +- OpenAI-DotNet/Common/BaseEndPoint.cs | 11 +- .../Completions/CompletionsEndpoint.cs | 14 +- OpenAI-DotNet/Edits/EditsEndpoint.cs | 6 +- .../Embeddings/EmbeddingsEndpoint.cs | 6 +- OpenAI-DotNet/Files/FilesEndpoint.cs | 23 +- .../FineTuning/FineTuningEndpoint.cs | 36 +- OpenAI-DotNet/Images/ImagesEndpoint.cs | 10 +- OpenAI-DotNet/Models/ModelsEndpoint.cs | 8 +- .../Moderations/ModerationsEndpoint.cs | 72 +-- OpenAI-DotNet/OpenAI-DotNet.csproj | 12 +- OpenAI-DotNet/Threads/ThreadsEndpoint.cs | 80 ++-- README.md | 20 +- 22 files changed, 301 insertions(+), 541 deletions(-) diff --git a/OpenAI-DotNet-Tests/TestFixture_01_Models.cs b/OpenAI-DotNet-Tests/TestFixture_01_Models.cs index e8bab4eb..1f045ee1 100644 --- a/OpenAI-DotNet-Tests/TestFixture_01_Models.cs +++ b/OpenAI-DotNet-Tests/TestFixture_01_Models.cs @@ -10,6 +10,7 @@ internal class TestFixture_01_Models : AbstractTestFixture [Test] public async Task Test_1_GetModels() { + Assert.IsNotNull(OpenAIClient.ModelsEndpoint); var results = await OpenAIClient.ModelsEndpoint.GetModelsAsync(); Assert.IsNotNull(results); Assert.NotZero(results.Count); @@ -18,7 +19,9 @@ public async Task Test_1_GetModels() [Test] public async Task Test_2_RetrieveModelDetails() { + Assert.IsNotNull(OpenAIClient.ModelsEndpoint); var models = await OpenAIClient.ModelsEndpoint.GetModelsAsync(); + Assert.IsNotEmpty(models); Console.WriteLine($"Found {models.Count} models!"); foreach (var model in models.OrderBy(model => model.Id)) diff --git a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs index b1452d0d..44a0c9f4 100644 --- a/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs +++ b/OpenAI-DotNet-Tests/TestFixture_03_Chat.cs @@ -14,7 +14,7 @@ namespace OpenAI.Tests internal class TestFixture_03_Chat : AbstractTestFixture { [Test] - public async Task Test_01_GetChatCompletion() + public async Task Test_01_01_GetChatCompletion() { Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List @@ -39,7 +39,7 @@ public async Task Test_01_GetChatCompletion() } [Test] - public async Task Test_02_GetChatStreamingCompletion() + public async Task Test_01_02_GetChatStreamingCompletion() { Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List @@ -67,7 +67,7 @@ public async Task Test_02_GetChatStreamingCompletion() var choice = response.FirstChoice; Assert.IsNotNull(choice); Assert.IsNotNull(choice.Message); - Assert.IsFalse(string.IsNullOrEmpty(choice.Message.Content)); + Assert.IsFalse(string.IsNullOrEmpty(choice.ToString())); Console.WriteLine($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); Assert.IsTrue(choice.Message.Role == Role.Assistant); Assert.IsTrue(choice.Message.Content!.Equals(cumulativeDelta)); @@ -76,7 +76,30 @@ public async Task Test_02_GetChatStreamingCompletion() } [Test] - public async Task Test_03_GetChatStreamingCompletionEnumerableAsync() + 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.IsNotEmpty(response.Choices); + + foreach (var choice in response.Choices) + { + Console.WriteLine($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); + } + + response.GetUsage(); + } + + [Test] + public async Task Test_01_04_GetChatStreamingCompletionEnumerableAsync() { Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List @@ -103,11 +126,11 @@ public async Task Test_03_GetChatStreamingCompletionEnumerableAsync() Console.WriteLine(cumulativeDelta); } - //[Test] - [Obsolete] - public async Task Test_04_GetChatFunctionCompletion() + [Test] + public async Task Test_02_01_GetChatToolCompletion() { Assert.IsNotNull(OpenAIClient.ChatEndpoint); + var messages = new List { new Message(Role.System, "You are a helpful weather assistant."), @@ -119,7 +142,7 @@ public async Task Test_04_GetChatFunctionCompletion() Console.WriteLine($"{message.Role}: {message.Content}"); } - var functions = new List + var tools = new List { new Function( nameof(WeatherService.GetCurrentWeather), @@ -143,8 +166,7 @@ public async Task Test_04_GetChatFunctionCompletion() ["required"] = new JsonArray { "location", "unit" } }) }; - - var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); + var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); var response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); Assert.IsNotNull(response); Assert.IsNotNull(response.Choices); @@ -156,7 +178,7 @@ public async Task Test_04_GetChatFunctionCompletion() var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); messages.Add(locationMessage); Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); Assert.IsNotNull(response); @@ -164,38 +186,40 @@ public async Task Test_04_GetChatFunctionCompletion() Assert.IsTrue(response.Choices.Count == 1); messages.Add(response.FirstChoice.Message); - if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content)) + if (!string.IsNullOrEmpty(response.ToString())) { Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var unitMessage = new Message(Role.User, "celsius"); messages.Add(unitMessage); Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); + 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(response.FirstChoice.FinishReason == "function_call"); - Assert.IsTrue(response.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); - Console.WriteLine($"{response.FirstChoice.Message.Function.Arguments}"); - var functionArgs = JsonSerializer.Deserialize(response.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)); + Console.WriteLine($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); + Console.WriteLine($"{usedTool.Function.Arguments}"); + var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); Assert.IsNotNull(functionResult); - messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather))); - Console.WriteLine($"{Role.Function}: {functionResult}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); + messages.Add(new Message(usedTool, functionResult)); + Console.WriteLine($"{Role.Tool}: {functionResult}"); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); Console.WriteLine(response); } - //[Test] - [Obsolete] - public async Task Test_05_GetChatFunctionCompletion_Streaming() + [Test] + public async Task Test_02_02_GetChatToolCompletion_Streaming() { + Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List { new Message(Role.System, "You are a helpful weather assistant."), @@ -207,7 +231,7 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming() Console.WriteLine($"{message.Role}: {message.Content}"); } - var functions = new List + var tools = new List { new Function( nameof(WeatherService.GetCurrentWeather), @@ -231,8 +255,7 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming() ["required"] = new JsonArray { "location", "unit" } }) }; - - var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); + var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); var response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); @@ -247,7 +270,7 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming() var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland"); messages.Add(locationMessage); Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); @@ -259,14 +282,14 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming() Assert.IsTrue(response.Choices.Count == 1); messages.Add(response.FirstChoice.Message); - if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content)) + if (!string.IsNullOrEmpty(response.ToString())) { Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); var unitMessage = new Message(Role.User, "celsius"); messages.Add(unitMessage); Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}"); - chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto"); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); response = await OpenAIClient.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { Assert.IsNotNull(partialResponse); @@ -278,198 +301,39 @@ public async Task Test_05_GetChatFunctionCompletion_Streaming() Assert.IsTrue(response.Choices.Count == 1); } - Assert.IsTrue(response.FirstChoice.FinishReason == "function_call"); - Assert.IsTrue(response.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); - Console.WriteLine($"{response.FirstChoice.Message.Function.Arguments}"); - - var functionArgs = JsonSerializer.Deserialize(response.FirstChoice.Message.Function.Arguments.ToString()); - var functionResult = WeatherService.GetCurrentWeather(functionArgs); - Assert.IsNotNull(functionResult); - messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather))); - Console.WriteLine($"{Role.Function}: {functionResult}"); - } - - //[Test] - [Obsolete] - public async Task Test_06_GetChatFunctionForceCompletion() - { - 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?"), - }; - - foreach (var message in messages) - { - Console.WriteLine($"{message.Role}: {message.Content}"); - } - - var functions = new List - { - new Function( - nameof(WeatherService.GetCurrentWeather), - "Get the current weather in a given location", - new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["location"] = new JsonObject - { - ["type"] = "string", - ["description"] = "The city and state, e.g. San Francisco, CA" - }, - ["unit"] = new JsonObject - { - ["type"] = "string", - ["enum"] = new JsonArray {"celsius", "fahrenheit"} - } - }, - ["required"] = new JsonArray { "location", "unit" } - }) - }; - - var chatRequest = new ChatRequest(messages, functions: functions); - 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); - - Console.WriteLine($"{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); - Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}"); - chatRequest = new ChatRequest( - messages, - functions: functions, - functionCall: nameof(WeatherService.GetCurrentWeather), - model: "gpt-3.5-turbo-0613"); - response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); - - Assert.IsNotNull(response); - Assert.IsNotNull(response.Choices); - Assert.IsTrue(response.Choices.Count == 1); - messages.Add(response.FirstChoice.Message); - - Assert.IsTrue(response.FirstChoice.FinishReason == "stop"); - Assert.IsTrue(response.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather)); - Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); - Console.WriteLine($"{response.FirstChoice.Message.Function.Arguments}"); - var functionArgs = JsonSerializer.Deserialize(response.FirstChoice.Message.Function.Arguments.ToString()); - var functionResult = WeatherService.GetCurrentWeather(functionArgs); - Assert.IsNotNull(functionResult); - messages.Add(new Message(Role.Function, functionResult, nameof(WeatherService.GetCurrentWeather))); - Console.WriteLine($"{Role.Function}: {functionResult}"); - } - - [Test] - public async Task Test_07_GetChatToolCompletion() - { - 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?"), - }; - - foreach (var message in messages) - { - Console.WriteLine($"{message.Role}: {message.Content}"); - } - - var tools = new List - { - new Function( - nameof(WeatherService.GetCurrentWeather), - "Get the current weather in a given location", - new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["location"] = new JsonObject - { - ["type"] = "string", - ["description"] = "The city and state, e.g. San Francisco, CA" - }, - ["unit"] = new JsonObject - { - ["type"] = "string", - ["enum"] = new JsonArray {"celsius", "fahrenheit"} - } - }, - ["required"] = new JsonArray { "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); - - Console.WriteLine($"{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); - Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}"); - 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); - messages.Add(response.FirstChoice.Message); - - if (!string.IsNullOrEmpty(response.FirstChoice)) - { - Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); - - var unitMessage = new Message(Role.User, "celsius"); - messages.Add(unitMessage); - Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}"); - 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(response.FirstChoice.FinishReason == "tool_calls"); var usedTool = response.FirstChoice.Message.ToolCalls[0]; Assert.IsNotNull(usedTool); Assert.IsTrue(usedTool.Function.Name == nameof(WeatherService.GetCurrentWeather)); Console.WriteLine($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); Console.WriteLine($"{usedTool.Function.Arguments}"); + var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString()); var functionResult = WeatherService.GetCurrentWeather(functionArgs); Assert.IsNotNull(functionResult); messages.Add(new Message(usedTool, functionResult)); Console.WriteLine($"{Role.Tool}: {functionResult}"); + chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); - response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); - Console.WriteLine(response); + 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() { + 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) - { - Console.WriteLine($"{message.Role}: {message.Content}"); - } - var tools = new List { new Function( @@ -488,83 +352,41 @@ public async Task Test_08_GetChatToolCompletion_Streaming() ["unit"] = new JsonObject { ["type"] = "string", - ["enum"] = new JsonArray {"celsius", "fahrenheit"} + ["enum"] = new JsonArray { "celsius", "fahrenheit" } } }, ["required"] = new JsonArray { "location", "unit" } }) }; - var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto"); + + 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(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); - Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}"); - 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); - }); - Assert.IsNotNull(response); - Assert.IsNotNull(response.Choices); - Assert.IsTrue(response.Choices.Count == 1); messages.Add(response.FirstChoice.Message); - if (!string.IsNullOrEmpty(response.FirstChoice.Message.Content)) - { - Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); + var toolCalls = response.FirstChoice.Message.ToolCalls; - var unitMessage = new Message(Role.User, "celsius"); - messages.Add(unitMessage); - Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}"); - 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); - }); - Assert.IsNotNull(response); - Assert.IsNotNull(response.Choices); - Assert.IsTrue(response.Choices.Count == 1); - } + Assert.NotNull(toolCalls); + Assert.AreEqual(2, toolCalls.Count); - 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)); - Console.WriteLine($"{response.FirstChoice.Message.Role}: {usedTool.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}"); - Console.WriteLine($"{usedTool.Function.Arguments}"); + foreach (var toolCall in toolCalls) + { + messages.Add(new Message(toolCall, "Sunny!")); + } - var functionArgs = JsonSerializer.Deserialize(usedTool.Function.Arguments.ToString()); - var functionResult = WeatherService.GetCurrentWeather(functionArgs); - Assert.IsNotNull(functionResult); - messages.Add(new Message(usedTool, functionResult)); - Console.WriteLine($"{Role.Tool}: {functionResult}"); + chatRequest = new ChatRequest(messages, model: "gpt-4-1106-preview", tools: tools, toolChoice: "auto"); + response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); - 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); - }); Assert.IsNotNull(response); } [Test] - public async Task Test_09_GetChatToolForceCompletion() + public async Task Test_02_04_GetChatToolForceCompletion() { Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List @@ -639,7 +461,7 @@ public async Task Test_09_GetChatToolForceCompletion() } [Test] - public async Task Test_10_GetChatVision() + public async Task Test_03_01_GetChatVision() { Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List @@ -647,7 +469,7 @@ public async Task Test_10_GetChatVision() new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), + "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) }) }; @@ -660,7 +482,7 @@ public async Task Test_10_GetChatVision() } [Test] - public async Task Test_11_GetChatVisionStreaming() + public async Task Test_03_02_GetChatVisionStreaming() { Assert.IsNotNull(OpenAIClient.ChatEndpoint); var messages = new List @@ -668,7 +490,7 @@ public async Task Test_11_GetChatVisionStreaming() new Message(Role.System, "You are a helpful assistant."), new Message(Role.User, new List { - new Content(ContentType.Text, "What's in this image?"), + "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) }) }; @@ -684,89 +506,5 @@ public async Task Test_11_GetChatVisionStreaming() Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishDetails}"); response.GetUsage(); } - - [Test] - public async Task Test_ChatCompletion_Multiple_Tools_Streaming() - { - 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 in San Diego and LA?"), - }; - - var tools = new List - { - new Function( - nameof(WeatherService.GetCurrentWeather), - "Get the current weather in a given location", - new JsonObject - { - ["type"] = "object", - ["properties"] = new JsonObject - { - ["location"] = new JsonObject - { - ["type"] = "string", - ["description"] = "The city and state, e.g. San Francisco, CA" - }, - ["unit"] = new JsonObject - { - ["type"] = "string", - ["enum"] = new JsonArray { "celsius", "fahrenheit" } - } - }, - ["required"] = new JsonArray { "location", "unit" } - }) - }; - - 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); - }); - - messages.Add(response.FirstChoice.Message); - - var toolCalls = response.FirstChoice.Message.ToolCalls; - - Assert.NotNull(toolCalls); - Assert.AreEqual(2, toolCalls.Count); - - foreach (var toolCall in toolCalls) - { - messages.Add(new Message(toolCall, "Sunny!")); - } - - chatRequest = new ChatRequest(messages, model: "gpt-4-1106-preview", tools: tools, toolChoice: "auto"); - response = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest); - - Assert.IsNotNull(response); - } - - [Test] - public async Task Test_12_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.IsNotEmpty(response.Choices); - - foreach (var choice in response.Choices) - { - Console.WriteLine($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}"); - } - - response.GetUsage(); - } } } \ No newline at end of file diff --git a/OpenAI-DotNet-Tests/TestFixture_05_Images.cs b/OpenAI-DotNet-Tests/TestFixture_05_Images.cs index 09b9a931..c74202e9 100644 --- a/OpenAI-DotNet-Tests/TestFixture_05_Images.cs +++ b/OpenAI-DotNet-Tests/TestFixture_05_Images.cs @@ -13,8 +13,7 @@ internal class TestFixture_05_Images : AbstractTestFixture public async Task Test_1_GenerateImages() { Assert.IsNotNull(OpenAIClient.ImagesEndPoint); - - var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_2); + var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_3); var imageResults = await OpenAIClient.ImagesEndPoint.GenerateImageAsync(request); Assert.IsNotNull(imageResults); diff --git a/OpenAI-DotNet-Tests/TestFixture_08_Files.cs b/OpenAI-DotNet-Tests/TestFixture_08_Files.cs index 0916e737..a48a5919 100644 --- a/OpenAI-DotNet-Tests/TestFixture_08_Files.cs +++ b/OpenAI-DotNet-Tests/TestFixture_08_Files.cs @@ -72,8 +72,8 @@ public async Task Test_04_DeleteFiles() foreach (var file in fileList) { - var result = await OpenAIClient.FilesEndpoint.DeleteFileAsync(file); - Assert.IsTrue(result); + var isDeleted = await OpenAIClient.FilesEndpoint.DeleteFileAsync(file); + Assert.IsTrue(isDeleted); Console.WriteLine($"{file.Id} -> deleted"); } diff --git a/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs b/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs index 27fdd7e0..2810a440 100644 --- a/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs +++ b/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs @@ -81,6 +81,7 @@ private async Task CreateTestTrainingDataAsync() }; const string localTrainingDataPath = "fineTunesTestTrainingData.jsonl"; await File.WriteAllLinesAsync(localTrainingDataPath, conversations.Select(conversation => JsonSerializer.Serialize(conversation, OpenAIClient.JsonSerializationOptions))); + var fileData = await OpenAIClient.FilesEndpoint.UploadFileAsync(localTrainingDataPath, "fine-tune"); File.Delete(localTrainingDataPath); Assert.IsFalse(File.Exists(localTrainingDataPath)); @@ -92,6 +93,7 @@ public async Task Test_01_CreateFineTuneJob() { Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); var fileData = await CreateTestTrainingDataAsync(); + Assert.IsNotNull(fileData); var request = new CreateFineTuneJobRequest(Model.GPT3_5_Turbo, fileData); var job = await OpenAIClient.FineTuningEndpoint.CreateJobAsync(request); @@ -103,11 +105,11 @@ public async Task Test_01_CreateFineTuneJob() public async Task Test_02_ListFineTuneJobs() { Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); - var list = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); - Assert.IsNotNull(list); - Assert.IsNotEmpty(list.Items); + var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); + Assert.IsNotNull(jobList); + Assert.IsNotEmpty(jobList.Items); - foreach (var job in list.Items.OrderByDescending(job => job.CreatedAt)) + foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt)) { Assert.IsNotNull(job); Assert.IsNotNull(job.Client); @@ -135,11 +137,11 @@ public async Task Test_03_RetrieveFineTuneJobInfo() public async Task Test_04_ListFineTuneEvents() { Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); - var list = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); - Assert.IsNotNull(list); - Assert.IsNotEmpty(list.Items); + var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); + Assert.IsNotNull(jobList); + Assert.IsNotEmpty(jobList.Items); - foreach (var job in list.Items) + foreach (var job in jobList.Items) { if (job.Status == JobStatus.Cancelled) { continue; } @@ -163,11 +165,11 @@ public async Task Test_04_ListFineTuneEvents() public async Task Test_05_CancelFineTuneJob() { Assert.IsNotNull(OpenAIClient.FineTuningEndpoint); - var list = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); - Assert.IsNotNull(list); - Assert.IsNotEmpty(list.Items); + var jobList = await OpenAIClient.FineTuningEndpoint.ListJobsAsync(); + Assert.IsNotNull(jobList); + Assert.IsNotEmpty(jobList.Items); - foreach (var job in list.Items) + foreach (var job in jobList.Items) { if (job.Status is > JobStatus.NotStarted and < JobStatus.Succeeded) { diff --git a/OpenAI-DotNet/Assistants/AssistantsEndpoint.cs b/OpenAI-DotNet/Assistants/AssistantsEndpoint.cs index 230368b9..6fd40871 100644 --- a/OpenAI-DotNet/Assistants/AssistantsEndpoint.cs +++ b/OpenAI-DotNet/Assistants/AssistantsEndpoint.cs @@ -9,7 +9,7 @@ namespace OpenAI.Assistants { public sealed class AssistantsEndpoint : BaseEndPoint { - internal AssistantsEndpoint(OpenAIClient api) : base(api) { } + internal AssistantsEndpoint(OpenAIClient client) : base(client) { } protected override string Root => "assistants"; @@ -21,9 +21,9 @@ internal AssistantsEndpoint(OpenAIClient api) : base(api) { } /// public async Task> ListAssistantsAsync(ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } /// @@ -36,9 +36,9 @@ public async Task CreateAssistantAsync(CreateAssistantRequest { request ??= new CreateAssistantRequest(); var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -49,9 +49,9 @@ public async Task CreateAssistantAsync(CreateAssistantRequest /// . public async Task RetrieveAssistantAsync(string assistantId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -64,9 +64,9 @@ public async Task RetrieveAssistantAsync(string assistantId, public async Task ModifyAssistantAsync(string assistantId, CreateAssistantRequest request, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{assistantId}"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{assistantId}"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -77,7 +77,7 @@ public async Task ModifyAssistantAsync(string assistantId, Cr /// True, if the assistant was deleted. public async Task DeleteAssistantAsync(string assistantId, CancellationToken cancellationToken = default) { - var response = await Api.Client.DeleteAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.DeleteAsync(GetUrl($"/{assistantId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; } @@ -93,9 +93,9 @@ public async Task DeleteAssistantAsync(string assistantId, CancellationTok /// . public async Task> ListFilesAsync(string assistantId, ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{assistantId}/files", query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{assistantId}/files", query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } /// @@ -116,9 +116,9 @@ public async Task AttachFileAsync(string assistantId, Fil } var jsonContent = JsonSerializer.Serialize(new { file_id = file.Id }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{assistantId}/files"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{assistantId}/files"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -130,9 +130,9 @@ public async Task AttachFileAsync(string assistantId, Fil /// . public async Task RetrieveFileAsync(string assistantId, string fileId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{assistantId}/files/{fileId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{assistantId}/files/{fileId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -149,7 +149,7 @@ public async Task RetrieveFileAsync(string assistantId, s /// True, if file was removed. public async Task RemoveFileAsync(string assistantId, string fileId, CancellationToken cancellationToken = default) { - var response = await Api.Client.DeleteAsync(GetUrl($"/{assistantId}/files/{fileId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.DeleteAsync(GetUrl($"/{assistantId}/files/{fileId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; } diff --git a/OpenAI-DotNet/Audio/AudioEndpoint.cs b/OpenAI-DotNet/Audio/AudioEndpoint.cs index 8de57131..9c44d701 100644 --- a/OpenAI-DotNet/Audio/AudioEndpoint.cs +++ b/OpenAI-DotNet/Audio/AudioEndpoint.cs @@ -27,7 +27,7 @@ public AudioResponse(string text) } /// - public AudioEndpoint(OpenAIClient api) : base(api) { } + public AudioEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "audio"; @@ -42,7 +42,7 @@ public AudioEndpoint(OpenAIClient api) : base(api) { } public async Task> CreateSpeechAsync(SpeechRequest request, Func, Task> chunkCallback = null, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl("/speech"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/speech"), jsonContent, cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var memoryStream = new MemoryStream(); @@ -106,7 +106,7 @@ public async Task CreateTranscriptionAsync(AudioTranscriptionRequest req request.Dispose(); - var response = await Api.Client.PostAsync(GetUrl("/transcriptions"), content, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/transcriptions"), content, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return responseFormat == AudioResponseFormat.Json @@ -143,7 +143,7 @@ public async Task CreateTranslationAsync(AudioTranslationRequest request request.Dispose(); - var response = await Api.Client.PostAsync(GetUrl("/translations"), content, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/translations"), content, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return responseFormat == AudioResponseFormat.Json diff --git a/OpenAI-DotNet/Chat/ChatEndpoint.cs b/OpenAI-DotNet/Chat/ChatEndpoint.cs index cd4ba12a..83e486e4 100644 --- a/OpenAI-DotNet/Chat/ChatEndpoint.cs +++ b/OpenAI-DotNet/Chat/ChatEndpoint.cs @@ -17,7 +17,7 @@ namespace OpenAI.Chat public sealed class ChatEndpoint : BaseEndPoint { /// - public ChatEndpoint(OpenAIClient api) : base(api) { } + public ChatEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "chat"; @@ -31,9 +31,9 @@ public ChatEndpoint(OpenAIClient api) : base(api) { } public async Task GetCompletionAsync(ChatRequest chatRequest, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl("/completions"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/completions"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -49,7 +49,7 @@ public async Task StreamCompletionAsync(ChatRequest chatRequest, A var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl("/completions")); request.Content = jsonContent; - var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var response = await client.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream); @@ -67,7 +67,7 @@ public async Task StreamCompletionAsync(ChatRequest chatRequest, A Console.WriteLine(eventData); } - var partialResponse = response.Deserialize(eventData, Api); + var partialResponse = response.Deserialize(eventData, client); if (chatResponse == null) { @@ -85,7 +85,7 @@ public async Task StreamCompletionAsync(ChatRequest chatRequest, A if (chatResponse == null) { return null; } - chatResponse.SetResponseData(response.Headers, Api); + chatResponse.SetResponseData(response.Headers, client); resultHandler?.Invoke(chatResponse); return chatResponse; } @@ -104,7 +104,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsync(Chat var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl("/completions")); request.Content = jsonContent; - var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var response = await client.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream); @@ -122,7 +122,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsync(Chat Console.WriteLine(eventData); } - var partialResponse = response.Deserialize(eventData, Api); + var partialResponse = response.Deserialize(eventData, client); if (chatResponse == null) { @@ -140,7 +140,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsync(Chat if (chatResponse == null) { yield break; } - chatResponse.SetResponseData(response.Headers, Api); + chatResponse.SetResponseData(response.Headers, client); yield return chatResponse; } } diff --git a/OpenAI-DotNet/Chat/Content.cs b/OpenAI-DotNet/Chat/Content.cs index df468bba..92d6822a 100644 --- a/OpenAI-DotNet/Chat/Content.cs +++ b/OpenAI-DotNet/Chat/Content.cs @@ -7,6 +7,18 @@ public sealed class Content { public Content() { } + public Content(ImageUrl imageUrl) + { + Type = ContentType.ImageUrl; + ImageUrl = imageUrl; + } + + public Content(string input) + { + Type = ContentType.Text; + Text = input; + } + public Content(ContentType type, string input) { Type = type; @@ -22,12 +34,6 @@ public Content(ContentType type, string input) } } - public Content(ImageUrl imageUrl) - { - Type = ContentType.ImageUrl; - ImageUrl = imageUrl; - } - [JsonInclude] [JsonPropertyName("type")] [JsonConverter(typeof(JsonStringEnumConverter))] @@ -43,6 +49,8 @@ public Content(ImageUrl imageUrl) [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public ImageUrl ImageUrl { get; private set; } + public static implicit operator Content(string input) => new Content(ContentType.Text, input); + public static implicit operator Content(ImageUrl imageUrl) => new Content(imageUrl); } } \ No newline at end of file diff --git a/OpenAI-DotNet/Chat/Conversation.cs b/OpenAI-DotNet/Chat/Conversation.cs index 0a6172ae..2714ee28 100644 --- a/OpenAI-DotNet/Chat/Conversation.cs +++ b/OpenAI-DotNet/Chat/Conversation.cs @@ -25,6 +25,6 @@ public Conversation(List messages) public override string ToString() => JsonSerializer.Serialize(this, OpenAIClient.JsonSerializationOptions); - public static implicit operator string(Conversation conversation) => conversation.ToString(); + public static implicit operator string(Conversation conversation) => conversation?.ToString(); } } \ No newline at end of file diff --git a/OpenAI-DotNet/Common/BaseEndPoint.cs b/OpenAI-DotNet/Common/BaseEndPoint.cs index ae03f96c..8ddacd3f 100644 --- a/OpenAI-DotNet/Common/BaseEndPoint.cs +++ b/OpenAI-DotNet/Common/BaseEndPoint.cs @@ -5,9 +5,10 @@ namespace OpenAI { public abstract class BaseEndPoint { - protected BaseEndPoint(OpenAIClient api) => Api = api; + protected BaseEndPoint(OpenAIClient client) => this.client = client; - protected readonly OpenAIClient Api; + // ReSharper disable once InconsistentNaming + protected readonly OpenAIClient client; /// /// The root endpoint address. @@ -21,9 +22,9 @@ public abstract class BaseEndPoint /// Optional, parameters to add to the endpoint. protected string GetUrl(string endpoint = "", Dictionary queryParameters = null) { - var result = string.Format(Api.OpenAIClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}"); + var result = string.Format(client.OpenAIClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}"); - foreach (var defaultQueryParameter in Api.OpenAIClientSettings.DefaultQueryParameters) + foreach (var defaultQueryParameter in client.OpenAIClientSettings.DefaultQueryParameters) { queryParameters ??= new Dictionary(); queryParameters.Add(defaultQueryParameter.Key, defaultQueryParameter.Value); @@ -45,7 +46,7 @@ protected string GetUrl(string endpoint = "", Dictionary queryPa /// public bool EnableDebug { - get => enableDebug || Api.EnableDebug; + get => enableDebug || client.EnableDebug; set => enableDebug = value; } } diff --git a/OpenAI-DotNet/Completions/CompletionsEndpoint.cs b/OpenAI-DotNet/Completions/CompletionsEndpoint.cs index a9a537f9..a4c4ad73 100644 --- a/OpenAI-DotNet/Completions/CompletionsEndpoint.cs +++ b/OpenAI-DotNet/Completions/CompletionsEndpoint.cs @@ -22,7 +22,7 @@ namespace OpenAI.Completions public sealed class CompletionsEndpoint : BaseEndPoint { /// - internal CompletionsEndpoint(OpenAIClient api) : base(api) { } + internal CompletionsEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "completions"; @@ -111,9 +111,9 @@ public async Task CreateCompletionAsync(CompletionRequest co { completionRequest.Stream = false; var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } #endregion Non-Streaming @@ -205,7 +205,7 @@ public async Task StreamCompletionAsync(CompletionRequest completionRequest, Act var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl()); request.Content = jsonContent; - var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var response = await client.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream); @@ -218,7 +218,7 @@ public async Task StreamCompletionAsync(CompletionRequest completionRequest, Act { if (string.IsNullOrWhiteSpace(eventData)) { continue; } - resultHandler(response.Deserialize(eventData, Api)); + resultHandler(response.Deserialize(eventData, client)); } else { @@ -314,7 +314,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsyn var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl()); request.Content = jsonContent; - var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var response = await client.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var reader = new StreamReader(stream); @@ -326,7 +326,7 @@ public async IAsyncEnumerable StreamCompletionEnumerableAsyn if (streamData.TryGetEventStreamData(out var eventData)) { if (string.IsNullOrWhiteSpace(eventData)) { continue; } - yield return response.Deserialize(eventData, Api); + yield return response.Deserialize(eventData, client); } else { diff --git a/OpenAI-DotNet/Edits/EditsEndpoint.cs b/OpenAI-DotNet/Edits/EditsEndpoint.cs index 519f814b..25a053d9 100644 --- a/OpenAI-DotNet/Edits/EditsEndpoint.cs +++ b/OpenAI-DotNet/Edits/EditsEndpoint.cs @@ -14,7 +14,7 @@ namespace OpenAI.Edits public sealed class EditsEndpoint : BaseEndPoint { /// - public EditsEndpoint(OpenAIClient api) : base(api) { } + public EditsEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "edits"; @@ -62,9 +62,9 @@ public async Task CreateEditAsync( public async Task CreateEditAsync(EditRequest request, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } } } diff --git a/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs b/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs index a6e0eb30..8929eada 100644 --- a/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs +++ b/OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs @@ -13,7 +13,7 @@ namespace OpenAI.Embeddings public sealed class EmbeddingsEndpoint : BaseEndPoint { /// - public EmbeddingsEndpoint(OpenAIClient api) : base(api) { } + public EmbeddingsEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "embeddings"; @@ -66,9 +66,9 @@ public async Task CreateEmbeddingAsync(IEnumerable i public async Task CreateEmbeddingAsync(EmbeddingsRequest request, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } } } diff --git a/OpenAI-DotNet/Files/FilesEndpoint.cs b/OpenAI-DotNet/Files/FilesEndpoint.cs index f8e74193..a6019ef6 100644 --- a/OpenAI-DotNet/Files/FilesEndpoint.cs +++ b/OpenAI-DotNet/Files/FilesEndpoint.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; @@ -23,7 +24,7 @@ private class FilesList } /// - public FilesEndpoint(OpenAIClient api) : base(api) { } + public FilesEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "files"; @@ -43,7 +44,7 @@ public async Task> ListFilesAsync(string purpose = n query = new Dictionary { { nameof(purpose), purpose } }; } - var response = await Api.Client.GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl(queryParameters: query), cancellationToken).ConfigureAwait(false); var resultAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(resultAsString, OpenAIClient.JsonSerializationOptions)?.Files; } @@ -82,8 +83,7 @@ public async Task UploadFileAsync(FileUploadRequest request, Cance content.Add(new StringContent(request.Purpose), "purpose"); content.Add(new ByteArrayContent(fileData.ToArray()), "file", request.FileName); request.Dispose(); - - var response = await Api.Client.PostAsync(GetUrl(), content, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl(), content, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); } @@ -100,22 +100,25 @@ public async Task DeleteFileAsync(string fileId, CancellationToken cancell async Task InternalDeleteFileAsync(int attempt) { - var response = await Api.Client.DeleteAsync(GetUrl($"/{fileId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.DeleteAsync(GetUrl($"/{fileId}"), cancellationToken).ConfigureAwait(false); // We specifically don't use the extension method here bc we need to check if it's still processing the file. var responseAsString = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { - if (responseAsString.Contains("File is still processing. Check back later.")) + const string fileProcessing = "File is still processing. Check back later."; + + if (response.StatusCode == /* 409 */ HttpStatusCode.Conflict || + !string.IsNullOrWhiteSpace(responseAsString) && + responseAsString.Contains(fileProcessing)) { // back off requests on each attempt await Task.Delay(1000 * attempt++, cancellationToken).ConfigureAwait(false); return await InternalDeleteFileAsync(attempt).ConfigureAwait(false); } - - throw new HttpRequestException($"{nameof(DeleteFileAsync)} Failed! HTTP status code: {response.StatusCode}. Response: {responseAsString}"); } + await response.CheckResponseAsync(cancellationToken); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; } } @@ -128,7 +131,7 @@ async Task InternalDeleteFileAsync(int attempt) /// public async Task GetFileInfoAsync(string fileId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{fileId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{fileId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); } @@ -181,7 +184,7 @@ public async Task DownloadFileAsync(FileResponse fileData, string direct } } - await using var response = await Api.Client.GetStreamAsync(GetUrl($"/{fileData.Id}/content"), cancellationToken).ConfigureAwait(false); + await using var response = await client.Client.GetStreamAsync(GetUrl($"/{fileData.Id}/content"), cancellationToken).ConfigureAwait(false); await using var fileStream = new FileStream(filePath, FileMode.CreateNew); await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); return filePath; diff --git a/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs b/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs index d04a80b2..f9aa8878 100644 --- a/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs +++ b/OpenAI-DotNet/FineTuning/FineTuningEndpoint.cs @@ -15,7 +15,7 @@ namespace OpenAI.FineTuning public sealed class FineTuningEndpoint : BaseEndPoint { /// - public FineTuningEndpoint(OpenAIClient api) : base(api) { } + public FineTuningEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "fine_tuning"; @@ -27,13 +27,13 @@ public FineTuningEndpoint(OpenAIClient api) : base(api) { } /// /// . /// Optional, . - /// . + /// . public async Task CreateJobAsync(CreateFineTuneJobRequest jobRequest, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(jobRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl("/jobs"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/jobs"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); + return response.Deserialize(responseAsString, client); } [Obsolete("Use new overload")] @@ -51,7 +51,7 @@ public async Task ListJobsAsync(int? limit, string after, Cance parameters.Add(nameof(after), after); } - var response = await Api.Client.GetAsync(GetUrl("/jobs", parameters), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl("/jobs", parameters), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); } @@ -61,12 +61,12 @@ public async Task ListJobsAsync(int? limit, string after, Cance /// /// . /// Optional, . - /// List of s. + /// List of s. public async Task> ListJobsAsync(ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl("/jobs", query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl("/jobs", query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } /// @@ -74,13 +74,13 @@ public async Task> ListJobsAsync(ListQuery que /// /// . /// Optional, . - /// . + /// . public async Task GetJobInfoAsync(string jobId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/jobs/{jobId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/jobs/{jobId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - var job = JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); - job.Events = (await ListJobEventsAsync(job, cancellationToken: cancellationToken).ConfigureAwait(false))?.Items; + var job = response.Deserialize(responseAsString, client); + job.Events = (await ListJobEventsAsync(job, query: null, cancellationToken: cancellationToken).ConfigureAwait(false))?.Items; return job; } @@ -89,10 +89,10 @@ public async Task GetJobInfoAsync(string jobId, Cancellatio /// /// to cancel. /// Optional, . - /// . + /// . public async Task CancelJobAsync(string jobId, CancellationToken cancellationToken = default) { - var response = await Api.Client.PostAsync(GetUrl($"/jobs/{jobId}/cancel"), null!, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/jobs/{jobId}/cancel"), null!, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); var result = JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); return result.Status == JobStatus.Cancelled; @@ -113,7 +113,7 @@ public async Task ListJobEventsAsync(string jobId, int? limit, string parameters.Add(nameof(after), after); } - var response = await Api.Client.GetAsync(GetUrl($"/jobs/{jobId}/events", parameters), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/jobs/{jobId}/events", parameters), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); } @@ -124,12 +124,12 @@ public async Task ListJobEventsAsync(string jobId, int? limit, string /// . /// . /// Optional, . - /// List of events for . + /// List of events for . public async Task> ListJobEventsAsync(string jobId, ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/jobs/{jobId}/events", query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/jobs/{jobId}/events", query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } } } diff --git a/OpenAI-DotNet/Images/ImagesEndpoint.cs b/OpenAI-DotNet/Images/ImagesEndpoint.cs index c732e915..4f4930ef 100644 --- a/OpenAI-DotNet/Images/ImagesEndpoint.cs +++ b/OpenAI-DotNet/Images/ImagesEndpoint.cs @@ -16,7 +16,7 @@ namespace OpenAI.Images public sealed class ImagesEndpoint : BaseEndPoint { /// - internal ImagesEndpoint(OpenAIClient api) : base(api) { } + internal ImagesEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "images"; @@ -63,7 +63,7 @@ public async Task> GenerateImageAsync( public async Task> GenerateImageAsync(ImageGenerationRequest request, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl("/generations"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/generations"), jsonContent, cancellationToken).ConfigureAwait(false); return await DeserializeResponseAsync(response, cancellationToken).ConfigureAwait(false); } @@ -139,7 +139,7 @@ public async Task> CreateImageEditAsync(ImageEditRequ } request.Dispose(); - var response = await Api.Client.PostAsync(GetUrl("/edits"), content, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/edits"), content, cancellationToken).ConfigureAwait(false); return await DeserializeResponseAsync(response, cancellationToken).ConfigureAwait(false); } @@ -197,14 +197,14 @@ public async Task> CreateImageVariationAsync(ImageVar } request.Dispose(); - var response = await Api.Client.PostAsync(GetUrl("/variations"), content, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/variations"), content, cancellationToken).ConfigureAwait(false); return await DeserializeResponseAsync(response, cancellationToken).ConfigureAwait(false); } private async Task> DeserializeResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken = default) { var resultAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - var imagesResponse = response.Deserialize(resultAsString, Api); + var imagesResponse = response.Deserialize(resultAsString, client); if (imagesResponse?.Results is not { Count: not 0 }) { diff --git a/OpenAI-DotNet/Models/ModelsEndpoint.cs b/OpenAI-DotNet/Models/ModelsEndpoint.cs index c0be66d8..ab04fd98 100644 --- a/OpenAI-DotNet/Models/ModelsEndpoint.cs +++ b/OpenAI-DotNet/Models/ModelsEndpoint.cs @@ -23,7 +23,7 @@ private sealed class ModelsList } /// - public ModelsEndpoint(OpenAIClient api) : base(api) { } + public ModelsEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "models"; @@ -35,7 +35,7 @@ public ModelsEndpoint(OpenAIClient api) : base(api) { } /// Asynchronously returns the list of all s public async Task> GetModelsAsync(CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Models; } @@ -48,7 +48,7 @@ public async Task> GetModelsAsync(CancellationToken cancell /// Asynchronously returns the with all available properties public async Task GetModelDetailsAsync(string id, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{id}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{id}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions); } @@ -72,7 +72,7 @@ public async Task DeleteFineTuneModelAsync(string modelId, CancellationTok try { - var response = await Api.Client.DeleteAsync(GetUrl($"/{model.Id}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.DeleteAsync(GetUrl($"/{model.Id}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; } diff --git a/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs b/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs index dea0292d..cc3993ea 100644 --- a/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs +++ b/OpenAI-DotNet/Moderations/ModerationsEndpoint.cs @@ -15,11 +15,45 @@ namespace OpenAI.Moderations public sealed class ModerationsEndpoint : BaseEndPoint { /// - public ModerationsEndpoint(OpenAIClient api) : base(api) { } + public ModerationsEndpoint(OpenAIClient client) : base(client) { } /// protected override string Root => "moderations"; + /// + /// Classifies if text violates OpenAI's Content Policy. + /// + /// + /// 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. + /// + /// Optional, . + /// + /// True, if the text has been flagged by the model as violating OpenAI's content policy. + /// + public async Task GetModerationAsync(string input, string model = null, CancellationToken cancellationToken = default) + { + var result = await CreateModerationAsync(new ModerationsRequest(input, model), cancellationToken).ConfigureAwait(false); + return result?.Results is { Count: not 0 } && result.Results.Any(moderationResult => moderationResult.Flagged); + } + + /// + /// Classifies if text violates OpenAI's Content Policy + /// + /// + /// Optional, . + public async Task CreateModerationAsync(ModerationsRequest request, CancellationToken cancellationToken = default) + { + var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); + var response = await client.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); + var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); + return response.Deserialize(responseAsString, client); + } + /// /// Classifies if text violates OpenAI's Content Policy. /// @@ -63,7 +97,7 @@ public async Task GetModerationChunkedAsync( { 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); @@ -76,39 +110,5 @@ public async Task GetModerationChunkedAsync( return false; } - - /// - /// Classifies if text violates OpenAI's Content Policy. - /// - /// - /// 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. - /// - /// Optional, . - /// - /// True, if the text has been flagged by the model as violating OpenAI's content policy. - /// - public async Task GetModerationAsync(string input, string model = null, CancellationToken cancellationToken = default) - { - var result = await CreateModerationAsync(new ModerationsRequest(input, model), cancellationToken).ConfigureAwait(false); - return result?.Results is { Count: not 0 } && result.Results.Any(moderationResult => moderationResult.Flagged); - } - - /// - /// Classifies if text violates OpenAI's Content Policy - /// - /// - /// Optional, . - public async Task CreateModerationAsync(ModerationsRequest request, CancellationToken cancellationToken = default) - { - var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false); - var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); - } } } diff --git a/OpenAI-DotNet/OpenAI-DotNet.csproj b/OpenAI-DotNet/OpenAI-DotNet.csproj index 3487e7f9..b7ba7909 100644 --- a/OpenAI-DotNet/OpenAI-DotNet.csproj +++ b/OpenAI-DotNet/OpenAI-DotNet.csproj @@ -17,9 +17,15 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet- https://github.com/RageAgainstThePixel/OpenAI-DotNet https://github.com/RageAgainstThePixel/OpenAI-DotNet OpenAI, AI, ML, API, gpt-4, gpt-3.5-tubo, gpt-3, chatGPT, chat-gpt, gpt-2, gpt, dall-e-2, dall-e-3 - OpenAI API - 7.3.7 - Version 7.3.7 + OpenAI-DotNet + 7.3.8 + Version 7.3.8 +- Added Chat.Content.ctr overloads and implicit casting for easier useage +- Internal refactoring of FilesEndpoint.DeleteFileAsync (better status checking) +- Internal refactoring of FineTuningEndpoint to ensure we're properly setting response data +- Updated unit tests +- Updated docs +Version 7.3.7 - Fixes streaming with tools not being property copied over Version 7.3.6 - Fixed ArgumentOutOfRangeException when streaming chat completion response contains more than one tool diff --git a/OpenAI-DotNet/Threads/ThreadsEndpoint.cs b/OpenAI-DotNet/Threads/ThreadsEndpoint.cs index c5f5fdca..b613f11b 100644 --- a/OpenAI-DotNet/Threads/ThreadsEndpoint.cs +++ b/OpenAI-DotNet/Threads/ThreadsEndpoint.cs @@ -12,7 +12,7 @@ namespace OpenAI.Threads /// public sealed class ThreadsEndpoint : BaseEndPoint { - public ThreadsEndpoint(OpenAIClient api) : base(api) { } + public ThreadsEndpoint(OpenAIClient client) : base(client) { } protected override string Root => "threads"; @@ -24,9 +24,9 @@ public ThreadsEndpoint(OpenAIClient api) : base(api) { } /// . public async Task CreateThreadAsync(CreateThreadRequest request = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.PostAsync(GetUrl(), request != null ? JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug) : null, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl(), request != null ? JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug) : null, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -37,9 +37,9 @@ public async Task CreateThreadAsync(CreateThreadRequest request /// . public async Task RetrieveThreadAsync(string threadId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -58,9 +58,9 @@ public async Task RetrieveThreadAsync(string threadId, Cancellat public async Task ModifyThreadAsync(string threadId, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(new { metadata }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{threadId}"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{threadId}"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -71,7 +71,7 @@ public async Task ModifyThreadAsync(string threadId, IReadOnlyDi /// True, if was successfully deleted. public async Task DeleteThreadAsync(string threadId, CancellationToken cancellationToken = default) { - var response = await Api.Client.DeleteAsync(GetUrl($"/{threadId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.DeleteAsync(GetUrl($"/{threadId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, OpenAIClient.JsonSerializationOptions)?.Deleted ?? false; } @@ -88,9 +88,9 @@ public async Task DeleteThreadAsync(string threadId, CancellationToken can public async Task CreateMessageAsync(string threadId, CreateMessageRequest request, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/messages"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{threadId}/messages"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -102,9 +102,9 @@ public async Task CreateMessageAsync(string threadId, CreateMes /// . public async Task> ListMessagesAsync(string threadId, ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages", query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/messages", query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } /// @@ -116,9 +116,9 @@ public async Task> ListMessagesAsync(string thread /// . public async Task RetrieveMessageAsync(string threadId, string messageId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -154,9 +154,9 @@ public async Task ModifyMessageAsync(MessageResponse message, I public async Task ModifyMessageAsync(string threadId, string messageId, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(new { metadata }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/messages/{messageId}"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{threadId}/messages/{messageId}"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } #endregion Messages @@ -173,9 +173,9 @@ public async Task ModifyMessageAsync(string threadId, string me /// . public async Task> ListFilesAsync(string threadId, string messageId, ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}/files", query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}/files", query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } /// @@ -188,9 +188,9 @@ public async Task> ListFilesAsync(string threa /// . public async Task RetrieveFileAsync(string threadId, string messageId, string fileId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}/files/{fileId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/messages/{messageId}/files/{fileId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } #endregion Files @@ -206,9 +206,9 @@ public async Task RetrieveFileAsync(string threadId, string /// public async Task> ListRunsAsync(string threadId, ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs", query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/runs", query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } /// @@ -222,14 +222,14 @@ public async Task CreateRunAsync(string threadId, CreateRunRequest { if (request == null || string.IsNullOrWhiteSpace(request.AssistantId)) { - var assistant = await Api.AssistantsEndpoint.CreateAssistantAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + var assistant = await client.AssistantsEndpoint.CreateAssistantAsync(cancellationToken: cancellationToken).ConfigureAwait(false); request = new CreateRunRequest(assistant, request); } var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{threadId}/runs"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -242,14 +242,14 @@ public async Task CreateThreadAndRunAsync(CreateThreadAndRunRequest { if (request == null || string.IsNullOrWhiteSpace(request.AssistantId)) { - var assistant = await Api.AssistantsEndpoint.CreateAssistantAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + var assistant = await client.AssistantsEndpoint.CreateAssistantAsync(cancellationToken: cancellationToken).ConfigureAwait(false); request = new CreateThreadAndRunRequest(assistant, request); } var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl("/runs"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl("/runs"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -261,9 +261,9 @@ public async Task CreateThreadAndRunAsync(CreateThreadAndRunRequest /// . public async Task RetrieveRunAsync(string threadId, string runId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -282,9 +282,9 @@ public async Task RetrieveRunAsync(string threadId, string runId, C public async Task ModifyRunAsync(string threadId, string runId, IReadOnlyDictionary metadata, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(new { metadata }, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -300,9 +300,9 @@ public async Task ModifyRunAsync(string threadId, string runId, IRe public async Task SubmitToolOutputsAsync(string threadId, string runId, SubmitToolOutputsRequest request, CancellationToken cancellationToken = default) { var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug); - var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}/submit_tool_outputs"), jsonContent, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}/submit_tool_outputs"), jsonContent, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -315,9 +315,9 @@ public async Task SubmitToolOutputsAsync(string threadId, string ru /// . public async Task> ListRunStepsAsync(string threadId, string runId, ListQuery query = null, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}/steps", query), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}/steps", query), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize>(responseAsString, Api); + return response.Deserialize>(responseAsString, client); } /// @@ -330,9 +330,9 @@ public async Task> ListRunStepsAsync(string thread /// . public async Task RetrieveRunStepAsync(string threadId, string runId, string stepId, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}/steps/{stepId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{threadId}/runs/{runId}/steps/{stepId}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } /// @@ -344,9 +344,9 @@ public async Task RetrieveRunStepAsync(string threadId, string /// . public async Task CancelRunAsync(string threadId, string runId, CancellationToken cancellationToken = default) { - var response = await Api.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}/cancel"), content: null, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{threadId}/runs/{runId}/cancel"), content: null, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); - return response.Deserialize(responseAsString, Api); + return response.Deserialize(responseAsString, client); } #endregion Runs diff --git a/README.md b/README.md index c2331ddb..305773ad 100644 --- a/README.md +++ b/README.md @@ -867,10 +867,7 @@ var messages = new List var chatRequest = new ChatRequest(messages); var response = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse => { - foreach (var choice in partialResponse.Choices.Where(choice => choice.Delta?.Content != null)) - { - Console.Write(choice.Delta.Content); - } + Console.Write(choice.Delta.ToString()); }); var choice = response.FirstChoice; Console.WriteLine($"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}"); @@ -902,6 +899,8 @@ Console.WriteLine(cumulativeDelta); #### [Chat Tools](https://platform.openai.com/docs/guides/function-calling) +> Only available with the latest 0613 model series! + ```csharp var api = new OpenAIClient(); var messages = new List @@ -912,7 +911,7 @@ var messages = new List foreach (var message in messages) { - Console.WriteLine($"{message.Role}: {message.Content}"); + Console.WriteLine($"{message.Role}: {message}"); } // Define the tools that the assistant is able to use: @@ -955,7 +954,7 @@ response = await api.ChatEndpoint.GetCompletionAsync(chatRequest); messages.Add(response.FirstChoice.Message); -if (!string.IsNullOrEmpty(response.FirstChoice)) +if (!string.IsNullOrEmpty(response.ToString())) { Console.WriteLine($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}"); @@ -988,6 +987,7 @@ Console.WriteLine($"{Role.Tool}: {functionResult}"); #### [Chat Vision](https://platform.openai.com/docs/guides/vision) > :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(); @@ -996,7 +996,7 @@ 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?"), + "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) }) }; @@ -1066,7 +1066,7 @@ var response = await api.AudioEndpoint.CreateTranscriptionAsync(request); Console.WriteLine(response); ``` -#### [Create Translation](https://platform.openai.com/docs/api-reference/audio/create) +#### [Create Translation](https://platform.openai.com/docs/api-reference/audio/createTranslation) Translates audio into into English. @@ -1230,7 +1230,7 @@ foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt))) } ``` -#### [Retrieve Fine Tune Job Info](https://platform.openai.com/docs/api-reference/fine-tunes/retrieve) +#### [Retrieve Fine Tune Job Info](https://platform.openai.com/docs/api-reference/fine-tuning/retrieve) Gets info about the fine-tune job. @@ -1240,7 +1240,7 @@ var job = await api.FineTuningEndpoint.GetJobInfoAsync(fineTuneJob); Console.WriteLine($"{job.Id} -> {job.CreatedAt} | {job.Status}"); ``` -#### [Cancel Fine Tune Job](https://platform.openai.com/docs/api-reference/fine-tunes/cancel) +#### [Cancel Fine Tune Job](https://platform.openai.com/docs/api-reference/fine-tuning/cancel) Immediately cancel a fine-tune job. From bc1dabae1a16dbd163d6dbbb3442d5c5e22e197d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 29 Nov 2023 00:08:10 -0500 Subject: [PATCH 2/3] updated unit tests --- OpenAI-DotNet-Tests/TestFixture_05_Images.cs | 12 ++++++------ OpenAI-DotNet-Tests/TestFixture_08_Files.cs | 2 +- OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs | 3 +-- OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/OpenAI-DotNet-Tests/TestFixture_05_Images.cs b/OpenAI-DotNet-Tests/TestFixture_05_Images.cs index c74202e9..a6c760c2 100644 --- a/OpenAI-DotNet-Tests/TestFixture_05_Images.cs +++ b/OpenAI-DotNet-Tests/TestFixture_05_Images.cs @@ -10,7 +10,7 @@ namespace OpenAI.Tests internal class TestFixture_05_Images : AbstractTestFixture { [Test] - public async Task Test_1_GenerateImages() + public async Task Test_01_01_GenerateImages() { Assert.IsNotNull(OpenAIClient.ImagesEndPoint); var request = new ImageGenerationRequest("A house riding a velociraptor", Model.DallE_3); @@ -27,7 +27,7 @@ public async Task Test_1_GenerateImages() } [Test] - public async Task Test_2_GenerateImages_B64_Json() + public async Task Test_01_02_GenerateImages_B64_Json() { Assert.IsNotNull(OpenAIClient.ImagesEndPoint); @@ -45,7 +45,7 @@ public async Task Test_2_GenerateImages_B64_Json() } [Test] - public async Task Test_3_GenerateImageEdits() + public async Task Test_02_01_CreateImageEdit() { Assert.IsNotNull(OpenAIClient.ImagesEndPoint); @@ -66,7 +66,7 @@ public async Task Test_3_GenerateImageEdits() } [Test] - public async Task Test_4_GenerateImageEdits_B64_Json() + public async Task Test_02_02_CreateImageEdit_B64_Json() { Assert.IsNotNull(OpenAIClient.ImagesEndPoint); @@ -87,7 +87,7 @@ public async Task Test_4_GenerateImageEdits_B64_Json() } [Test] - public async Task Test_5_GenerateImageVariations() + public async Task Test_03_01_CreateImageVariation() { Assert.IsNotNull(OpenAIClient.ImagesEndPoint); @@ -106,7 +106,7 @@ public async Task Test_5_GenerateImageVariations() } [Test] - public async Task Test_6_GenerateImageVariations_B64_Json() + public async Task Test_03_02_CreateImageVariation_B64_Json() { Assert.IsNotNull(OpenAIClient.ImagesEndPoint); diff --git a/OpenAI-DotNet-Tests/TestFixture_08_Files.cs b/OpenAI-DotNet-Tests/TestFixture_08_Files.cs index a48a5919..160264dd 100644 --- a/OpenAI-DotNet-Tests/TestFixture_08_Files.cs +++ b/OpenAI-DotNet-Tests/TestFixture_08_Files.cs @@ -15,7 +15,7 @@ public async Task Test_01_UploadFile() { 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", JsonSerializer.Serialize(testData, OpenAIClient.JsonSerializationOptions)); + await File.WriteAllTextAsync("test.jsonl", testData); Assert.IsTrue(File.Exists("test.jsonl")); var result = await OpenAIClient.FilesEndpoint.UploadFileAsync("test.jsonl", "fine-tune"); Assert.IsNotNull(result); diff --git a/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs b/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs index 2810a440..3803b07f 100644 --- a/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs +++ b/OpenAI-DotNet-Tests/TestFixture_09_FineTuning.cs @@ -80,7 +80,7 @@ private async Task CreateTestTrainingDataAsync() }) }; const string localTrainingDataPath = "fineTunesTestTrainingData.jsonl"; - await File.WriteAllLinesAsync(localTrainingDataPath, conversations.Select(conversation => JsonSerializer.Serialize(conversation, OpenAIClient.JsonSerializationOptions))); + await File.WriteAllLinesAsync(localTrainingDataPath, conversations.Select(conversation => conversation.ToString())); var fileData = await OpenAIClient.FilesEndpoint.UploadFileAsync(localTrainingDataPath, "fine-tune"); File.Delete(localTrainingDataPath); @@ -188,7 +188,6 @@ public async Task Test_05_CancelFineTuneJob() public async Task Test_06_DeleteFineTunedModel() { Assert.IsNotNull(OpenAIClient.ModelsEndpoint); - var models = await OpenAIClient.ModelsEndpoint.GetModelsAsync(); Assert.IsNotNull(models); Assert.IsNotEmpty(models); diff --git a/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs b/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs index 1cfa4112..632d30a9 100644 --- a/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs +++ b/OpenAI-DotNet-Tests/TestFixture_10_Moderations.cs @@ -18,7 +18,7 @@ public async Task Test_01_Moderate() [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); Console.WriteLine(response.Results?[0]?.Scores?.ToString()); From 996f165d719283ae876f09f088f1395f69e829d2 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 29 Nov 2023 12:14:22 -0500 Subject: [PATCH 3/3] fixed typo --- OpenAI-DotNet/OpenAI-DotNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenAI-DotNet/OpenAI-DotNet.csproj b/OpenAI-DotNet/OpenAI-DotNet.csproj index b7ba7909..80f8c9ab 100644 --- a/OpenAI-DotNet/OpenAI-DotNet.csproj +++ b/OpenAI-DotNet/OpenAI-DotNet.csproj @@ -20,7 +20,7 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet- OpenAI-DotNet 7.3.8 Version 7.3.8 -- Added Chat.Content.ctr overloads and implicit casting for easier useage +- Added Chat.Content.ctr overloads and implicit casting for easier usage - Internal refactoring of FilesEndpoint.DeleteFileAsync (better status checking) - Internal refactoring of FineTuningEndpoint to ensure we're properly setting response data - Updated unit tests