From 8b662961c15fec92193a8a645cf6a8e89b78c39d Mon Sep 17 00:00:00 2001 From: Ernie <ernestfrench@gmail.com> Date: Wed, 21 Feb 2024 12:15:16 -0800 Subject: [PATCH 1/5] Fix revised_prompt parameter name so it will be correctly deserialized. --- OpenAI_API/Images/ImageResult.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/OpenAI_API/Images/ImageResult.cs b/OpenAI_API/Images/ImageResult.cs index 5c9770e..d2f2aa0 100644 --- a/OpenAI_API/Images/ImageResult.cs +++ b/OpenAI_API/Images/ImageResult.cs @@ -51,10 +51,11 @@ public class Data [JsonProperty("b64_json")] public string Base64Data { get; set; } - /// <summary> - /// The prompt that was used to generate the image, if there was any revision to the prompt. - /// </summary> - public string RevisedPrompt { get; set; } + /// <summary> + /// The prompt that was used to generate the image, if there was any revision to the prompt. + /// </summary> + [JsonProperty("revised_prompt")] + public string RevisedPrompt { get; set; } } } From 0f4384be3627661b703ba85ba64c278e6e5ddd43 Mon Sep 17 00:00:00 2001 From: Ernie <ernestfrench@gmail.com> Date: Sun, 25 Feb 2024 10:14:15 -0800 Subject: [PATCH 2/5] whitespace --- OpenAI_API/Images/ImageResult.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenAI_API/Images/ImageResult.cs b/OpenAI_API/Images/ImageResult.cs index d2f2aa0..a871a47 100644 --- a/OpenAI_API/Images/ImageResult.cs +++ b/OpenAI_API/Images/ImageResult.cs @@ -52,9 +52,9 @@ public class Data public string Base64Data { get; set; } /// <summary> - /// The prompt that was used to generate the image, if there was any revision to the prompt. - /// </summary> - [JsonProperty("revised_prompt")] + /// The prompt that was used to generate the image, if there was any revision to the prompt. + /// </summary> + [JsonProperty("revised_prompt")] public string RevisedPrompt { get; set; } } From 85da80bf87b58a850d51280785bf59a110f0ac89 Mon Sep 17 00:00:00 2001 From: Ernie <ernestfrench@gmail.com> Date: Sun, 25 Feb 2024 10:26:26 -0800 Subject: [PATCH 3/5] formatting --- OpenAI_API/Images/ImageResult.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenAI_API/Images/ImageResult.cs b/OpenAI_API/Images/ImageResult.cs index a871a47..17e6e87 100644 --- a/OpenAI_API/Images/ImageResult.cs +++ b/OpenAI_API/Images/ImageResult.cs @@ -49,13 +49,13 @@ public class Data /// The base64-encoded image data as returned by the API /// </summary> [JsonProperty("b64_json")] - public string Base64Data { get; set; } + public string Base64Data { get; set; } - /// <summary> + /// <summary> /// The prompt that was used to generate the image, if there was any revision to the prompt. /// </summary> [JsonProperty("revised_prompt")] - public string RevisedPrompt { get; set; } + public string RevisedPrompt { get; set; } } } From 4fce673a82223110d188212e43f23c491ac6722d Mon Sep 17 00:00:00 2001 From: Ernie <ernestfrench@gmail.com> Date: Fri, 1 Mar 2024 20:20:26 -0800 Subject: [PATCH 4/5] fix a few little things, icon location, reporting etc. --- OpenAI_API/EndpointBase.cs | 785 ++++++++++++++++--------------- OpenAI_API/Images/ImageResult.cs | 2 +- 2 files changed, 397 insertions(+), 390 deletions(-) diff --git a/OpenAI_API/EndpointBase.cs b/OpenAI_API/EndpointBase.cs index d981c7e..3a27fa4 100644 --- a/OpenAI_API/EndpointBase.cs +++ b/OpenAI_API/EndpointBase.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; + using System; using System.Collections.Generic; using System.Diagnostics; @@ -11,233 +12,233 @@ namespace OpenAI_API { - /// <summary> - /// A base object for any OpenAI API endpoint, encompassing common functionality - /// </summary> - public abstract class EndpointBase - { - private const string UserAgent = "okgodoit/dotnet_openai_api"; - - /// <summary> - /// The internal reference to the API, mostly used for authentication - /// </summary> - protected readonly OpenAIAPI _Api; - - /// <summary> - /// Constructor of the api endpoint base, to be called from the contructor of any devived classes. Rather than instantiating any endpoint yourself, access it through an instance of <see cref="OpenAIAPI"/>. - /// </summary> - /// <param name="api"></param> - internal EndpointBase(OpenAIAPI api) - { - this._Api = api; - } - - /// <summary> - /// The name of the endpoint, which is the final path segment in the API URL. Must be overriden in a derived class. - /// </summary> - protected abstract string Endpoint { get; } - - /// <summary> - /// Gets the URL of the endpoint, based on the base OpenAI API URL followed by the endpoint name. For example "https://api.openai.com/v1/completions" - /// </summary> - protected string Url - { - get - { - return string.Format(_Api.ApiUrlFormat, _Api.ApiVersion, Endpoint); - } - } - - /// <summary> - /// Gets an HTTPClient with the appropriate authorization and other headers set - /// </summary> - /// <returns>The fully initialized HttpClient</returns> - /// <exception cref="AuthenticationException">Thrown if there is no valid authentication. Please refer to <see href="https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication"/> for details.</exception> - protected HttpClient GetClient() - { - if (_Api.Auth?.ApiKey is null) - { - throw new AuthenticationException("You must provide API authentication. Please refer to https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication for details."); - } - - HttpClient client; - var clientFactory = _Api.HttpClientFactory; - if (clientFactory != null) - { - client = clientFactory.CreateClient(); - } - else - { - client = new HttpClient(); - } - - client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _Api.Auth.ApiKey); - // Further authentication-header used for Azure openAI service - client.DefaultRequestHeaders.Add("api-key", _Api.Auth.ApiKey); - client.DefaultRequestHeaders.Add("User-Agent", UserAgent); - if (!string.IsNullOrEmpty(_Api.Auth.OpenAIOrganization)) client.DefaultRequestHeaders.Add("OpenAI-Organization", _Api.Auth.OpenAIOrganization); - - return client; - } - - /// <summary> - /// Formats a human-readable error message relating to calling the API and parsing the response - /// </summary> - /// <param name="resultAsString">The full content returned in the http response</param> - /// <param name="response">The http response object itself</param> - /// <param name="name">The name of the endpoint being used</param> - /// <param name="description">Additional details about the endpoint of this request (optional)</param> - /// <returns>A human-readable string error message.</returns> - protected string GetErrorMessage(string resultAsString, HttpResponseMessage response, string name, string description = "") - { - return $"Error at {name} ({description}) with HTTP status code: {response.StatusCode}. Content: {resultAsString ?? "<no content>"}"; - } - - - /// <summary> - /// Sends an HTTP request and returns the response. Does not do any parsing, but does do error handling. - /// </summary> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <param name="streaming">(optional) If true, streams the response. Otherwise waits for the entire response before returning.</param> - /// <returns>The HttpResponseMessage of the response, which is confirmed to be successful.</returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> - private async Task<HttpResponseMessage> HttpRequestRaw(string url = null, HttpMethod verb = null, object postData = null, bool streaming = false) - { - if (string.IsNullOrEmpty(url)) - url = this.Url; - - if (verb == null) - verb = HttpMethod.Get; - - using var client = GetClient(); - - HttpResponseMessage response = null; - string resultAsString = null; - HttpRequestMessage req = new HttpRequestMessage(verb, url); - - if (postData != null) - { - if (postData is HttpContent) - { - req.Content = postData as HttpContent; - } - else - { - string jsonContent = JsonConvert.SerializeObject(postData, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); - var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json"); - req.Content = stringContent; - } - } - response = await client.SendAsync(req, streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); - - if (response.IsSuccessStatusCode) - { - return response; - } - else - { - try - { - resultAsString = await response.Content.ReadAsStringAsync(); - } - catch (Exception readError) - { - resultAsString = "Additionally, the following error was thrown when attemping to read the response content: " + readError.ToString(); - } - - if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) - { - throw new AuthenticationException("OpenAI rejected your authorization, most likely due to an invalid API Key. Try checking your API Key and see https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication for guidance. Full API response follows: " + resultAsString); - } - else if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) - { - throw new HttpRequestException("OpenAI had an internal server error, which can happen occasionally. Please retry your request. " + GetErrorMessage(resultAsString, response, Endpoint, url)); - } - else - { - var errorToThrow = new HttpRequestException(GetErrorMessage(resultAsString, response, Endpoint, url)); - - var parsedError = JsonConvert.DeserializeObject<ApiErrorResponse>(resultAsString); - try - { - errorToThrow.Data.Add("message", parsedError.Error.Message); - errorToThrow.Data.Add("type", parsedError.Error.ErrorType); - errorToThrow.Data.Add("param", parsedError.Error.Parameter); - errorToThrow.Data.Add("code", parsedError.Error.ErrorCode); - } - catch (Exception parsingError) - { - throw new HttpRequestException(errorToThrow.Message, parsingError); - } - throw errorToThrow; - } - } - } - - /// <summary> - /// Sends an HTTP Get request and return the string content of the response without parsing, and does error handling. - /// </summary> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <returns>The text string of the response, which is confirmed to be successful.</returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> - internal async Task<string> HttpGetContent(string url = null, HttpMethod verb = null, object postData = null) - { - var response = await HttpRequestRaw(url, verb, postData); - return await response.Content.ReadAsStringAsync(); - } - - /// <summary> - /// Sends an HTTP request and return the raw content stream of the response without parsing, and does error handling. - /// </summary> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <returns>The response content stream, which is confirmed to be successful.</returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> - internal async Task<Stream> HttpRequest(string url = null, HttpMethod verb = null, object postData = null) - { - var response = await HttpRequestRaw(url, verb, postData); - return await response.Content.ReadAsStreamAsync(); - } - - - /// <summary> - /// Sends an HTTP Request and does initial parsing - /// </summary> - /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> - private async Task<T> HttpRequest<T>(string url = null, HttpMethod verb = null, object postData = null) where T : ApiResultBase - { - var response = await HttpRequestRaw(url, verb, postData); - string resultAsString = await response.Content.ReadAsStringAsync(); - - var res = JsonConvert.DeserializeObject<T>(resultAsString); - try - { - res.Organization = response.Headers.GetValues("Openai-Organization").FirstOrDefault(); - res.RequestId = response.Headers.GetValues("X-Request-ID").FirstOrDefault(); - res.ProcessingTime = TimeSpan.FromMilliseconds(int.Parse(response.Headers.GetValues("Openai-Processing-Ms").First())); - res.OpenaiVersion = response.Headers.GetValues("Openai-Version").FirstOrDefault(); - if (string.IsNullOrEmpty(res.Model)) - res.Model = response.Headers.GetValues("Openai-Model").FirstOrDefault(); - } - catch (Exception e) - { - Debug.Print($"Issue parsing metadata of OpenAi Response. Url: {url}, Error: {e.ToString()}, Response: {resultAsString}. This is probably ignorable."); - } - - return res; - } - - /* + /// <summary> + /// A base object for any OpenAI API endpoint, encompassing common functionality + /// </summary> + public abstract class EndpointBase + { + private const string UserAgent = "okgodoit/dotnet_openai_api"; + + /// <summary> + /// The internal reference to the API, mostly used for authentication + /// </summary> + protected readonly OpenAIAPI _Api; + + /// <summary> + /// Constructor of the api endpoint base, to be called from the contructor of any devived classes. Rather than instantiating any endpoint yourself, access it through an instance of <see cref="OpenAIAPI"/>. + /// </summary> + /// <param name="api"></param> + internal EndpointBase(OpenAIAPI api) + { + this._Api = api; + } + + /// <summary> + /// The name of the endpoint, which is the final path segment in the API URL. Must be overriden in a derived class. + /// </summary> + protected abstract string Endpoint { get; } + + /// <summary> + /// Gets the URL of the endpoint, based on the base OpenAI API URL followed by the endpoint name. For example "https://api.openai.com/v1/completions" + /// </summary> + protected string Url + { + get + { + return string.Format(_Api.ApiUrlFormat, _Api.ApiVersion, Endpoint); + } + } + + /// <summary> + /// Gets an HTTPClient with the appropriate authorization and other headers set + /// </summary> + /// <returns>The fully initialized HttpClient</returns> + /// <exception cref="AuthenticationException">Thrown if there is no valid authentication. Please refer to <see href="https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication"/> for details.</exception> + protected HttpClient GetClient() + { + if (_Api.Auth?.ApiKey is null) + { + throw new AuthenticationException("You must provide API authentication. Please refer to https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication for details."); + } + + HttpClient client; + var clientFactory = _Api.HttpClientFactory; + if (clientFactory != null) + { + client = clientFactory.CreateClient(); + } + else + { + client = new HttpClient(); + } + + client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _Api.Auth.ApiKey); + // Further authentication-header used for Azure openAI service + client.DefaultRequestHeaders.Add("api-key", _Api.Auth.ApiKey); + client.DefaultRequestHeaders.Add("User-Agent", UserAgent); + if (!string.IsNullOrEmpty(_Api.Auth.OpenAIOrganization)) client.DefaultRequestHeaders.Add("OpenAI-Organization", _Api.Auth.OpenAIOrganization); + + return client; + } + + /// <summary> + /// Formats a human-readable error message relating to calling the API and parsing the response + /// </summary> + /// <param name="resultAsString">The full content returned in the http response</param> + /// <param name="response">The http response object itself</param> + /// <param name="name">The name of the endpoint being used</param> + /// <param name="description">Additional details about the endpoint of this request (optional)</param> + /// <returns>A human-readable string error message.</returns> + protected string GetErrorMessage(string resultAsString, HttpResponseMessage response, string name, string description = "") + { + return $"Error at {name} ({description}) with HTTP status code: {response.StatusCode}. Content: {resultAsString ?? "<no content>"}"; + } + + /// <summary> + /// Sends an HTTP request and returns the response. Does not do any parsing, but does do error handling. + /// </summary> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <param name="streaming">(optional) If true, streams the response. Otherwise waits for the entire response before returning.</param> + /// <returns>The HttpResponseMessage of the response, which is confirmed to be successful.</returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> + private async Task<HttpResponseMessage> HttpRequestRaw(string url = null, HttpMethod verb = null, object postData = null, bool streaming = false) + { + if (string.IsNullOrEmpty(url)) + url = this.Url; + + if (verb == null) + verb = HttpMethod.Get; + + using var client = GetClient(); + + HttpResponseMessage response = null; + string resultAsString = null; + HttpRequestMessage req = new HttpRequestMessage(verb, url); + + if (postData != null) + { + if (postData is HttpContent) + { + req.Content = postData as HttpContent; + } + else + { + string jsonContent = JsonConvert.SerializeObject(postData, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); + var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json"); + req.Content = stringContent; + } + } + response = await client.SendAsync(req, streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); + + if (response.IsSuccessStatusCode) + { + return response; + } + else + { + try + { + resultAsString = await response.Content.ReadAsStringAsync(); + } + catch (Exception readError) + { + resultAsString = "Additionally, the following error was thrown when attemping to read the response content: " + readError.ToString(); + } + + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + throw new AuthenticationException("OpenAI rejected your authorization, most likely due to an invalid API Key. Try checking your API Key and see https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication for guidance. Full API response follows: " + resultAsString); + } + else if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) + { + throw new HttpRequestException("OpenAI had an internal server error, which can happen occasionally. Please retry your request. " + GetErrorMessage(resultAsString, response, Endpoint, url)); + } + else + { + var errorToThrow = new HttpRequestException(GetErrorMessage(resultAsString, response, Endpoint, url)); + + var parsedError = JsonConvert.DeserializeObject<ApiErrorResponse>(resultAsString); + try + { + errorToThrow.Data.Add("message", parsedError.Error.Message); + errorToThrow.Data.Add("type", parsedError.Error.ErrorType); + errorToThrow.Data.Add("param", parsedError.Error.Parameter); + errorToThrow.Data.Add("code", parsedError.Error.ErrorCode); + } + catch (Exception parsingError) + { + throw new HttpRequestException(errorToThrow.Message, parsingError); + } + throw errorToThrow; + } + } + } + + /// <summary> + /// Sends an HTTP Get request and return the string content of the response without parsing, and does error handling. + /// </summary> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <returns>The text string of the response, which is confirmed to be successful.</returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> + internal async Task<string> HttpGetContent(string url = null, HttpMethod verb = null, object postData = null) + { + var response = await HttpRequestRaw(url, verb, postData); + return await response.Content.ReadAsStringAsync(); + } + + /// <summary> + /// Sends an HTTP request and return the raw content stream of the response without parsing, and does error handling. + /// </summary> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <returns>The response content stream, which is confirmed to be successful.</returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> + internal async Task<Stream> HttpRequest(string url = null, HttpMethod verb = null, object postData = null) + { + var response = await HttpRequestRaw(url, verb, postData); + return await response.Content.ReadAsStreamAsync(); + } + + /// <summary> + /// Sends an HTTP Request and does initial parsing + /// </summary> + /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> + private async Task<T> HttpRequest<T>(string url = null, HttpMethod verb = null, object postData = null) where T : ApiResultBase + { + var response = await HttpRequestRaw(url, verb, postData); + string resultAsString = await response.Content.ReadAsStringAsync(); + + var res = JsonConvert.DeserializeObject<T>(resultAsString); + + try + { + + res.Organization = response.Headers.GetValues("Openai-Organization").FirstOrDefault(); + res.RequestId = response.Headers.GetValues("X-Request-ID").FirstOrDefault(); + res.ProcessingTime = TimeSpan.FromMilliseconds(int.Parse(response.Headers.GetValues("Openai-Processing-Ms").First())); + res.OpenaiVersion = response.Headers.GetValues("Openai-Version").FirstOrDefault(); + if (string.IsNullOrEmpty(res.Model)) + res.Model = response.Headers.GetValues("Openai-Model").FirstOrDefault(); + } + catch (Exception e) + { + Debug.Print($"Issue parsing metadata of OpenAi Response. Url: {url}, Error: {e.ToString()}, Response: {resultAsString}. This is probably ignorable."); + } + + return res; + } + + /* /// <summary> /// Sends an HTTP Request, supporting a streaming response /// </summary> @@ -271,61 +272,58 @@ private async Task<T> StreamingHttpRequest<T>(string url = null, HttpMethod verb } */ - /// <summary> - /// Sends an HTTP Get request and does initial parsing - /// </summary> - /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> - internal async Task<T> HttpGet<T>(string url = null) where T : ApiResultBase - { - return await HttpRequest<T>(url, HttpMethod.Get); - } - - /// <summary> - /// Sends an HTTP Post request and does initial parsing - /// </summary> - /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> - internal async Task<T> HttpPost<T>(string url = null, object postData = null) where T : ApiResultBase - { - return await HttpRequest<T>(url, HttpMethod.Post, postData); - } - - /// <summary> - /// Sends an HTTP Delete request and does initial parsing - /// </summary> - /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> - internal async Task<T> HttpDelete<T>(string url = null, object postData = null) where T : ApiResultBase - { - return await HttpRequest<T>(url, HttpMethod.Delete, postData); - } - - - /// <summary> - /// Sends an HTTP Put request and does initial parsing - /// </summary> - /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> - internal async Task<T> HttpPut<T>(string url = null, object postData = null) where T : ApiResultBase - { - return await HttpRequest<T>(url, HttpMethod.Put, postData); - } - - - - /* + /// <summary> + /// Sends an HTTP Get request and does initial parsing + /// </summary> + /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> + internal async Task<T> HttpGet<T>(string url = null) where T : ApiResultBase + { + return await HttpRequest<T>(url, HttpMethod.Get); + } + + /// <summary> + /// Sends an HTTP Post request and does initial parsing + /// </summary> + /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> + internal async Task<T> HttpPost<T>(string url = null, object postData = null) where T : ApiResultBase + { + return await HttpRequest<T>(url, HttpMethod.Post, postData); + } + + /// <summary> + /// Sends an HTTP Delete request and does initial parsing + /// </summary> + /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> + internal async Task<T> HttpDelete<T>(string url = null, object postData = null) where T : ApiResultBase + { + return await HttpRequest<T>(url, HttpMethod.Delete, postData); + } + + /// <summary> + /// Sends an HTTP Put request and does initial parsing + /// </summary> + /// <typeparam name="T">The <see cref="ApiResultBase"/>-derived class for the result</typeparam> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <returns>An awaitable Task with the parsed result of type <typeparamref name="T"/></returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.</exception> + internal async Task<T> HttpPut<T>(string url = null, object postData = null) where T : ApiResultBase + { + return await HttpRequest<T>(url, HttpMethod.Put, postData); + } + + /* /// <summary> /// Sends an HTTP request and handles a streaming response. Does basic line splitting and error handling. /// </summary> @@ -359,111 +357,120 @@ private async IAsyncEnumerable<string> HttpStreamingRequestRaw(string url = null } */ - - /// <summary> - /// Sends an HTTP request and handles a streaming response. Does basic line splitting and error handling. - /// </summary> - /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> - /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> - /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> - /// <returns>The HttpResponseMessage of the response, which is confirmed to be successful.</returns> - /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> - protected async IAsyncEnumerable<T> HttpStreamingRequest<T>(string url = null, HttpMethod verb = null, object postData = null) where T : ApiResultBase - { - var response = await HttpRequestRaw(url, verb, postData, true); - - string organization = null; - string requestId = null; - TimeSpan processingTime = TimeSpan.Zero; - string openaiVersion = null; - string modelFromHeaders = null; - - try - { - organization = response.Headers.GetValues("Openai-Organization").FirstOrDefault(); - requestId = response.Headers.GetValues("X-Request-ID").FirstOrDefault(); - processingTime = TimeSpan.FromMilliseconds(int.Parse(response.Headers.GetValues("Openai-Processing-Ms").First())); - openaiVersion = response.Headers.GetValues("Openai-Version").FirstOrDefault(); - modelFromHeaders = response.Headers.GetValues("Openai-Model").FirstOrDefault(); - } - catch (Exception e) - { - Debug.Print($"Issue parsing metadata of OpenAi Response. Url: {url}, Error: {e.ToString()}. This is probably ignorable."); - } - - string resultAsString = ""; - - using (var stream = await response.Content.ReadAsStreamAsync()) - using (StreamReader reader = new StreamReader(stream)) - { - string line; - while ((line = await reader.ReadLineAsync()) != null) - { - resultAsString += line + Environment.NewLine; - - if (line.StartsWith("data:")) - line = line.Substring("data:".Length); - - line = line.TrimStart(); - - if (line == "[DONE]") - { - yield break; - } - else if (line.StartsWith(":")) - { } - else if (!string.IsNullOrWhiteSpace(line)) - { - var res = JsonConvert.DeserializeObject<T>(line); - - res.Organization = organization; - res.RequestId = requestId; - res.ProcessingTime = processingTime; - res.OpenaiVersion = openaiVersion; - if (string.IsNullOrEmpty(res.Model)) - res.Model = modelFromHeaders; - - yield return res; - } - } - } - } - - internal class ApiErrorResponse - { - /// <summary> - /// The error details - /// </summary> - [JsonProperty("error")] - public ApiErrorResponseError Error { get; set; } - } - internal class ApiErrorResponseError - { - /// <summary> - /// The error message - /// </summary> - [JsonProperty("message")] - - public string Message { get; set; } - - /// <summary> - /// The type of error - /// </summary> - [JsonProperty("type")] - public string ErrorType { get; set; } - - /// <summary> - /// The parameter that caused the error - /// </summary> - [JsonProperty("param")] - - public string Parameter { get; set; } - - /// <summary> - /// The error code - /// </summary> - [JsonProperty("code")] - public string ErrorCode { get; set; } - } - } + /// <summary> + /// Sends an HTTP request and handles a streaming response. Does basic line splitting and error handling. + /// </summary> + /// <param name="url">(optional) If provided, overrides the url endpoint for this request. If omitted, then <see cref="Url"/> will be used.</param> + /// <param name="verb">(optional) The HTTP verb to use, for example "<see cref="HttpMethod.Get"/>". If omitted, then "GET" is assumed.</param> + /// <param name="postData">(optional) A json-serializable object to include in the request body.</param> + /// <returns>The HttpResponseMessage of the response, which is confirmed to be successful.</returns> + /// <exception cref="HttpRequestException">Throws an exception if a non-success HTTP response was returned</exception> + protected async IAsyncEnumerable<T> HttpStreamingRequest<T>(string url = null, HttpMethod verb = null, object postData = null) where T : ApiResultBase + { + var response = await HttpRequestRaw(url, verb, postData, true); + + string organization = null; + string requestId = null; + TimeSpan processingTime = TimeSpan.Zero; + string openaiVersion = null; + string modelFromHeaders = null; + var headerNameHolder = ""; + try + { + headerNameHolder = "Openai-Organization"; + organization = response.Headers.GetValues(headerNameHolder).FirstOrDefault(); + + headerNameHolder = "X-Request-ID"; + requestId = response.Headers.GetValues(headerNameHolder).FirstOrDefault(); + + headerNameHolder = "Openai-Processing-Ms"; + processingTime = TimeSpan.FromMilliseconds(int.Parse(response.Headers.GetValues(headerNameHolder).First())); + + headerNameHolder = "Openai-Version"; + openaiVersion = response.Headers.GetValues(headerNameHolder).FirstOrDefault(); + + headerNameHolder = "Openai-Model"; + modelFromHeaders = response.Headers.GetValues(headerNameHolder).FirstOrDefault(); + } + catch (Exception e) + { + Debug.Print($"Issue parsing metadata of OpenAi Response while trying to get header name: {headerNameHolder}. Url: {url}, Error: {e.ToString()}. This is probably ignorable. Data gotten so far: organization={organization}, requestId={requestId}, processingTime ={processingTime}, openaiVersion={openaiVersion},modelFromHeaders={modelFromHeaders},"); + + } + + string resultAsString = ""; + + using (var stream = await response.Content.ReadAsStreamAsync()) + using (StreamReader reader = new StreamReader(stream)) + { + string line; + while ((line = await reader.ReadLineAsync()) != null) + { + resultAsString += line + Environment.NewLine; + + if (line.StartsWith("data:")) + line = line.Substring("data:".Length); + + line = line.TrimStart(); + + if (line == "[DONE]") + { + yield break; + } + else if (line.StartsWith(":")) + { } + else if (!string.IsNullOrWhiteSpace(line)) + { + var res = JsonConvert.DeserializeObject<T>(line); + + res.Organization = organization; + res.RequestId = requestId; + res.ProcessingTime = processingTime; + res.OpenaiVersion = openaiVersion; + if (string.IsNullOrEmpty(res.Model)) + res.Model = modelFromHeaders; + + yield return res; + } + } + } + } + + internal class ApiErrorResponse + { + /// <summary> + /// The error details + /// </summary> + [JsonProperty("error")] + public ApiErrorResponseError Error { get; set; } + } + internal class ApiErrorResponseError + { + /// <summary> + /// The error message + /// </summary> + [JsonProperty("message")] + + public string Message { get; set; } + + /// <summary> + /// The type of error + /// </summary> + [JsonProperty("type")] + public string ErrorType { get; set; } + + /// <summary> + /// The parameter that caused the error + /// </summary> + [JsonProperty("param")] + + public string Parameter { get; set; } + + /// <summary> + /// The error code + /// </summary> + [JsonProperty("code")] + public string ErrorCode { get; set; } + } + } } diff --git a/OpenAI_API/Images/ImageResult.cs b/OpenAI_API/Images/ImageResult.cs index 17e6e87..6817baf 100644 --- a/OpenAI_API/Images/ImageResult.cs +++ b/OpenAI_API/Images/ImageResult.cs @@ -49,7 +49,7 @@ public class Data /// The base64-encoded image data as returned by the API /// </summary> [JsonProperty("b64_json")] - public string Base64Data { get; set; } + public string Base64Data { get; set; } /// <summary> /// The prompt that was used to generate the image, if there was any revision to the prompt. From eb905171fa0c42d7cc8fe2b3d8b9cbb021e5699b Mon Sep 17 00:00:00 2001 From: Ernie French <efrench@gmail.com> Date: Fri, 3 May 2024 15:33:54 -0700 Subject: [PATCH 5/5] added vivid. --- OpenAI_API/Images/ImageGenerationRequest.cs | 58 ++++++++++++++++----- OpenAI_API/OpenAI_API.csproj | 8 +++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/OpenAI_API/Images/ImageGenerationRequest.cs b/OpenAI_API/Images/ImageGenerationRequest.cs index 98a8277..9483053 100644 --- a/OpenAI_API/Images/ImageGenerationRequest.cs +++ b/OpenAI_API/Images/ImageGenerationRequest.cs @@ -14,6 +14,7 @@ public class ImageGenerationRequest { private int? numOfImages = 1; private ImageSize size = ImageSize._1024; + private string style = "vivid"; private string quality = "standard"; /// <summary> @@ -65,10 +66,36 @@ public ImageSize Size set => size = value; } - /// <summary> - /// By default, images are generated at `standard` quality, but when using DALL·E 3 you can set quality to `hd` for enhanced detail. Square, standard quality images are the fastest to generate. - /// </summary> - [JsonProperty("quality", NullValueHandling=NullValueHandling.Ignore)] + /// <summary> + /// Adding this, upstream is missing this value too. What else is there?? revisedText was VITAL yet not added by upstream yet. + /// </summary> + [JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)] + public string Style + { + get + { + return style; + } + set + { + switch (value.ToLower().Trim()) + { + case "vivid": + style = "vivid"; + break; + case "natural": + style = "natural"; + break; + default: + throw new ArgumentException("Style must be either 'vivid' or 'natural'."); + } + } + } + + /// <summary> + /// By default, images are generated at `standard` quality, but when using DALL·E 3 you can set quality to `hd` for enhanced detail. Square, standard quality images are the fastest to generate. + /// </summary> + [JsonProperty("quality", NullValueHandling=NullValueHandling.Ignore)] public string Quality { get @@ -109,26 +136,29 @@ public ImageGenerationRequest() } - /// <summary> - /// Creates a new <see cref="ImageGenerationRequest"/> with the specified parameters - /// </summary> - /// <param name="prompt">A text description of the desired image(s). The maximum length is 1000 characters.</param> - /// <param name="model">The model to use for this request. Defaults to DALL-E 2.</param> - /// <param name="size">The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024.</param> - /// <param name="quality">By default, images are generated at `standard` quality, but when using DALL·E 3 you can set quality to `hd` for enhanced detail.</param> - /// <param name="user">A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.</param> - /// <param name="responseFormat">The format in which the generated images are returned. Must be one of url or b64_json.</param> - public ImageGenerationRequest( + /// <summary> + /// Creates a new <see cref="ImageGenerationRequest"/> with the specified parameters + /// </summary> + /// <param name="prompt">A text description of the desired image(s). The maximum length is 1000 characters.</param> + /// <param name="model">The model to use for this request. Defaults to DALL-E 2.</param> + /// <param name="size">The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024.</param> + /// <param name="quality">By default, images are generated at `standard` quality, but when using DALL·E 3 you can set quality to `hd` for enhanced detail.</param> + /// <param name="style">natural or vivid, part of the openAI API</param> + /// <param name="user">A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.</param> + /// <param name="responseFormat">The format in which the generated images are returned. Must be one of url or b64_json.</param> + public ImageGenerationRequest( string prompt, Model model, ImageSize size = null, string quality = "standard", + string style = "vivid", string user = null, ImageResponseFormat responseFormat = null) { this.Prompt = prompt; this.Model = model ?? OpenAI_API.Models.Model.DALLE2; this.Quality = quality ?? "standard"; + this.Style = style ?? "vivid"; this.User = user; this.Size = size ?? ImageSize._1024; this.ResponseFormat = responseFormat ?? ImageResponseFormat.Url; diff --git a/OpenAI_API/OpenAI_API.csproj b/OpenAI_API/OpenAI_API.csproj index 75dc2c3..8f2e9df 100644 --- a/OpenAI_API/OpenAI_API.csproj +++ b/OpenAI_API/OpenAI_API.csproj @@ -37,6 +37,14 @@ <PackageIcon>nuget_logo.png</PackageIcon> </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <NoWarn>1701;1702;1591</NoWarn> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> + <NoWarn>1701;1702;1591</NoWarn> + </PropertyGroup> + <ItemGroup> <None Include="nuget_logo.png" Pack="true" PackagePath="\" /> </ItemGroup>