From 7b322ef7fbedebd96147ca167676ae972c715679 Mon Sep 17 00:00:00 2001 From: femsci Date: Fri, 17 Nov 2023 22:46:30 +0100 Subject: [PATCH] absolute utter filth yet it's working --- src/Nyanlabs.Umogen.Core/Models/ApiChoice.cs | 15 +++++ src/Nyanlabs.Umogen.Core/Models/ApiRequest.cs | 3 + .../Models/ApiResponse.cs | 16 ++++++ .../Models/ApiResponseFormat.cs | 6 ++ src/Nyanlabs.Umogen.Core/Models/GptMessage.cs | 7 +++ .../Models/UmoDocument.cs | 2 +- src/Nyanlabs.Umogen.Core/Models/UsageStats.cs | 17 ++++++ src/Nyanlabs.Umogen.Core/UmoEngine.cs | 2 +- src/Nyanlabs.Umogen.Core/UmoProcess.cs | 57 ++++++++++++++++++- src/Nyanlabs.Umogen.Core/Umogen.cs | 8 ++- .../GenerationTests.cs | 40 +++++++++++++ 11 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 src/Nyanlabs.Umogen.Core/Models/ApiChoice.cs create mode 100644 src/Nyanlabs.Umogen.Core/Models/ApiResponse.cs create mode 100644 src/Nyanlabs.Umogen.Core/Models/ApiResponseFormat.cs create mode 100644 src/Nyanlabs.Umogen.Core/Models/GptMessage.cs create mode 100644 src/Nyanlabs.Umogen.Core/Models/UsageStats.cs create mode 100644 test/Nyanlabs.Umogen.WebTests/GenerationTests.cs diff --git a/src/Nyanlabs.Umogen.Core/Models/ApiChoice.cs b/src/Nyanlabs.Umogen.Core/Models/ApiChoice.cs new file mode 100644 index 0000000..68462c9 --- /dev/null +++ b/src/Nyanlabs.Umogen.Core/Models/ApiChoice.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nyanlabs.Umogen.Core.Models; + +public class ApiChoice +{ + public string FinishReason { get; set; } = default!; + public int Index { get; set; } + public GptMessage? Message { get; set; } + public GptMessage? Delta { get; set; } + +} diff --git a/src/Nyanlabs.Umogen.Core/Models/ApiRequest.cs b/src/Nyanlabs.Umogen.Core/Models/ApiRequest.cs index 06b7c37..4646f18 100644 --- a/src/Nyanlabs.Umogen.Core/Models/ApiRequest.cs +++ b/src/Nyanlabs.Umogen.Core/Models/ApiRequest.cs @@ -4,4 +4,7 @@ public record ApiRequest { public string Model { get; set; } = "gpt-3.5-turbo"; public float Temperature { get; set; } = 0.7f; + public ICollection Messages { get; set; } = new List(); + public bool Stream { get; set; } + //public ApiResponseFormat ResponseFormat { get; set; } = new(); } diff --git a/src/Nyanlabs.Umogen.Core/Models/ApiResponse.cs b/src/Nyanlabs.Umogen.Core/Models/ApiResponse.cs new file mode 100644 index 0000000..8421f3b --- /dev/null +++ b/src/Nyanlabs.Umogen.Core/Models/ApiResponse.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Nyanlabs.Umogen.Core.Models; + +public class ApiResponse +{ + public string Id { get; set; } = default!; + public string Model { get; set; } = default!; + public string Object { get; set; } = default!; + public UsageStats Usage { get; set; } = default!; + public ICollection Choices { get; set; } = new List(); + public long Created { get; set; } +} diff --git a/src/Nyanlabs.Umogen.Core/Models/ApiResponseFormat.cs b/src/Nyanlabs.Umogen.Core/Models/ApiResponseFormat.cs new file mode 100644 index 0000000..61616b6 --- /dev/null +++ b/src/Nyanlabs.Umogen.Core/Models/ApiResponseFormat.cs @@ -0,0 +1,6 @@ +namespace Nyanlabs.Umogen.Core.Models; + +public class ApiResponseFormat +{ + public string Type { get; set; } = "json_object"; +} diff --git a/src/Nyanlabs.Umogen.Core/Models/GptMessage.cs b/src/Nyanlabs.Umogen.Core/Models/GptMessage.cs new file mode 100644 index 0000000..d85d550 --- /dev/null +++ b/src/Nyanlabs.Umogen.Core/Models/GptMessage.cs @@ -0,0 +1,7 @@ +namespace Nyanlabs.Umogen.Core.Models; + +public record GptMessage(string Role, string Content) +{ + public string Role { get; } = Role; + public string Content { get; } = Content; +} diff --git a/src/Nyanlabs.Umogen.Core/Models/UmoDocument.cs b/src/Nyanlabs.Umogen.Core/Models/UmoDocument.cs index 4f6d715..291b648 100644 --- a/src/Nyanlabs.Umogen.Core/Models/UmoDocument.cs +++ b/src/Nyanlabs.Umogen.Core/Models/UmoDocument.cs @@ -45,6 +45,6 @@ public class UmoDocument : INLSerializable public ValidTime? ValidTime { get; set; } public string NLSerialize() { - return $"{Doctype.Name().Item1} for {Employee.NLSerialize()} working for {Employer.NLSerialize()}; valid {ValidTime?.NLSerialize() ?? "for unspecified time"}."; + return $"{Doctype.Name().Item2} for {Employee.NLSerialize()} working for {Employer.NLSerialize()}; valid {ValidTime?.NLSerialize() ?? "for unspecified time"}."; } } diff --git a/src/Nyanlabs.Umogen.Core/Models/UsageStats.cs b/src/Nyanlabs.Umogen.Core/Models/UsageStats.cs new file mode 100644 index 0000000..45077e6 --- /dev/null +++ b/src/Nyanlabs.Umogen.Core/Models/UsageStats.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Nyanlabs.Umogen.Core.Models; + +public class UsageStats +{ + [JsonPropertyName("completion_tokens")] + public int CompletionTokens { get; set; } + [JsonPropertyName("prompt_tokens")] + public int PromptTokens { get; set; } + [JsonPropertyName("total_tokens")] + public int TotalTokens { get; set; } +} diff --git a/src/Nyanlabs.Umogen.Core/UmoEngine.cs b/src/Nyanlabs.Umogen.Core/UmoEngine.cs index 87771a3..67d21e5 100644 --- a/src/Nyanlabs.Umogen.Core/UmoEngine.cs +++ b/src/Nyanlabs.Umogen.Core/UmoEngine.cs @@ -38,7 +38,7 @@ public class UmoEngine : IDisposable _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); } - private readonly HttpClient _http; + internal readonly HttpClient _http; public async Task ValidateKey() { diff --git a/src/Nyanlabs.Umogen.Core/UmoProcess.cs b/src/Nyanlabs.Umogen.Core/UmoProcess.cs index 439423b..9257e31 100644 --- a/src/Nyanlabs.Umogen.Core/UmoProcess.cs +++ b/src/Nyanlabs.Umogen.Core/UmoProcess.cs @@ -1,3 +1,10 @@ +using System.Data; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; + +using Nyanlabs.Umogen.Core.Models; + namespace Nyanlabs.Umogen.Core; public class UmoProcess @@ -9,8 +16,56 @@ public class UmoProcess private readonly UmoEngine _eng; - public async Task Ask() + public async IAsyncEnumerable Ask(string content) { + ApiRequest req = new() + { + Messages = new List() + { + new("system", Umogen.PROMPT), + new("user", content) + }, + Stream = true + }; + HttpRequestMessage msg = new(HttpMethod.Post, "chat/completions"); + msg.Content = new StringContent(JsonSerializer.Serialize(req, options: Umogen.JSON_OPTS), Encoding.UTF8, "application/json"); + var resp = await _eng._http.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead); + + if (!resp.IsSuccessStatusCode) + { + Console.WriteLine(await resp.Content.ReadAsStringAsync()); + throw new DataException($"Cannot process request: {resp.StatusCode}."); + } + + var apiResp = await resp.Content.ReadAsStreamAsync(); + + var reader = new StreamReader(apiResp); + + string? line; + while ((line = await reader.ReadLineAsync()) != null) + { + if (line.StartsWith("data:")) + { + line = line[5..].Trim(); + } + + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + File.AppendAllText("/tmp/raw.md", line + "\n"); + + ApiResponse respChunk = JsonSerializer.Deserialize(line, options: Umogen.JSON_OPTS)!; + ApiChoice choice = respChunk.Choices.First(); + + if (choice.FinishReason == "stop") + { + break; + } + + yield return choice.Delta?.Content ?? ":<"; + } } } diff --git a/src/Nyanlabs.Umogen.Core/Umogen.cs b/src/Nyanlabs.Umogen.Core/Umogen.cs index 116dbde..b9781e6 100644 --- a/src/Nyanlabs.Umogen.Core/Umogen.cs +++ b/src/Nyanlabs.Umogen.Core/Umogen.cs @@ -1,7 +1,13 @@ +using System.Text.Json; + namespace Nyanlabs.Umogen.Core; public class Umogen { public const string DEFAULT_API_KEY_FILE = "umogenkey.secret"; - public const string PROMPT = "You write Polish legal document in markdown format."; + public const string PROMPT = "You write Polish legal contracts in Polish language in markdown format."; + public static readonly JsonSerializerOptions JSON_OPTS = new(JsonSerializerDefaults.Web) + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + }; } diff --git a/test/Nyanlabs.Umogen.WebTests/GenerationTests.cs b/test/Nyanlabs.Umogen.WebTests/GenerationTests.cs new file mode 100644 index 0000000..72fe9d8 --- /dev/null +++ b/test/Nyanlabs.Umogen.WebTests/GenerationTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Nyanlabs.Umogen.Core; +using Nyanlabs.Umogen.Core.Models; + +namespace Nyanlabs.Umogen.WebTests; + +public class GenerationTests +{ + private Person ModelEmployer => new("Kotek", "Miauczyński", "CBS4327563", "04281308999", new DateTime(2004, 08, 13)); + + private Person ModelEmployee => new("Miau", "Kotczyński", "CBS69696969", "76011694336", new DateTime(1976, 1, 16)); + + [Fact] + public async Task Meow4Me() + { + // Given + var rootDir = Environment.CurrentDirectory.Replace("test/Nyanlabs.Umogen.WebTests/bin/Debug/net8.0", ""); + + UmoEngine eng = new(Path.Combine(rootDir, Core.Umogen.DEFAULT_API_KEY_FILE)); + + // When + UmoDocument doc = new(UmoDoctype.CONTR_EMPLOYMENT, ModelEmployer, ModelEmployee); + + UmoProcess proc = new(eng); + var enu = proc.Ask(doc.NLSerialize()); + + await foreach (var str in enu) + { + Console.Write(str); + File.AppendAllText("/tmp/doc.md", str); + } + + Console.WriteLine("~"); + + } +}