diff --git a/OpenAI.Playground/Program.cs b/OpenAI.Playground/Program.cs index dac9d7e5..2b8f0e8c 100644 --- a/OpenAI.Playground/Program.cs +++ b/OpenAI.Playground/Program.cs @@ -43,15 +43,16 @@ // |-----------------------------------------------------------------------| await ChatCompletionTestHelper.RunSimpleChatCompletionTest(sdk); +//await ChatCompletionTestHelper.RunSimpleCompletionStreamTest(sdk); + // Vision //await VisionTestHelper.RunSimpleVisionTest(sdk); //await VisionTestHelper.RunSimpleVisionStreamTest(sdk); //await VisionTestHelper.RunSimpleVisionTestUsingBase64EncodedImage(sdk); -//await ChatCompletionTestHelper.RunSimpleCompletionStreamTest(sdk); +// Tools //await ChatCompletionTestHelper.RunChatFunctionCallTest(sdk); //await ChatCompletionTestHelper.RunChatFunctionCallTestAsStream(sdk); -//await FineTuningJobTestHelper.RunCaseStudyIsTheModelMakingUntrueStatements(sdk); // Whisper //await AudioTestHelper.RunSimpleAudioCreateTranscriptionTest(sdk); @@ -73,9 +74,11 @@ //await EmbeddingTestHelper.RunSimpleEmbeddingTest(sdk); //////await FileTestHelper.RunSimpleFileTest(sdk); //will delete all of your files //////await FineTuningTestHelper.CleanUpAllFineTunings(sdk); //!!!!! will delete all fine-tunings +//await FineTuningJobTestHelper.RunCaseStudyIsTheModelMakingUntrueStatements(sdk); //await FineTuningTestHelper.RunCaseStudyIsTheModelMakingUntrueStatements(sdk); //await TokenizerTestHelper.RunTokenizerTest(); //await TokenizerTestHelper.RunTokenizerCountTest(); //await TokenizerTestHelper.RunTokenizerTestCrClean(); +Console.WriteLine("Press any key to exit..."); Console.ReadLine(); \ No newline at end of file diff --git a/OpenAI.Playground/TestHelpers/ImageTestHelper.cs b/OpenAI.Playground/TestHelpers/ImageTestHelper.cs index 1f6ffda1..b90fa7ee 100644 --- a/OpenAI.Playground/TestHelpers/ImageTestHelper.cs +++ b/OpenAI.Playground/TestHelpers/ImageTestHelper.cs @@ -16,8 +16,8 @@ public static async Task RunSimpleCreateImageTest(IOpenAIService sdk) var imageResult = await sdk.Image.CreateImage(new ImageCreateRequest { Prompt = "Laser cat eyes", - N = 2, - Size = StaticValues.ImageStatics.Size.Size256, + N = 1, + Size = StaticValues.ImageStatics.Size.Size1024, ResponseFormat = StaticValues.ImageStatics.ResponseFormat.Url, User = "TestUser" }); diff --git a/OpenAI.SDK/Extensions/HttpclientExtensions.cs b/OpenAI.SDK/Extensions/HttpclientExtensions.cs index c1bfa78b..9fbe37ff 100644 --- a/OpenAI.SDK/Extensions/HttpclientExtensions.cs +++ b/OpenAI.SDK/Extensions/HttpclientExtensions.cs @@ -6,18 +6,23 @@ namespace OpenAI.Extensions; -public static class HttpClientExtensions +internal static class HttpClientExtensions { - public static async Task PostAndReadAsAsync(this HttpClient client, string uri, object? requestModel, CancellationToken cancellationToken = default) + public static async Task PostAndReadAsAsync(this HttpClient client, string uri, object? requestModel, CancellationToken cancellationToken = default) where TResponse : BaseResponse, new() { var response = await client.PostAsJsonAsync(uri, requestModel, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }, cancellationToken); - return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) ?? throw new InvalidOperationException(); + return await HandleResponseContent(response, cancellationToken); } - public static async Task PostAndReadAsDataAsync(this HttpClient client, string uri, object? requestModel, CancellationToken cancellationToken = default) where TResponse : DataBaseResponse, new() + public static string? GetHeaderValue(this HttpResponseHeaders headers, string headerName) + { + return headers.Contains(headerName) ? headers.GetValues(headerName).FirstOrDefault() : null; + } + + public static async Task PostAndReadAsDataAsync(this HttpClient client, string uri, object? requestModel, CancellationToken cancellationToken = default) where TResponse : DataBaseResponse, new() { var response = await client.PostAsJsonAsync(uri, requestModel, new JsonSerializerOptions { @@ -26,7 +31,7 @@ public static async Task PostAndReadAsAsync(this HttpClien if (!response.IsSuccessStatusCode) { - return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) ?? throw new InvalidOperationException(); + return await HandleResponseContent(response, cancellationToken); } TData data; @@ -47,7 +52,7 @@ public static async Task PostAndReadAsAsync(this HttpClien throw new NotSupportedException("Unsupported type for TData"); } - return new TResponse { Data = data }; + return new() { Data = data }; } @@ -88,16 +93,16 @@ private static HttpResponseMessage SendRequestPreNet6(HttpClient client, HttpReq private static HttpRequestMessage CreatePostEventStreamRequest(string uri, HttpContent content) { var request = new HttpRequestMessage(HttpMethod.Post, uri); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); + request.Headers.Accept.Add(new("text/event-stream")); request.Content = content; return request; } - public static async Task PostFileAndReadAsAsync(this HttpClient client, string uri, HttpContent content, CancellationToken cancellationToken = default) + public static async Task PostFileAndReadAsAsync(this HttpClient client, string uri, HttpContent content, CancellationToken cancellationToken = default) where TResponse : BaseResponse, new() { var response = await client.PostAsync(uri, content, cancellationToken); - return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) ?? throw new InvalidOperationException(); + return await HandleResponseContent(response, cancellationToken); } public static async Task PostFileAndReadAsStringAsync(this HttpClient client, string uri, HttpContent content, CancellationToken cancellationToken = default) @@ -106,10 +111,71 @@ public static async Task PostFileAndReadAsStringAsync(this HttpClient cl return await response.Content.ReadAsStringAsync(cancellationToken) ?? throw new InvalidOperationException(); } - public static async Task DeleteAndReadAsAsync(this HttpClient client, string uri, CancellationToken cancellationToken = default) + public static async Task DeleteAndReadAsAsync(this HttpClient client, string uri, CancellationToken cancellationToken = default) where TResponse : BaseResponse, new() { var response = await client.DeleteAsync(uri, cancellationToken); - return await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) ?? throw new InvalidOperationException(); + return await HandleResponseContent(response, cancellationToken); + } + + private static async Task HandleResponseContent(this HttpResponseMessage response, CancellationToken cancellationToken) where TResponse : BaseResponse, new() + { + TResponse result; + + if (!response.Content.Headers.ContentType?.MediaType?.Equals("application/json", StringComparison.OrdinalIgnoreCase) ?? true) + { + result = new() + { + Error = new() + { + MessageObject = await response.Content.ReadAsStringAsync(cancellationToken) + } + }; + } + else + { + result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken) ?? + throw new InvalidOperationException(); + } + + result.HttpStatusCode = response.StatusCode; + result.HeaderValues = new() + { + Date = response.Headers.Date, + Connection = response.Headers.Connection?.ToString(), + AccessControlAllowOrigin = response.Headers.GetHeaderValue("access-control-allow-origin"), + CacheControl = response.Headers.GetHeaderValue("cache-control"), + Vary = response.Headers.Vary?.ToString(), + XRequestId = response.Headers.GetHeaderValue("x-request-id"), + StrictTransportSecurity = response.Headers.GetHeaderValue("strict-transport-security"), + CFCacheStatus = response.Headers.GetHeaderValue("cf-cache-status"), + SetCookie = response.Headers.Contains("set-cookie") ? response.Headers.GetValues("set-cookie").ToList() : null, + Server = response.Headers.Server?.ToString(), + CF_RAY = response.Headers.GetHeaderValue("cf-ray"), + AltSvc = response.Headers.GetHeaderValue("alt-svc"), + All = response.Headers.ToDictionary(x => x.Key, x => x.Value.AsEnumerable()), + + RateLimits = new() + { + LimitRequests = response.Headers.GetHeaderValue("x-ratelimit-limit-requests"), + LimitTokens = response.Headers.GetHeaderValue("x-ratelimit-limit-tokens"), + LimitTokensUsageBased = response.Headers.GetHeaderValue("x-ratelimit-limit-tokens_usage_based"), + RemainingRequests = response.Headers.GetHeaderValue("x-ratelimit-remaining-requests"), + RemainingTokens = response.Headers.GetHeaderValue("x-ratelimit-remaining-tokens"), + RemainingTokensUsageBased = response.Headers.GetHeaderValue("x-ratelimit-remaining-tokens_usage_based"), + ResetRequests = response.Headers.GetHeaderValue("x-ratelimit-reset-requests"), + ResetTokens = response.Headers.GetHeaderValue("x-ratelimit-reset-tokens"), + ResetTokensUsageBased = response.Headers.GetHeaderValue("x-ratelimit-reset-tokens_usage_based") + }, + + OpenAI = new() + { + Model = response.Headers.GetHeaderValue("openai-model"), + Organization = response.Headers.GetHeaderValue("openai-organization"), + ProcessingMs = response.Headers.GetHeaderValue("openai-processing-ms"), + Version = response.Headers.GetHeaderValue("openai-version") + } + }; + return result; } #if NETSTANDARD2_0 diff --git a/OpenAI.SDK/ObjectModels/RequestModels/AudioCreateSpeechRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/AudioCreateSpeechRequest.cs index 909b8eab..929eddc1 100644 --- a/OpenAI.SDK/ObjectModels/RequestModels/AudioCreateSpeechRequest.cs +++ b/OpenAI.SDK/ObjectModels/RequestModels/AudioCreateSpeechRequest.cs @@ -27,7 +27,7 @@ public record AudioCreateSpeechRequest : IOpenAiModels.IModel /// The format to audio in. Supported formats are mp3, opus, aac, and flac /// Defaults to mp3 /// - [JsonPropertyName("responseFormat")] + [JsonPropertyName("response_format")] public string? ResponseFormat { get; set; } /// diff --git a/OpenAI.SDK/ObjectModels/ResponseModels/BaseResponse.cs b/OpenAI.SDK/ObjectModels/ResponseModels/BaseResponse.cs index ec61e543..be675868 100644 --- a/OpenAI.SDK/ObjectModels/ResponseModels/BaseResponse.cs +++ b/OpenAI.SDK/ObjectModels/ResponseModels/BaseResponse.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Net; +using System.Text.Json; using System.Text.Json.Serialization; namespace OpenAI.ObjectModels.ResponseModels; @@ -8,46 +9,55 @@ public record BaseResponse [JsonPropertyName("object")] public string? ObjectTypeName { get; set; } public bool Successful => Error == null; [JsonPropertyName("error")] public Error? Error { get; set; } + public HttpStatusCode HttpStatusCode { get; set; } + public ResponseHeaderValues? HeaderValues { get; set; } +} + +public record RateLimitInfo +{ + public string? LimitRequests { get; set; } + public string? LimitTokens { get; set; } + public string? LimitTokensUsageBased { get; set; } + public string? RemainingRequests { get; set; } + public string? RemainingTokens { get; set; } + public string? RemainingTokensUsageBased { get; set; } + public string? ResetRequests { get; set; } + public string? ResetTokens { get; set; } + public string? ResetTokensUsageBased { get; set; } +} + +public record OpenAIInfo +{ + public string? Model { get; set; } + public string? Organization { get; set; } + public string? ProcessingMs { get; set; } + public string? Version { get; set; } +} + +public record ResponseHeaderValues +{ + public DateTimeOffset? Date { get; set; } + public string? Connection { get; set; } + public string? AccessControlAllowOrigin { get; set; } + public string? CacheControl { get; set; } + public string? Vary { get; set; } + public string? XRequestId { get; set; } + public string? StrictTransportSecurity { get; set; } + public string? CFCacheStatus { get; set; } + public List? SetCookie { get; set; } + public string? Server { get; set; } + public string? CF_RAY { get; set; } + public string? AltSvc { get; set; } + public Dictionary>? All { get; set; } + + public RateLimitInfo? RateLimits { get; set; } + public OpenAIInfo? OpenAI { get; set; } } public record DataBaseResponse : BaseResponse { [JsonPropertyName("data")] public T? Data { get; set; } } -//public record Error -//{ -// [JsonPropertyName("code")] public string? Code { get; set; } - -// [JsonPropertyName("message")] public object? MessageRaw { get; set; } -// [JsonIgnore] -// public List? Messages -// { -// get -// { -// if (MessageRaw?.GetType() == typeof(string)) -// { -// return new List {MessageRaw.ToString()!}; -// } -// return MessageRaw?.GetType() == typeof(List) ? (List) MessageRaw : null; -// } -// } -// [JsonIgnore] -// public string? Message -// { -// get -// { -// if (MessageRaw?.GetType() == typeof(string)) -// { -// return MessageRaw.ToString(); -// } -// return MessageRaw?.GetType() == typeof(List) ? string.Join(Environment.NewLine,(List) MessageRaw) : null; -// } -// } - -// [JsonPropertyName("param")] public string? Param { get; set; } - -// [JsonPropertyName("type")] public string? Type { get; set; } -//} public class Error { @@ -71,7 +81,7 @@ public object MessageObject { case string s: Message = s; - Messages = new List {s}; + Messages = new() { s }; break; case List list when list.All(i => i is JsonElement): Messages = list.Cast().Select(e => e.GetString()).ToList(); diff --git a/OpenAI.SDK/OpenAI.csproj b/OpenAI.SDK/OpenAI.csproj index 88c679df..a93ee9bd 100644 --- a/OpenAI.SDK/OpenAI.csproj +++ b/OpenAI.SDK/OpenAI.csproj @@ -11,7 +11,7 @@ OpenAI-Betalgo.png true OpenAI SDK by Betalgo - 7.4.2 + 7.4.3 Tolga Kayhan, Betalgo Betalgo Up Ltd. OpenAI ChatGPT, Whisper, GPT-4 and DALL·E dotnet SDK diff --git a/OpenAI.SDK/OpenAiOptions.cs b/OpenAI.SDK/OpenAiOptions.cs index 2bcdcaed..e268ff59 100644 --- a/OpenAI.SDK/OpenAiOptions.cs +++ b/OpenAI.SDK/OpenAiOptions.cs @@ -20,7 +20,7 @@ public class OpenAiOptions { private const string OpenAiDefaultApiVersion = "v1"; private const string OpenAiDefaultBaseDomain = "https://api.openai.com/"; - private const string AzureOpenAiDefaultApiVersion = "2023-09-01-preview"; + private const string AzureOpenAiDefaultApiVersion = "2023-12-01-preview"; /// diff --git a/OpenAI.sln.DotSettings b/OpenAI.sln.DotSettings index 1130bb79..b71f8592 100644 --- a/OpenAI.sln.DotSettings +++ b/OpenAI.sln.DotSettings @@ -12,5 +12,7 @@ True True True + True True + True True \ No newline at end of file diff --git a/Readme.md b/Readme.md index 9656859c..1fb61d0e 100644 --- a/Readme.md +++ b/Readme.md @@ -294,6 +294,12 @@ I will always be using the latest libraries, and future releases will frequently I am incredibly busy. If I forgot your name, please accept my apologies and let me know so I can add it to the list. ## Changelog +### 7.4.3 +- Fixed the response format of AudioCreateSpeechRequest. +- Updated Azure OpenAI version to `2023-12-01-preview`, which now supports dall-e 3. +- Added the ability to retrieve header values from the base response, such as ratelimit, etc. Please note that this feature is experimental and may change in the future. +- Semi-Breaking change: + - The SDK will now attempt to handle 500 errors and other similar errors from the OpenAI server. Previously, an exception was thrown in such cases. Now, the SDK will try to read the response and return it as an error message. This change provides more visibility to developers and helps them understand the cause of the error. ### 7.4.2 - Let's start with breaking changes: - OpenAI has replaced function calling with tools. We have made the necessary changes to our code. This is not a major change; now you just have a wrapper around your function calling, which is named as "tool". The Playground provides an example. Please take a look to see how you can update your code.