diff --git a/OpenAI_API/EndpointBase.cs b/OpenAI_API/EndpointBase.cs
index d981c7e..b2e4c93 100644
--- a/OpenAI_API/EndpointBase.cs
+++ b/OpenAI_API/EndpointBase.cs
@@ -11,233 +11,253 @@
namespace OpenAI_API
{
- ///
- /// A base object for any OpenAI API endpoint, encompassing common functionality
- ///
- public abstract class EndpointBase
- {
- private const string UserAgent = "okgodoit/dotnet_openai_api";
-
- ///
- /// The internal reference to the API, mostly used for authentication
- ///
- protected readonly OpenAIAPI _Api;
-
- ///
- /// 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 .
- ///
- ///
- internal EndpointBase(OpenAIAPI api)
- {
- this._Api = api;
- }
-
- ///
- /// The name of the endpoint, which is the final path segment in the API URL. Must be overriden in a derived class.
- ///
- protected abstract string Endpoint { get; }
-
- ///
- /// 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"
- ///
- protected string Url
- {
- get
- {
- return string.Format(_Api.ApiUrlFormat, _Api.ApiVersion, Endpoint);
- }
- }
-
- ///
- /// Gets an HTTPClient with the appropriate authorization and other headers set
- ///
- /// The fully initialized HttpClient
- /// Thrown if there is no valid authentication. Please refer to for details.
- 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;
- }
-
- ///
- /// Formats a human-readable error message relating to calling the API and parsing the response
- ///
- /// The full content returned in the http response
- /// The http response object itself
- /// The name of the endpoint being used
- /// Additional details about the endpoint of this request (optional)
- /// A human-readable string error message.
- protected string GetErrorMessage(string resultAsString, HttpResponseMessage response, string name, string description = "")
- {
- return $"Error at {name} ({description}) with HTTP status code: {response.StatusCode}. Content: {resultAsString ?? ""}";
- }
-
-
- ///
- /// Sends an HTTP request and returns the response. Does not do any parsing, but does do error handling.
- ///
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
- /// (optional) A json-serializable object to include in the request body.
- /// (optional) If true, streams the response. Otherwise waits for the entire response before returning.
- /// The HttpResponseMessage of the response, which is confirmed to be successful.
- /// Throws an exception if a non-success HTTP response was returned
- private async Task 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(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;
- }
- }
- }
-
- ///
- /// Sends an HTTP Get request and return the string content of the response without parsing, and does error handling.
- ///
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
- /// (optional) A json-serializable object to include in the request body.
- /// The text string of the response, which is confirmed to be successful.
- /// Throws an exception if a non-success HTTP response was returned
- internal async Task HttpGetContent(string url = null, HttpMethod verb = null, object postData = null)
- {
- var response = await HttpRequestRaw(url, verb, postData);
- return await response.Content.ReadAsStringAsync();
- }
-
- ///
- /// Sends an HTTP request and return the raw content stream of the response without parsing, and does error handling.
- ///
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
- /// (optional) A json-serializable object to include in the request body.
- /// The response content stream, which is confirmed to be successful.
- /// Throws an exception if a non-success HTTP response was returned
- internal async Task HttpRequest(string url = null, HttpMethod verb = null, object postData = null)
- {
- var response = await HttpRequestRaw(url, verb, postData);
- return await response.Content.ReadAsStreamAsync();
- }
-
-
- ///
- /// Sends an HTTP Request and does initial parsing
- ///
- /// The -derived class for the result
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
- /// (optional) A json-serializable object to include in the request body.
- /// An awaitable Task with the parsed result of type
- /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
- private async Task HttpRequest(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(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;
- }
-
- /*
+ ///
+ /// A base object for any OpenAI API endpoint, encompassing common functionality
+ ///
+ public abstract class EndpointBase
+ {
+ private const string UserAgent = "okgodoit/dotnet_openai_api";
+
+ ///
+ /// The internal reference to the API, mostly used for authentication
+ ///
+ protected readonly OpenAIAPI _Api;
+
+ ///
+ /// 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 .
+ ///
+ ///
+ internal EndpointBase(OpenAIAPI api)
+ {
+ this._Api = api;
+ }
+
+ ///
+ /// The name of the endpoint, which is the final path segment in the API URL. Must be overriden in a derived class.
+ ///
+ protected abstract string Endpoint { get; }
+
+ ///
+ /// 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"
+ ///
+ protected string Url
+ {
+ get
+ {
+ return string.Format(_Api.ApiUrlFormat, _Api.ApiVersion, Endpoint);
+ }
+ }
+
+ ///
+ /// Gets an HTTPClient with the appropriate authorization and other headers set
+ ///
+ /// The fully initialized HttpClient
+ /// Thrown if there is no valid authentication. Please refer to for details.
+ 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;
+ }
+
+ ///
+ /// Formats a human-readable error message relating to calling the API and parsing the response
+ ///
+ /// The full content returned in the http response
+ /// The http response object itself
+ /// The name of the endpoint being used
+ /// Additional details about the endpoint of this request (optional)
+ /// A human-readable string error message.
+ protected string GetErrorMessage(string resultAsString, HttpResponseMessage response, string name, string description = "")
+ {
+ return $"Error at {name} ({description}) with HTTP status code: {response.StatusCode}. Content: {resultAsString ?? ""}";
+ }
+
+ ///
+ /// Sends an HTTP request and returns the response. Does not do any parsing, but does do error handling.
+ ///
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
+ /// (optional) A json-serializable object to include in the request body.
+ /// (optional) If true, streams the response. Otherwise waits for the entire response before returning.
+ /// The HttpResponseMessage of the response, which is confirmed to be successful.
+ /// Throws an exception if a non-success HTTP response was returned
+ private async Task 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));
+ ApiErrorResponse? parsedError;
+ try
+ {
+ parsedError = JsonConvert.DeserializeObject(resultAsString);
+ }
+ catch (Exception ex)
+ {
+ //typically gateway timeout 504
+ //"\r\n504 Gateway Time-out\r\n\r\n504 Gateway Time-out
\r\n
openresty/1.21.4.2\r\n\r\n\r\n"
+ if (resultAsString == "\r\n504 Gateway Time-out\r\n\r\n504 Gateway Time-out
\r\n
openresty/1.21.4.2\r\n\r\n\r\n")
+ {
+ parsedError = new ApiErrorResponse();
+ parsedError.Error = new ApiErrorResponseError();
+ parsedError.Error.Message = "504 Gateway Time-out";
+ parsedError.Error.ErrorType = "Gateway Time-out";
+ parsedError.Error.Parameter = "N/A";
+ parsedError.Error.ErrorCode = "504";
+ }
+ else
+ {
+ var a = 3;
+ throw ex;
+ }
+ }
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Sends an HTTP Get request and return the string content of the response without parsing, and does error handling.
+ ///
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
+ /// (optional) A json-serializable object to include in the request body.
+ /// The text string of the response, which is confirmed to be successful.
+ /// Throws an exception if a non-success HTTP response was returned
+ internal async Task HttpGetContent(string url = null, HttpMethod verb = null, object postData = null)
+ {
+ var response = await HttpRequestRaw(url, verb, postData);
+ return await response.Content.ReadAsStringAsync();
+ }
+
+ ///
+ /// Sends an HTTP request and return the raw content stream of the response without parsing, and does error handling.
+ ///
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
+ /// (optional) A json-serializable object to include in the request body.
+ /// The response content stream, which is confirmed to be successful.
+ /// Throws an exception if a non-success HTTP response was returned
+ internal async Task HttpRequest(string url = null, HttpMethod verb = null, object postData = null)
+ {
+ var response = await HttpRequestRaw(url, verb, postData);
+ return await response.Content.ReadAsStreamAsync();
+ }
+
+ ///
+ /// Sends an HTTP Request and does initial parsing
+ ///
+ /// The -derived class for the result
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
+ /// (optional) A json-serializable object to include in the request body.
+ /// An awaitable Task with the parsed result of type
+ /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
+ private async Task HttpRequest(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(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;
+ }
+
+ /*
///
/// Sends an HTTP Request, supporting a streaming response
///
@@ -271,61 +291,58 @@ private async Task StreamingHttpRequest(string url = null, HttpMethod verb
}
*/
- ///
- /// Sends an HTTP Get request and does initial parsing
- ///
- /// The -derived class for the result
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// An awaitable Task with the parsed result of type
- /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
- internal async Task HttpGet(string url = null) where T : ApiResultBase
- {
- return await HttpRequest(url, HttpMethod.Get);
- }
-
- ///
- /// Sends an HTTP Post request and does initial parsing
- ///
- /// The -derived class for the result
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) A json-serializable object to include in the request body.
- /// An awaitable Task with the parsed result of type
- /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
- internal async Task HttpPost(string url = null, object postData = null) where T : ApiResultBase
- {
- return await HttpRequest(url, HttpMethod.Post, postData);
- }
-
- ///
- /// Sends an HTTP Delete request and does initial parsing
- ///
- /// The -derived class for the result
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) A json-serializable object to include in the request body.
- /// An awaitable Task with the parsed result of type
- /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
- internal async Task HttpDelete(string url = null, object postData = null) where T : ApiResultBase
- {
- return await HttpRequest(url, HttpMethod.Delete, postData);
- }
-
-
- ///
- /// Sends an HTTP Put request and does initial parsing
- ///
- /// The -derived class for the result
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) A json-serializable object to include in the request body.
- /// An awaitable Task with the parsed result of type
- /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
- internal async Task HttpPut(string url = null, object postData = null) where T : ApiResultBase
- {
- return await HttpRequest(url, HttpMethod.Put, postData);
- }
-
-
-
- /*
+ ///
+ /// Sends an HTTP Get request and does initial parsing
+ ///
+ /// The -derived class for the result
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// An awaitable Task with the parsed result of type
+ /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
+ internal async Task HttpGet(string url = null) where T : ApiResultBase
+ {
+ return await HttpRequest(url, HttpMethod.Get);
+ }
+
+ ///
+ /// Sends an HTTP Post request and does initial parsing
+ ///
+ /// The -derived class for the result
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) A json-serializable object to include in the request body.
+ /// An awaitable Task with the parsed result of type
+ /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
+ internal async Task HttpPost(string url = null, object postData = null) where T : ApiResultBase
+ {
+ return await HttpRequest(url, HttpMethod.Post, postData);
+ }
+
+ ///
+ /// Sends an HTTP Delete request and does initial parsing
+ ///
+ /// The -derived class for the result
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) A json-serializable object to include in the request body.
+ /// An awaitable Task with the parsed result of type
+ /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
+ internal async Task HttpDelete(string url = null, object postData = null) where T : ApiResultBase
+ {
+ return await HttpRequest(url, HttpMethod.Delete, postData);
+ }
+
+ ///
+ /// Sends an HTTP Put request and does initial parsing
+ ///
+ /// The -derived class for the result
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) A json-serializable object to include in the request body.
+ /// An awaitable Task with the parsed result of type
+ /// Throws an exception if a non-success HTTP response was returned or if the result couldn't be parsed.
+ internal async Task HttpPut(string url = null, object postData = null) where T : ApiResultBase
+ {
+ return await HttpRequest(url, HttpMethod.Put, postData);
+ }
+
+ /*
///
/// Sends an HTTP request and handles a streaming response. Does basic line splitting and error handling.
///
@@ -359,111 +376,110 @@ private async IAsyncEnumerable HttpStreamingRequestRaw(string url = null
}
*/
-
- ///
- /// Sends an HTTP request and handles a streaming response. Does basic line splitting and error handling.
- ///
- /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
- /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
- /// (optional) A json-serializable object to include in the request body.
- /// The HttpResponseMessage of the response, which is confirmed to be successful.
- /// Throws an exception if a non-success HTTP response was returned
- protected async IAsyncEnumerable HttpStreamingRequest(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(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
- {
- ///
- /// The error details
- ///
- [JsonProperty("error")]
- public ApiErrorResponseError Error { get; set; }
- }
- internal class ApiErrorResponseError
- {
- ///
- /// The error message
- ///
- [JsonProperty("message")]
-
- public string Message { get; set; }
-
- ///
- /// The type of error
- ///
- [JsonProperty("type")]
- public string ErrorType { get; set; }
-
- ///
- /// The parameter that caused the error
- ///
- [JsonProperty("param")]
-
- public string Parameter { get; set; }
-
- ///
- /// The error code
- ///
- [JsonProperty("code")]
- public string ErrorCode { get; set; }
- }
- }
+ ///
+ /// Sends an HTTP request and handles a streaming response. Does basic line splitting and error handling.
+ ///
+ /// (optional) If provided, overrides the url endpoint for this request. If omitted, then will be used.
+ /// (optional) The HTTP verb to use, for example "". If omitted, then "GET" is assumed.
+ /// (optional) A json-serializable object to include in the request body.
+ /// The HttpResponseMessage of the response, which is confirmed to be successful.
+ /// Throws an exception if a non-success HTTP response was returned
+ protected async IAsyncEnumerable HttpStreamingRequest(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(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
+ {
+ ///
+ /// The error details
+ ///
+ [JsonProperty("error")]
+ public ApiErrorResponseError Error { get; set; }
+ }
+ internal class ApiErrorResponseError
+ {
+ ///
+ /// The error message
+ ///
+ [JsonProperty("message")]
+
+ public string Message { get; set; }
+
+ ///
+ /// The type of error
+ ///
+ [JsonProperty("type")]
+ public string ErrorType { get; set; }
+
+ ///
+ /// The parameter that caused the error
+ ///
+ [JsonProperty("param")]
+
+ public string Parameter { get; set; }
+
+ ///
+ /// The error code
+ ///
+ [JsonProperty("code")]
+ public string ErrorCode { get; set; }
+ }
+ }
}