| | | 1 | | using System.Net.Http.Json; |
| | | 2 | | using System.Text.Json; |
| | | 3 | | using System.Text.Json.Serialization; |
| | | 4 | | |
| | | 5 | | namespace Intentum.AI.SystemOne; |
| | | 6 | | |
| | | 7 | | /// <summary>HTTP client for a System One compatible <c>POST /v1/systemone</c> endpoint.</summary> |
| | | 8 | | public sealed class SystemOneClient |
| | | 9 | | { |
| | 1 | 10 | | private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; |
| | | 11 | | |
| | | 12 | | private readonly SystemOneOptions _options; |
| | | 13 | | private readonly HttpClient _httpClient; |
| | | 14 | | |
| | | 15 | | /// <summary>Creates a client for the given engine options.</summary> |
| | 4 | 16 | | public SystemOneClient(SystemOneOptions options, HttpClient httpClient) |
| | 4 | 17 | | { |
| | 4 | 18 | | _options = options ?? throw new ArgumentNullException(nameof(options)); |
| | 4 | 19 | | _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); |
| | 4 | 20 | | } |
| | | 21 | | |
| | | 22 | | /// <summary>Sends the state and typed questions and returns the answers synchronously.</summary> |
| | | 23 | | /// <param name="state">State to evaluate (string or object).</param> |
| | | 24 | | /// <param name="questions">Typed questions keyed by your own id.</param> |
| | | 25 | | public SystemOneResponse Decide(object state, IReadOnlyDictionary<string, SystemOneQuestion> questions) => |
| | 4 | 26 | | DecideAsync(state, questions).GetAwaiter().GetResult(); |
| | | 27 | | |
| | | 28 | | /// <summary>Sends the state and typed questions and returns the answers.</summary> |
| | | 29 | | /// <param name="state">State to evaluate (string or object).</param> |
| | | 30 | | /// <param name="questions">Typed questions keyed by your own id.</param> |
| | | 31 | | /// <param name="cancellationToken">Cancellation token.</param> |
| | | 32 | | public async Task<SystemOneResponse> DecideAsync( |
| | | 33 | | object state, |
| | | 34 | | IReadOnlyDictionary<string, SystemOneQuestion> questions, |
| | | 35 | | CancellationToken cancellationToken = default) |
| | 4 | 36 | | { |
| | 4 | 37 | | _options.Validate(); |
| | 4 | 38 | | ArgumentNullException.ThrowIfNull(state); |
| | 4 | 39 | | ArgumentNullException.ThrowIfNull(questions); |
| | | 40 | | |
| | 4 | 41 | | var request = new SystemOneRequest(state, questions, _options.Model); |
| | 4 | 42 | | var json = JsonSerializer.SerializeToUtf8Bytes(request, JsonOptions); |
| | 4 | 43 | | using var message = new HttpRequestMessage(HttpMethod.Post, BuildRequestUri()) |
| | 4 | 44 | | { |
| | 4 | 45 | | Content = new ByteArrayContent(json) |
| | 4 | 46 | | { |
| | 4 | 47 | | Headers = { ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json") { CharSet |
| | 4 | 48 | | } |
| | 4 | 49 | | }; |
| | | 50 | | |
| | 4 | 51 | | if (_options.ApiKey is not null) |
| | 1 | 52 | | message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _options.Api |
| | | 53 | | |
| | 4 | 54 | | var response = await _httpClient |
| | 4 | 55 | | .SendAsync(message, cancellationToken) |
| | 4 | 56 | | .ConfigureAwait(false); |
| | 4 | 57 | | response.EnsureSuccessStatusCode(); |
| | | 58 | | |
| | 4 | 59 | | var payload = await response.Content |
| | 4 | 60 | | .ReadFromJsonAsync<SystemOneResponse>(JsonOptions, cancellationToken) |
| | 4 | 61 | | .ConfigureAwait(false); |
| | | 62 | | |
| | 4 | 63 | | return payload ?? throw new InvalidOperationException("System One engine returned an empty response."); |
| | 4 | 64 | | } |
| | | 65 | | |
| | | 66 | | private Uri BuildRequestUri() |
| | 4 | 67 | | { |
| | 4 | 68 | | var baseUrl = _options.BaseUrl.TrimEnd('/'); |
| | 4 | 69 | | var path = _options.EndpointPath.StartsWith('/') ? _options.EndpointPath : "/" + _options.EndpointPath; |
| | 4 | 70 | | return new Uri(baseUrl + path); |
| | 4 | 71 | | } |
| | | 72 | | |
| | 4 | 73 | | private sealed record SystemOneRequest( |
| | 4 | 74 | | [property: JsonPropertyName("state")] object State, |
| | 4 | 75 | | [property: JsonPropertyName("questions")] IReadOnlyDictionary<string, SystemOneQuestion> Questions, |
| | 4 | 76 | | [property: JsonPropertyName("model")] |
| | 4 | 77 | | [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |
| | 8 | 78 | | string? Model); |
| | | 79 | | } |