From ace96e47ed55b0df2eab37e84d050c4aec4d3786 Mon Sep 17 00:00:00 2001 From: Quentin McCain Date: Wed, 11 Jan 2023 17:23:41 -0500 Subject: [PATCH 1/7] feat: Add function to Service to allow stream the results back from a completion request --- OpenAI.Playground/Program.cs | 4 +- .../TestHelpers/CompletionTestHelper.cs | 46 +++++++++++++++++ OpenAI.SDK/Interfaces/ICompletionService.cs | 8 +++ OpenAI.SDK/Managers/OpenAICompletions.cs | 51 ++++++++++++++++++- .../RequestModels/CompletionCreateRequest.cs | 38 +++++++------- 5 files changed, 127 insertions(+), 20 deletions(-) diff --git a/OpenAI.Playground/Program.cs b/OpenAI.Playground/Program.cs index d4648a26..5297485d 100644 --- a/OpenAI.Playground/Program.cs +++ b/OpenAI.Playground/Program.cs @@ -30,7 +30,9 @@ //await ImageTestHelper.RunSimpleCreateImageEditTest(sdk); //await ImageTestHelper.RunSimpleCreateImageVariationTest(sdk); //await ModerationTestHelper.CreateModerationTest(sdk); -await CompletionTestHelper.RunSimpleCompletionTest(sdk); +//await CompletionTestHelper.RunSimpleCompletionTest(sdk); + +await CompletionTestHelper.RunSimpleCompletionStreamTest(sdk); //await EmbeddingTestHelper.RunSimpleEmbeddingTest(sdk); //await FileTestHelper.RunSimpleFileTest(sdk); ////await FineTuningTestHelper.CleanUpAllFineTunings(sdk); //!!!!! will delete all fine-tunings diff --git a/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs b/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs index 01dd25d3..bf6467dd 100644 --- a/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs +++ b/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs @@ -43,5 +43,51 @@ public static async Task RunSimpleCompletionTest(IOpenAIService sdk) throw; } } + + public static async Task RunSimpleCompletionStreamTest(IOpenAIService sdk) + { + ConsoleExtensions.WriteLine("Completion Testing is starting:", ConsoleColor.Cyan); + + try + { + ConsoleExtensions.WriteLine("Completion Test:", ConsoleColor.DarkCyan); + var completionResult = sdk.Completions.CreateCompletionStream(new CompletionCreateRequest() + { + Prompt = "Once upon a time", + // PromptAsList = new []{"Once upon a time"}, + MaxTokens = 100, + LogProbs = 1, + LogitBias = null // this was causing an exception to be thrown when serializing the request to JSON + }, Models.Davinci); + + await foreach (var completion in completionResult) + { + if (completion.Successful) + { + Console.Write(completion.Choices.FirstOrDefault()?.Text); + } + else + { + if (completion.Error == null) + { + throw new Exception("Unknown Error"); + } + + Console.WriteLine($"{completion.Error.Code}: {completion.Error.Message}"); + } + } + + Console.WriteLine(""); + Console.WriteLine("Complete"); + + + + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } } } \ No newline at end of file diff --git a/OpenAI.SDK/Interfaces/ICompletionService.cs b/OpenAI.SDK/Interfaces/ICompletionService.cs index 2848b764..a521a7b4 100644 --- a/OpenAI.SDK/Interfaces/ICompletionService.cs +++ b/OpenAI.SDK/Interfaces/ICompletionService.cs @@ -18,6 +18,14 @@ public interface ICompletionService /// Task CreateCompletion(CompletionCreateRequest createCompletionModel, string? engineId = null); + /// + /// Creates a new completion for the provided prompt and parameters and returns a stream of CompletionCreateRequests + /// + /// The ID of the engine to use for this request + /// + /// + IAsyncEnumerable CreateCompletionStream(CompletionCreateRequest createCompletionModel, string? engineId = null); + /// /// Creates a new completion for the provided prompt and parameters /// diff --git a/OpenAI.SDK/Managers/OpenAICompletions.cs b/OpenAI.SDK/Managers/OpenAICompletions.cs index 486ef745..183cc649 100644 --- a/OpenAI.SDK/Managers/OpenAICompletions.cs +++ b/OpenAI.SDK/Managers/OpenAICompletions.cs @@ -2,6 +2,7 @@ using OpenAI.GPT3.Interfaces; using OpenAI.GPT3.ObjectModels.RequestModels; using OpenAI.GPT3.ObjectModels.ResponseModels; +using System.Text; namespace OpenAI.GPT3.Managers; @@ -11,4 +12,52 @@ public async Task CreateCompletion(CompletionCreateReq { return await _httpClient.PostAndReadAsAsync(_endpointProvider.CompletionCreate(ProcessEngineId(engineId)), createCompletionRequest); } -} \ No newline at end of file + + + + public async IAsyncEnumerable CreateCompletionStream(CompletionCreateRequest createCompletionRequest, string? engineId = null) + { + // Mark the request as streaming and include a logit bias + createCompletionRequest.Stream = true; + + // Serialize the request and create a StringContent + var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(createCompletionRequest), Encoding.UTF8, "application/json"); + + // Send the request to the CompletionCreate endpoint + var request = await _httpClient.PostAsync(_endpointProvider.CompletionCreate(ProcessEngineId(engineId)), content); + + // Read the response as a stream + using (var stream = await request.Content.ReadAsStreamAsync()) + using (var reader = new StreamReader(stream)) + { + // Continuously read the stream until the end of it + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync(); + // Skip empty lines + if (string.IsNullOrEmpty(line)) continue; + + line = line.Replace("data: ", string.Empty); + + // Exit the loop if the stream is done + if (line.Contains("[DONE]")) break; + + CompletionCreateResponse block = null; + try + { + // When the response is good, each line is a serializable CompletionCreateRequest + block = System.Text.Json.JsonSerializer.Deserialize(line); + } + catch (Exception) + { + // When the API returns an error, it does not come back as a block, it returns a single character of text ("{"). + // In this instance, read through the rest of the response, which should be a complete object to parse. + line += await reader.ReadToEndAsync(); + block = System.Text.Json.JsonSerializer.Deserialize(line); + } + if (null != block) yield return block; + } + } + } +} + \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs index 6d67a0ca..e43a7c9f 100644 --- a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs +++ b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs @@ -2,6 +2,7 @@ using System.Text.Json.Serialization; using OpenAI.GPT3.Interfaces; using OpenAI.GPT3.ObjectModels.SharedModels; +using static System.Net.WebRequestMethods; namespace OpenAI.GPT3.ObjectModels.RequestModels { @@ -30,7 +31,7 @@ public record CompletionCreateRequest : IModelValidate, IOpenAiModels.ITemperatu [JsonIgnore] public IList? PromptAsList { get; set; } - [JsonPropertyName("prompt")] + [JsonPropertyName("prompt"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IList? PromptCalculated { get @@ -42,7 +43,7 @@ public IList? PromptCalculated if (Prompt != null) { - return new List() {Prompt}; + return new List() { Prompt }; } @@ -53,7 +54,7 @@ public IList? PromptCalculated /// /// The suffix that comes after a completion of inserted text. /// - [JsonPropertyName("suffix")] + [JsonPropertyName("suffix"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Suffix { get; set; } /// @@ -62,7 +63,7 @@ public IList? PromptCalculated /// length of 2048 tokens (except davinci-codex, which supports 4096). /// /// - [JsonPropertyName("max_tokens")] + [JsonPropertyName("max_tokens"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? MaxTokens { get; set; } /// @@ -71,7 +72,7 @@ public IList? PromptCalculated /// considered. /// We generally recommend altering this or temperature but not both. /// - [JsonPropertyName("top_p")] + [JsonPropertyName("top_p"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? TopP { get; set; } /// @@ -80,7 +81,7 @@ public IList? PromptCalculated /// Note: Because this parameter generates many completions, it can quickly consume your token quota.Use carefully and /// ensure that you have reasonable settings for max_tokens and stop. /// - [JsonPropertyName("n")] + [JsonPropertyName("n"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? N { get; set; } /// @@ -91,13 +92,13 @@ public IList? PromptCalculated /// /// as they become available, with the stream terminated by a data: [DONE] message. /// - [JsonPropertyName("stream")] + [JsonPropertyName("stream"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Stream { get; set; } /// /// Echo back the prompt in addition to the completion /// - [JsonPropertyName("echo")] + [JsonPropertyName("echo"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? Echo { get; set; } /// @@ -114,7 +115,7 @@ public IList? PromptCalculated [JsonIgnore] public IList? StopAsList { get; set; } - [JsonPropertyName("stop")] + [JsonPropertyName("stop"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IList? StopCalculated { get @@ -126,7 +127,7 @@ public IList? StopCalculated if (Stop != null) { - return new List() {Stop}; + return new List() { Stop }; } return StopAsList; @@ -138,7 +139,7 @@ public IList? StopCalculated /// increasing the model's likelihood to talk about new topics. /// /// - [JsonPropertyName("presence_penalty")] + [JsonPropertyName("presence_penalty"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? PresencePenalty { get; set; } @@ -147,7 +148,7 @@ public IList? StopCalculated /// far, decreasing the model's likelihood to repeat the same line verbatim. /// /// - [JsonPropertyName("frequency_penalty")] + [JsonPropertyName("frequency_penalty"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? FrequencyPenalty { get; set; } /// @@ -158,7 +159,7 @@ public IList? StopCalculated /// Note: Because this parameter generates many completions, it can quickly consume your token quota.Use carefully and /// ensure that you have reasonable settings for max_tokens and stop. /// - [JsonPropertyName("best_of")] + [JsonPropertyName("best_of"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? BestOf { get; set; } /// @@ -172,7 +173,7 @@ public IList? StopCalculated /// to prevent the endoftext token from being generated. /// /// - [JsonPropertyName("logit_bias")] + [JsonPropertyName("logit_bias"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object? LogitBias { get; set; } /// @@ -182,10 +183,11 @@ public IList? StopCalculated /// The maximum value for logprobs is 5. If you need more than this, please contact support@openai.com and describe /// your use case. /// - [JsonPropertyName("logprobs")] + [JsonPropertyName("logprobs"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? LogProbs { get; set; } - [JsonPropertyName("model")] public string? Model { get; set; } + [JsonPropertyName("model"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Model { get; set; } public IEnumerable Validate() { @@ -200,13 +202,13 @@ public IEnumerable Validate() /// We generally recommend altering this or top_p but not both. /// /// - [JsonPropertyName("temperature")] + [JsonPropertyName("temperature"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public float? Temperature { get; set; } /// /// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. /// - [JsonPropertyName("user")] + [JsonPropertyName("user"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? User { get; set; } } } \ No newline at end of file From caef3441235730718304fc3b43884c71c8c02d0f Mon Sep 17 00:00:00 2001 From: Quentin McCain Date: Thu, 12 Jan 2023 18:41:46 -0500 Subject: [PATCH 2/7] fix: removed JsonIgnore attributes --- .../RequestModels/CompletionCreateRequest.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs index e43a7c9f..8b4cd93a 100644 --- a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs +++ b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs @@ -31,7 +31,7 @@ public record CompletionCreateRequest : IModelValidate, IOpenAiModels.ITemperatu [JsonIgnore] public IList? PromptAsList { get; set; } - [JsonPropertyName("prompt"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("prompt")] public IList? PromptCalculated { get @@ -54,7 +54,7 @@ public IList? PromptCalculated /// /// The suffix that comes after a completion of inserted text. /// - [JsonPropertyName("suffix"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("suffix")] public string? Suffix { get; set; } /// @@ -63,7 +63,7 @@ public IList? PromptCalculated /// length of 2048 tokens (except davinci-codex, which supports 4096). /// /// - [JsonPropertyName("max_tokens"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("max_tokens")] public int? MaxTokens { get; set; } /// @@ -72,7 +72,7 @@ public IList? PromptCalculated /// considered. /// We generally recommend altering this or temperature but not both. /// - [JsonPropertyName("top_p"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("top_p")] public float? TopP { get; set; } /// @@ -81,7 +81,7 @@ public IList? PromptCalculated /// Note: Because this parameter generates many completions, it can quickly consume your token quota.Use carefully and /// ensure that you have reasonable settings for max_tokens and stop. /// - [JsonPropertyName("n"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("n")] public int? N { get; set; } /// @@ -92,13 +92,13 @@ public IList? PromptCalculated /// /// as they become available, with the stream terminated by a data: [DONE] message. /// - [JsonPropertyName("stream"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("stream")] public bool? Stream { get; set; } /// /// Echo back the prompt in addition to the completion /// - [JsonPropertyName("echo"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("echo")] public bool? Echo { get; set; } /// @@ -115,7 +115,7 @@ public IList? PromptCalculated [JsonIgnore] public IList? StopAsList { get; set; } - [JsonPropertyName("stop"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("stop")] public IList? StopCalculated { get @@ -139,7 +139,7 @@ public IList? StopCalculated /// increasing the model's likelihood to talk about new topics. /// /// - [JsonPropertyName("presence_penalty"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("presence_penalty")] public float? PresencePenalty { get; set; } @@ -148,7 +148,7 @@ public IList? StopCalculated /// far, decreasing the model's likelihood to repeat the same line verbatim. /// /// - [JsonPropertyName("frequency_penalty"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("frequency_penalty")] public float? FrequencyPenalty { get; set; } /// @@ -159,7 +159,7 @@ public IList? StopCalculated /// Note: Because this parameter generates many completions, it can quickly consume your token quota.Use carefully and /// ensure that you have reasonable settings for max_tokens and stop. /// - [JsonPropertyName("best_of"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("best_of")] public int? BestOf { get; set; } /// @@ -173,7 +173,7 @@ public IList? StopCalculated /// to prevent the endoftext token from being generated. /// /// - [JsonPropertyName("logit_bias"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("logit_bias")] public object? LogitBias { get; set; } /// @@ -183,10 +183,10 @@ public IList? StopCalculated /// The maximum value for logprobs is 5. If you need more than this, please contact support@openai.com and describe /// your use case. /// - [JsonPropertyName("logprobs"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("logprobs")] public int? LogProbs { get; set; } - [JsonPropertyName("model"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] public string? Model { get; set; } public IEnumerable Validate() @@ -202,13 +202,13 @@ public IEnumerable Validate() /// We generally recommend altering this or top_p but not both. /// /// - [JsonPropertyName("temperature"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("temperature")] public float? Temperature { get; set; } /// /// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. /// - [JsonPropertyName("user"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("user")] public string? User { get; set; } } } \ No newline at end of file From f461b9b1722c81498594c6bd0ab38cf1ae9a6469 Mon Sep 17 00:00:00 2001 From: Quentin McCain Date: Thu, 12 Jan 2023 18:47:18 -0500 Subject: [PATCH 3/7] fix: remove unneeded formatting changes --- OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs index 8b4cd93a..330eec32 100644 --- a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs +++ b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs @@ -2,7 +2,6 @@ using System.Text.Json.Serialization; using OpenAI.GPT3.Interfaces; using OpenAI.GPT3.ObjectModels.SharedModels; -using static System.Net.WebRequestMethods; namespace OpenAI.GPT3.ObjectModels.RequestModels { From 24f0127fcb3ecb7a8bdf075d6c74eb2a4985fb13 Mon Sep 17 00:00:00 2001 From: Quentin McCain Date: Thu, 12 Jan 2023 18:50:14 -0500 Subject: [PATCH 4/7] Update CompletionCreateRequest.cs --- .../RequestModels/CompletionCreateRequest.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs index 330eec32..4c969f20 100644 --- a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs +++ b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using OpenAI.GPT3.Interfaces; using OpenAI.GPT3.ObjectModels.SharedModels; @@ -42,7 +42,7 @@ public IList? PromptCalculated if (Prompt != null) { - return new List() { Prompt }; + return new List() {Prompt}; } @@ -126,7 +126,7 @@ public IList? StopCalculated if (Stop != null) { - return new List() { Stop }; + return new List() {Stop}; } return StopAsList; @@ -185,8 +185,7 @@ public IList? StopCalculated [JsonPropertyName("logprobs")] public int? LogProbs { get; set; } - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonPropertyName("model")] public string? Model { get; set; } public IEnumerable Validate() { @@ -210,4 +209,4 @@ public IEnumerable Validate() [JsonPropertyName("user")] public string? User { get; set; } } -} \ No newline at end of file +} From 142b0b5cea88320775ec019a0c0401945d6b91b4 Mon Sep 17 00:00:00 2001 From: Tolga Kayhan Date: Tue, 17 Jan 2023 00:47:54 +0000 Subject: [PATCH 5/7] small updates --- OpenAI.Playground/Program.cs | 6 +- .../TestHelpers/CompletionTestHelper.cs | 7 +- OpenAI.SDK/Interfaces/ICompletionService.cs | 2 +- OpenAI.SDK/Managers/OpenAICompletions.cs | 69 +++++++++---------- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/OpenAI.Playground/Program.cs b/OpenAI.Playground/Program.cs index 4132c240..b52b59e9 100644 --- a/OpenAI.Playground/Program.cs +++ b/OpenAI.Playground/Program.cs @@ -30,9 +30,9 @@ //await ImageTestHelper.RunSimpleCreateImageEditTest(sdk); //await ImageTestHelper.RunSimpleCreateImageVariationTest(sdk); //await ModerationTestHelper.CreateModerationTest(sdk); -await CompletionTestHelper.RunSimpleCompletionTest(sdk); -await CompletionTestHelper.RunSimpleCompletionTest2(sdk); -await CompletionTestHelper.RunSimpleCompletionTest3(sdk); +//await CompletionTestHelper.RunSimpleCompletionTest(sdk); +//await CompletionTestHelper.RunSimpleCompletionTest2(sdk); +//await CompletionTestHelper.RunSimpleCompletionTest3(sdk); await CompletionTestHelper.RunSimpleCompletionStreamTest(sdk); //await EmbeddingTestHelper.RunSimpleEmbeddingTest(sdk); //////await FileTestHelper.RunSimpleFileTest(sdk); //will delete files diff --git a/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs b/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs index b33a44ff..447f526b 100644 --- a/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs +++ b/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs @@ -130,13 +130,12 @@ public static async Task RunSimpleCompletionStreamTest(IOpenAIService sdk) try { ConsoleExtensions.WriteLine("Completion Test:", ConsoleColor.DarkCyan); - var completionResult = sdk.Completions.CreateCompletionStream(new CompletionCreateRequest() + var completionResult = sdk.Completions.CreateCompletionAsStream(new CompletionCreateRequest() { Prompt = "Once upon a time", // PromptAsList = new []{"Once upon a time"}, - MaxTokens = 100, - LogProbs = 1, - LogitBias = null // this was causing an exception to be thrown when serializing the request to JSON + MaxTokens = 1000, + // LogProbs = 1 }, Models.Davinci); await foreach (var completion in completionResult) diff --git a/OpenAI.SDK/Interfaces/ICompletionService.cs b/OpenAI.SDK/Interfaces/ICompletionService.cs index a521a7b4..75ea9f55 100644 --- a/OpenAI.SDK/Interfaces/ICompletionService.cs +++ b/OpenAI.SDK/Interfaces/ICompletionService.cs @@ -24,7 +24,7 @@ public interface ICompletionService /// The ID of the engine to use for this request /// /// - IAsyncEnumerable CreateCompletionStream(CompletionCreateRequest createCompletionModel, string? engineId = null); + IAsyncEnumerable CreateCompletionAsStream(CompletionCreateRequest createCompletionModel, string? engineId = null); /// /// Creates a new completion for the provided prompt and parameters diff --git a/OpenAI.SDK/Managers/OpenAICompletions.cs b/OpenAI.SDK/Managers/OpenAICompletions.cs index 7e673498..7bf39b96 100644 --- a/OpenAI.SDK/Managers/OpenAICompletions.cs +++ b/OpenAI.SDK/Managers/OpenAICompletions.cs @@ -2,7 +2,9 @@ using OpenAI.GPT3.Interfaces; using OpenAI.GPT3.ObjectModels.RequestModels; using OpenAI.GPT3.ObjectModels.ResponseModels; -using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Net.Http.Json; namespace OpenAI.GPT3.Managers; @@ -11,54 +13,51 @@ public partial class OpenAIService : ICompletionService public async Task CreateCompletion(CompletionCreateRequest createCompletionRequest, string? modelId = null) { createCompletionRequest.ProcessModelId(modelId, _defaultModelId); - return await _httpClient.PostAndReadAsAsync(_endpointProvider.CompletionCreate(), createCompletionRequest); } - - - - public async IAsyncEnumerable CreateCompletionStream(CompletionCreateRequest createCompletionRequest, string? engineId = null) + + public async IAsyncEnumerable CreateCompletionAsStream(CompletionCreateRequest createCompletionRequest, string? modelId = null) { // Mark the request as streaming and include a logit bias createCompletionRequest.Stream = true; - // Serialize the request and create a StringContent - var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(createCompletionRequest), Encoding.UTF8, "application/json"); - // Send the request to the CompletionCreate endpoint - var request = await _httpClient.PostAsync(_endpointProvider.CompletionCreate(ProcessEngineId(engineId)), content); + createCompletionRequest.ProcessModelId(modelId, _defaultModelId); + + var request = await _httpClient.PostAsJsonAsync(_endpointProvider.CompletionCreate(), createCompletionRequest, new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }); // Read the response as a stream - using (var stream = await request.Content.ReadAsStreamAsync()) - using (var reader = new StreamReader(stream)) + await using var stream = await request.Content.ReadAsStreamAsync(); + using var reader = new StreamReader(stream); + // Continuously read the stream until the end of it + while (!reader.EndOfStream) { - // Continuously read the stream until the end of it - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync(); - // Skip empty lines - if (string.IsNullOrEmpty(line)) continue; + var line = await reader.ReadLineAsync(); + // Skip empty lines + if (string.IsNullOrEmpty(line)) continue; - line = line.Replace("data: ", string.Empty); + line = line.Replace("data: ", string.Empty); - // Exit the loop if the stream is done - if (line.Contains("[DONE]")) break; + // Exit the loop if the stream is done + if (line.Contains("[DONE]")) break; - CompletionCreateResponse block = null; - try - { - // When the response is good, each line is a serializable CompletionCreateRequest - block = System.Text.Json.JsonSerializer.Deserialize(line); - } - catch (Exception) - { - // When the API returns an error, it does not come back as a block, it returns a single character of text ("{"). - // In this instance, read through the rest of the response, which should be a complete object to parse. - line += await reader.ReadToEndAsync(); - block = System.Text.Json.JsonSerializer.Deserialize(line); - } - if (null != block) yield return block; + CompletionCreateResponse? block; + try + { + // When the response is good, each line is a serializable CompletionCreateRequest + block = JsonSerializer.Deserialize(line); + } + catch (Exception) + { + // When the API returns an error, it does not come back as a block, it returns a single character of text ("{"). + // In this instance, read through the rest of the response, which should be a complete object to parse. + line += await reader.ReadToEndAsync(); + block = JsonSerializer.Deserialize(line); } + if (null != block) yield return block; } } } From 0dc690db6966eaf6d61ef44f3e87aedc788d6a2d Mon Sep 17 00:00:00 2001 From: Quentin McCain Date: Tue, 17 Jan 2023 20:10:16 -0500 Subject: [PATCH 6/7] fix: Switched method used from PostAsync to Send. Also set the request to set headers for the completion --- OpenAI.SDK/Extensions/HttpclientExtensions.cs | 21 ++++++- OpenAI.SDK/Managers/OpenAICompletions.cs | 60 ++++++++++--------- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/OpenAI.SDK/Extensions/HttpclientExtensions.cs b/OpenAI.SDK/Extensions/HttpclientExtensions.cs index 08ffc354..1e956c37 100644 --- a/OpenAI.SDK/Extensions/HttpclientExtensions.cs +++ b/OpenAI.SDK/Extensions/HttpclientExtensions.cs @@ -1,4 +1,7 @@ -using System.Net.Http.Json; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Runtime.ConstrainedExecution; using System.Text.Json; using System.Text.Json.Serialization; @@ -21,6 +24,22 @@ public static async Task PostAndReadAsAsync(this HttpClien return await response.Content.ReadFromJsonAsync() ?? throw new InvalidOperationException(); } + public static HttpResponseMessage PostAsStreamAsync(this HttpClient client, string uri, object requestModel) + { + var settings = new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }; + + JsonContent content = JsonContent.Create(requestModel, mediaType: null, settings); + + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); + request.Content = content; + + return client.Send(request, HttpCompletionOption.ResponseHeadersRead); + } + public static async Task PostFileAndReadAsAsync(this HttpClient client, string uri, HttpContent content) { var response = await client.PostAsync(uri, content); diff --git a/OpenAI.SDK/Managers/OpenAICompletions.cs b/OpenAI.SDK/Managers/OpenAICompletions.cs index 7bf39b96..adfcf2e3 100644 --- a/OpenAI.SDK/Managers/OpenAICompletions.cs +++ b/OpenAI.SDK/Managers/OpenAICompletions.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Net.Http.Json; +using System.Net.Http.Headers; namespace OpenAI.GPT3.Managers; @@ -18,47 +19,48 @@ public async Task CreateCompletion(CompletionCreateReq public async IAsyncEnumerable CreateCompletionAsStream(CompletionCreateRequest createCompletionRequest, string? modelId = null) { - // Mark the request as streaming and include a logit bias + // Mark the request as streaming createCompletionRequest.Stream = true; // Send the request to the CompletionCreate endpoint createCompletionRequest.ProcessModelId(modelId, _defaultModelId); - var request = await _httpClient.PostAsJsonAsync(_endpointProvider.CompletionCreate(), createCompletionRequest, new JsonSerializerOptions() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault - }); - // Read the response as a stream - await using var stream = await request.Content.ReadAsStreamAsync(); - using var reader = new StreamReader(stream); - // Continuously read the stream until the end of it - while (!reader.EndOfStream) + using (var response = _httpClient.PostAsStreamAsync(_endpointProvider.CompletionCreate(), createCompletionRequest)) + using (var stream = response.Content.ReadAsStream()) { - var line = await reader.ReadLineAsync(); - // Skip empty lines - if (string.IsNullOrEmpty(line)) continue; + + using var reader = new StreamReader(stream); + // Continuously read the stream until the end of it + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync(); + // Skip empty lines + if (string.IsNullOrEmpty(line)) continue; - line = line.Replace("data: ", string.Empty); + line = line.Replace("data: ", string.Empty); - // Exit the loop if the stream is done - if (line.Contains("[DONE]")) break; + // Exit the loop if the stream is done + if (line.Contains("[DONE]")) break; - CompletionCreateResponse? block; - try - { - // When the response is good, each line is a serializable CompletionCreateRequest - block = JsonSerializer.Deserialize(line); + CompletionCreateResponse? block; + try + { + // When the response is good, each line is a serializable CompletionCreateRequest + block = JsonSerializer.Deserialize(line); + } + catch (Exception) + { + // When the API returns an error, it does not come back as a block, it returns a single character of text ("{"). + // In this instance, read through the rest of the response, which should be a complete object to parse. + line += await reader.ReadToEndAsync(); + block = JsonSerializer.Deserialize(line); + } + if (null != block) yield return block; } - catch (Exception) - { - // When the API returns an error, it does not come back as a block, it returns a single character of text ("{"). - // In this instance, read through the rest of the response, which should be a complete object to parse. - line += await reader.ReadToEndAsync(); - block = JsonSerializer.Deserialize(line); - } - if (null != block) yield return block; + } + } } \ No newline at end of file From 0243d435d6df8d49aef762cef3ed04488389e3d7 Mon Sep 17 00:00:00 2001 From: Tolga Kayhan Date: Thu, 19 Jan 2023 10:17:30 +0000 Subject: [PATCH 7/7] Small changes for Completion Stream and code cleanup --- .../TestHelpers/CompletionTestHelper.cs | 11 +--- OpenAI.SDK/Extensions/HttpclientExtensions.cs | 10 ++- OpenAI.SDK/Extensions/StringExtensions.cs | 20 ++++++ OpenAI.SDK/Interfaces/ICompletionService.cs | 14 ++-- OpenAI.SDK/Managers/OpenAICompletions.cs | 66 +++++++++---------- .../RequestModels/CompletionCreateRequest.cs | 3 +- 6 files changed, 65 insertions(+), 59 deletions(-) create mode 100644 OpenAI.SDK/Extensions/StringExtensions.cs diff --git a/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs b/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs index 447f526b..c12dc539 100644 --- a/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs +++ b/OpenAI.Playground/TestHelpers/CompletionTestHelper.cs @@ -125,17 +125,15 @@ public static async Task RunSimpleCompletionTest3(IOpenAIService sdk) public static async Task RunSimpleCompletionStreamTest(IOpenAIService sdk) { - ConsoleExtensions.WriteLine("Completion Testing is starting:", ConsoleColor.Cyan); + ConsoleExtensions.WriteLine("Completion Stream Testing is starting:", ConsoleColor.Cyan); try { - ConsoleExtensions.WriteLine("Completion Test:", ConsoleColor.DarkCyan); + ConsoleExtensions.WriteLine("Completion Stream Test:", ConsoleColor.DarkCyan); var completionResult = sdk.Completions.CreateCompletionAsStream(new CompletionCreateRequest() { Prompt = "Once upon a time", - // PromptAsList = new []{"Once upon a time"}, - MaxTokens = 1000, - // LogProbs = 1 + MaxTokens = 50 }, Models.Davinci); await foreach (var completion in completionResult) @@ -157,9 +155,6 @@ public static async Task RunSimpleCompletionStreamTest(IOpenAIService sdk) Console.WriteLine(""); Console.WriteLine("Complete"); - - - } catch (Exception e) { diff --git a/OpenAI.SDK/Extensions/HttpclientExtensions.cs b/OpenAI.SDK/Extensions/HttpclientExtensions.cs index 1e956c37..413fbb5f 100644 --- a/OpenAI.SDK/Extensions/HttpclientExtensions.cs +++ b/OpenAI.SDK/Extensions/HttpclientExtensions.cs @@ -1,7 +1,5 @@ -using System.Net.Http; -using System.Net.Http.Headers; +using System.Net.Http.Headers; using System.Net.Http.Json; -using System.Runtime.ConstrainedExecution; using System.Text.Json; using System.Text.Json.Serialization; @@ -31,12 +29,12 @@ public static HttpResponseMessage PostAsStreamAsync(this HttpClient client, stri DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; - JsonContent content = JsonContent.Create(requestModel, mediaType: null, settings); + var content = JsonContent.Create(requestModel, null, settings); - using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri); + using var request = new HttpRequestMessage(HttpMethod.Post, uri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); request.Content = content; - + return client.Send(request, HttpCompletionOption.ResponseHeadersRead); } diff --git a/OpenAI.SDK/Extensions/StringExtensions.cs b/OpenAI.SDK/Extensions/StringExtensions.cs new file mode 100644 index 00000000..0964de3a --- /dev/null +++ b/OpenAI.SDK/Extensions/StringExtensions.cs @@ -0,0 +1,20 @@ +namespace OpenAI.GPT3.Extensions +{ + /// + /// Extension methods for string manipulation + /// + public static class StringExtensions + { + /// + /// Remove the search string from the begging of string if exist + /// + /// + /// + /// + public static string RemoveIfStartWith(this string text, string search) + { + var pos = text.IndexOf(search, StringComparison.Ordinal); + return pos != 0 ? text : text[search.Length..]; + } + } +} \ No newline at end of file diff --git a/OpenAI.SDK/Interfaces/ICompletionService.cs b/OpenAI.SDK/Interfaces/ICompletionService.cs index 75ea9f55..9c8f100f 100644 --- a/OpenAI.SDK/Interfaces/ICompletionService.cs +++ b/OpenAI.SDK/Interfaces/ICompletionService.cs @@ -13,27 +13,27 @@ public interface ICompletionService /// /// Creates a new completion for the provided prompt and parameters /// - /// The ID of the engine to use for this request + /// The ID of the engine to use for this request /// /// - Task CreateCompletion(CompletionCreateRequest createCompletionModel, string? engineId = null); + Task CreateCompletion(CompletionCreateRequest createCompletionModel, string? modelId = null); /// /// Creates a new completion for the provided prompt and parameters and returns a stream of CompletionCreateRequests /// - /// The ID of the engine to use for this request + /// The ID of the engine to use for this request /// /// - IAsyncEnumerable CreateCompletionAsStream(CompletionCreateRequest createCompletionModel, string? engineId = null); + IAsyncEnumerable CreateCompletionAsStream(CompletionCreateRequest createCompletionModel, string? modelId = null); /// /// Creates a new completion for the provided prompt and parameters /// /// - /// The ID of the engine to use for this request + /// The ID of the engine to use for this request /// - Task Create(CompletionCreateRequest createCompletionModel, Models.Model engineId) + Task Create(CompletionCreateRequest createCompletionModel, Models.Model modelId) { - return CreateCompletion(createCompletionModel, engineId.EnumToString()); + return CreateCompletion(createCompletionModel, modelId.EnumToString()); } } \ No newline at end of file diff --git a/OpenAI.SDK/Managers/OpenAICompletions.cs b/OpenAI.SDK/Managers/OpenAICompletions.cs index adfcf2e3..9c6580fa 100644 --- a/OpenAI.SDK/Managers/OpenAICompletions.cs +++ b/OpenAI.SDK/Managers/OpenAICompletions.cs @@ -1,22 +1,22 @@ -using OpenAI.GPT3.Extensions; +using System.Text.Json; +using OpenAI.GPT3.Extensions; using OpenAI.GPT3.Interfaces; using OpenAI.GPT3.ObjectModels.RequestModels; using OpenAI.GPT3.ObjectModels.ResponseModels; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Net.Http.Json; -using System.Net.Http.Headers; namespace OpenAI.GPT3.Managers; + public partial class OpenAIService : ICompletionService { + /// public async Task CreateCompletion(CompletionCreateRequest createCompletionRequest, string? modelId = null) { createCompletionRequest.ProcessModelId(modelId, _defaultModelId); return await _httpClient.PostAndReadAsAsync(_endpointProvider.CompletionCreate(), createCompletionRequest); } + /// public async IAsyncEnumerable CreateCompletionAsStream(CompletionCreateRequest createCompletionRequest, string? modelId = null) { // Mark the request as streaming @@ -25,42 +25,36 @@ public async IAsyncEnumerable CreateCompletionAsStream // Send the request to the CompletionCreate endpoint createCompletionRequest.ProcessModelId(modelId, _defaultModelId); - - using (var response = _httpClient.PostAsStreamAsync(_endpointProvider.CompletionCreate(), createCompletionRequest)) - using (var stream = response.Content.ReadAsStream()) + using var response = _httpClient.PostAsStreamAsync(_endpointProvider.CompletionCreate(), createCompletionRequest); + await using var stream = await response.Content.ReadAsStreamAsync(); + using var reader = new StreamReader(stream); + // Continuously read the stream until the end of it + while (!reader.EndOfStream) { - - using var reader = new StreamReader(stream); - // Continuously read the stream until the end of it - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync(); - // Skip empty lines - if (string.IsNullOrEmpty(line)) continue; + var line = await reader.ReadLineAsync(); + // Skip empty lines + if (string.IsNullOrEmpty(line)) continue; - line = line.Replace("data: ", string.Empty); + line = line.RemoveIfStartWith("data: "); - // Exit the loop if the stream is done - if (line.Contains("[DONE]")) break; + // Exit the loop if the stream is done + if (line.StartsWith("[DONE]")) break; - CompletionCreateResponse? block; - try - { - // When the response is good, each line is a serializable CompletionCreateRequest - block = JsonSerializer.Deserialize(line); - } - catch (Exception) - { - // When the API returns an error, it does not come back as a block, it returns a single character of text ("{"). - // In this instance, read through the rest of the response, which should be a complete object to parse. - line += await reader.ReadToEndAsync(); - block = JsonSerializer.Deserialize(line); - } - if (null != block) yield return block; + CompletionCreateResponse? block; + try + { + // When the response is good, each line is a serializable CompletionCreateRequest + block = JsonSerializer.Deserialize(line); + } + catch (Exception) + { + // When the API returns an error, it does not come back as a block, it returns a single character of text ("{"). + // In this instance, read through the rest of the response, which should be a complete object to parse. + line += await reader.ReadToEndAsync(); + block = JsonSerializer.Deserialize(line); } + if (null != block) yield return block; } - } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs index 4c969f20..a831474c 100644 --- a/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs +++ b/OpenAI.SDK/ObjectModels/RequestModels/CompletionCreateRequest.cs @@ -6,7 +6,6 @@ namespace OpenAI.GPT3.ObjectModels.RequestModels { //TODO add model validation - //TODO check what is string or array for prompt,.. /// /// Create Completion Request Model /// @@ -209,4 +208,4 @@ public IEnumerable Validate() [JsonPropertyName("user")] public string? User { get; set; } } -} +} \ No newline at end of file