From 0ae319df7a43a7d182756bc14c201439d4630c70 Mon Sep 17 00:00:00 2001 From: Rodion Mostovoi Date: Fri, 17 Nov 2023 17:46:54 +0800 Subject: [PATCH] Add initial Azure support; Update to .NET 8 --- .github/workflows/dotnet.yml | 5 +- .github/workflows/publish_to_nuget.yml | 4 +- global.json | 7 +++ .../ChatGpt.BlazorExample.csproj | 4 +- samples/ChatGpt.BlazorExample/Program.cs | 3 +- .../ChatGpt.ConsoleExample.csproj | 2 +- .../ChatGpt.SpectreConsoleExample.csproj | 2 +- .../ChatGpt.TelegramBotExample.csproj | 4 +- src/Directory.Build.props | 3 +- .../OpenAI.ChatGpt.AspNetCore.csproj | 12 ++-- .../OpenAI.ChatGpt.EntityFrameworkCore.csproj | 6 +- src/OpenAI.ChatGpt/AzureOpenAiClient.cs | 57 +++++++++++++++++++ .../ChatCompletion/ChatCompletionRequest.cs | 4 +- src/OpenAI.ChatGpt/OpenAI.ChatGpt.csproj | 2 - src/OpenAI.ChatGpt/OpenAiClient.cs | 46 +++++++++------ ....ChatGpt.Modules.StructuredResponse.csproj | 4 +- ...iClientExtensions.GetStructuredResponse.cs | 2 +- .../OpenAI.ChatGpt.Modules.Translator.csproj | 2 - .../ChatGptTests.cs | 4 +- .../OpenAI.ChatGpt.IntegrationTests.csproj | 17 +++--- .../AzureOpenAiClientTests.cs | 25 ++++++++ .../ChatGptTranslatorServiceTests.cs | 2 +- .../OpenAI.ChatGpt.UnitTests.csproj | 18 +++--- tests/OpenAI.Tests.Shared/Helpers.cs | 43 +++++++++----- .../OpenAI.Tests.Shared.csproj | 10 +++- 25 files changed, 199 insertions(+), 89 deletions(-) create mode 100644 global.json create mode 100644 src/OpenAI.ChatGpt/AzureOpenAiClient.cs create mode 100644 tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/AzureOpenAiClientTests.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 847e689..715d717 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build @@ -28,4 +28,7 @@ jobs: - name: Test env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_ENDPOINT_URL: ${{ secrets.AZURE_OPENAI_ENDPOINT_URL }} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_DEPLOYMENT_NAME }} run: dotnet test --no-build --verbosity normal diff --git a/.github/workflows/publish_to_nuget.yml b/.github/workflows/publish_to_nuget.yml index 5206589..ac86405 100644 --- a/.github/workflows/publish_to_nuget.yml +++ b/.github/workflows/publish_to_nuget.yml @@ -2,7 +2,7 @@ name: Publish NuGet Package on: release: - types: [ ] + types: [ created ] push: branches: [ release ] @@ -17,7 +17,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/global.json b/global.json new file mode 100644 index 0000000..dad2db5 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/samples/ChatGpt.BlazorExample/ChatGpt.BlazorExample.csproj b/samples/ChatGpt.BlazorExample/ChatGpt.BlazorExample.csproj index 4be33d7..41b5579 100644 --- a/samples/ChatGpt.BlazorExample/ChatGpt.BlazorExample.csproj +++ b/samples/ChatGpt.BlazorExample/ChatGpt.BlazorExample.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable e883db38-8c66-433b-a1cf-301b5b3b2171 @@ -9,7 +9,7 @@ - + diff --git a/samples/ChatGpt.BlazorExample/Program.cs b/samples/ChatGpt.BlazorExample/Program.cs index bac98a6..12ce7af 100644 --- a/samples/ChatGpt.BlazorExample/Program.cs +++ b/samples/ChatGpt.BlazorExample/Program.cs @@ -10,8 +10,7 @@ // make sure that you have added the OpenAICredentials:ApiKey to your secrets.json // or environment variables builder.Services.AddChatGptEntityFrameworkIntegration( - options => options.UseSqlite("Data Source=chats.db")) - .AddServerSideBlazor(); + options => options.UseSqlite("Data Source=chats.db")); var app = builder.Build(); diff --git a/samples/ChatGpt.ConsoleExample/ChatGpt.ConsoleExample.csproj b/samples/ChatGpt.ConsoleExample/ChatGpt.ConsoleExample.csproj index 8296b06..e360dbf 100644 --- a/samples/ChatGpt.ConsoleExample/ChatGpt.ConsoleExample.csproj +++ b/samples/ChatGpt.ConsoleExample/ChatGpt.ConsoleExample.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/samples/ChatGpt.SpectreConsoleExample/ChatGpt.SpectreConsoleExample.csproj b/samples/ChatGpt.SpectreConsoleExample/ChatGpt.SpectreConsoleExample.csproj index 3db0558..b440ce0 100644 --- a/samples/ChatGpt.SpectreConsoleExample/ChatGpt.SpectreConsoleExample.csproj +++ b/samples/ChatGpt.SpectreConsoleExample/ChatGpt.SpectreConsoleExample.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/samples/ChatGpt.TelegramBotExample/ChatGpt.TelegramBotExample.csproj b/samples/ChatGpt.TelegramBotExample/ChatGpt.TelegramBotExample.csproj index d32a0d2..7c0cc3f 100644 --- a/samples/ChatGpt.TelegramBotExample/ChatGpt.TelegramBotExample.csproj +++ b/samples/ChatGpt.TelegramBotExample/ChatGpt.TelegramBotExample.csproj @@ -2,14 +2,14 @@ Exe - net7.0 + net8.0 enable enable - + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bd2ad62..975e26e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,7 @@ - 3.1.1 + 3.2.0 + net6.0;net7.0;net8.0 enable enable 12 diff --git a/src/OpenAI.ChatGpt.AspNetCore/OpenAI.ChatGpt.AspNetCore.csproj b/src/OpenAI.ChatGpt.AspNetCore/OpenAI.ChatGpt.AspNetCore.csproj index 17057b0..8449459 100644 --- a/src/OpenAI.ChatGpt.AspNetCore/OpenAI.ChatGpt.AspNetCore.csproj +++ b/src/OpenAI.ChatGpt.AspNetCore/OpenAI.ChatGpt.AspNetCore.csproj @@ -2,7 +2,6 @@ enable enable - 11 Rodion Mostovoi true OpenAI.ChatGPT.AspNetCore @@ -10,7 +9,6 @@ OpenAI ChatGPT integration for .NET with DI OpenAI Chat Completions API (ChatGPT) integration with easy DI supporting (Microsoft.Extensions.DependencyInjection). It allows you to use the API in your .NET applications. Also, the client supports streaming responses (like ChatGPT) via async streams. https://github.com/rodion-m/ChatGPT_API_dotnet - net6.0;net7.0 chatgpt, openai, sdk, api, chatcompletions, gpt3, gpt4, aspnetcore MIT ChatGPT easy DI for ASP.NET Core @@ -36,11 +34,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + diff --git a/src/OpenAI.ChatGpt.EntityFrameworkCore/OpenAI.ChatGpt.EntityFrameworkCore.csproj b/src/OpenAI.ChatGpt.EntityFrameworkCore/OpenAI.ChatGpt.EntityFrameworkCore.csproj index 284a7b3..3d713e9 100644 --- a/src/OpenAI.ChatGpt.EntityFrameworkCore/OpenAI.ChatGpt.EntityFrameworkCore.csproj +++ b/src/OpenAI.ChatGpt.EntityFrameworkCore/OpenAI.ChatGpt.EntityFrameworkCore.csproj @@ -3,7 +3,6 @@ enable enable - 11 Rodion Mostovoi true OpenAI.ChatGPT.EntityFrameworkCore @@ -11,7 +10,6 @@ OpenAI ChatGPT integration for .NET with EF Core storage OpenAI Chat Completions API (ChatGPT) integration with DI and EF Core supporting. It allows you to use the API in your .NET applications. Also, the client supports streaming responses (like ChatGPT) via async streams. https://github.com/rodion-m/ChatGPT_API_dotnet - net6.0;net7.0 chatgpt, openai, sdk, api, chatcompletions, gpt3, gpt4, di, entityframework, ef MIT ChatGPT easy DI with EF Core storage @@ -31,8 +29,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/OpenAI.ChatGpt/AzureOpenAiClient.cs b/src/OpenAI.ChatGpt/AzureOpenAiClient.cs new file mode 100644 index 0000000..6a8e3c1 --- /dev/null +++ b/src/OpenAI.ChatGpt/AzureOpenAiClient.cs @@ -0,0 +1,57 @@ +namespace OpenAI.ChatGpt; + +/// +/// Azure OpenAI services client +/// +/// +/// Docs: https://learn.microsoft.com/en-us/azure/ai-services/openai/reference +/// Models availability by zones: https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models +/// +public class AzureOpenAiClient : OpenAiClient +{ + private readonly string _apiVersion; + private const string DefaultApiVersion = "2023-12-01-preview"; + + //https://github.com/Azure/azure-rest-api-specs/tree/main/specification/cognitiveservices/data-plane/AzureOpenAI/inference + + /// + /// Creates Azure OpenAI services client + /// + /// Endpoint URL like https://{your-resource-name}.openai.azure.com/ + /// Deployment name from the page https://oai.azure.com/deployment + /// Azure OpenAI API Key + /// Azure OpenAI API version + /// + /// See currently available API versions: https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#completions + /// + public AzureOpenAiClient( + string endpointUrl, + string deploymentName, + string azureKey, + string apiVersion = DefaultApiVersion) + { + if (string.IsNullOrWhiteSpace(azureKey)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(azureKey)); + + _apiVersion = apiVersion ?? throw new ArgumentNullException(nameof(apiVersion)); + HttpClient = new HttpClient() + { + BaseAddress = new Uri($"{endpointUrl}/openai/deployments/{deploymentName}/"), + DefaultRequestHeaders = + { + { "api-key", azureKey } + } + }; + IsHttpClientInjected = false; + } + + public AzureOpenAiClient(HttpClient httpClient, string apiVersion) : base(httpClient) + { + _apiVersion = apiVersion ?? throw new ArgumentNullException(nameof(apiVersion)); + } + + protected override string GetChatCompletionsEndpoint() + { + return $"chat/completions?api-version={_apiVersion}"; + } +} \ No newline at end of file diff --git a/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionRequest.cs b/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionRequest.cs index 9834361..d73bd67 100644 --- a/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionRequest.cs +++ b/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionRequest.cs @@ -144,8 +144,8 @@ public int MaxTokens /// /// An object specifying the format that the model must output. /// - [JsonPropertyName("response_format")] - public ChatCompletionResponseFormat ResponseFormat { get; set; } = new(false); + [JsonPropertyName("response_format"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public ChatCompletionResponseFormat? ResponseFormat { get; set; } /// /// This feature is in Beta. diff --git a/src/OpenAI.ChatGpt/OpenAI.ChatGpt.csproj b/src/OpenAI.ChatGpt/OpenAI.ChatGpt.csproj index 9ec483e..89cd06f 100644 --- a/src/OpenAI.ChatGpt/OpenAI.ChatGpt.csproj +++ b/src/OpenAI.ChatGpt/OpenAI.ChatGpt.csproj @@ -3,7 +3,6 @@ enable enable - 11 Rodion Mostovoi OpenAI ChatGPT integration for .NET true @@ -13,7 +12,6 @@ .NET integration for ChatGPT with streaming responses supporting (like ChatGPT) via async streams. https://github.com/rodion-m/ChatGPT_API_dotnet MIT - net6.0;net7.0 OpenAI.ChatGpt Rodion Mostovoi chatgpt, openai, sdk, api, chatcompletions, gpt3, gpt4 diff --git a/src/OpenAI.ChatGpt/OpenAiClient.cs b/src/OpenAI.ChatGpt/OpenAiClient.cs index 1ae2ac5..b430fbe 100644 --- a/src/OpenAI.ChatGpt/OpenAiClient.cs +++ b/src/OpenAI.ChatGpt/OpenAiClient.cs @@ -19,8 +19,8 @@ public class OpenAiClient : IOpenAiClient, IDisposable private static readonly Uri DefaultHostUri = new(DefaultHost); - private readonly HttpClient _httpClient; - private readonly bool _isHttpClientInjected; + protected HttpClient HttpClient; + protected bool IsHttpClientInjected; private bool _disposed; private readonly JsonSerializerOptions _nullIgnoreSerializerOptions = new() @@ -28,6 +28,12 @@ public class OpenAiClient : IOpenAiClient, IDisposable DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + protected OpenAiClient() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + } + /// /// Creates a new OpenAI client with given . /// @@ -39,19 +45,19 @@ public OpenAiClient(string apiKey, string? host = DefaultHost) throw new ArgumentException("API key cannot be null or whitespace.", nameof(apiKey)); var uri = ValidateHost(host); - _httpClient = new HttpClient() + HttpClient = new HttpClient() { BaseAddress = uri }; var header = new AuthenticationHeaderValue("Bearer", apiKey); - _httpClient.DefaultRequestHeaders.Authorization = header; + HttpClient.DefaultRequestHeaders.Authorization = header; } /// /// Creates a new OpenAI client from DI with given . /// /// - /// from DI. It should have an Authorization header set with OpenAI API key. + /// from DI. It should have an Authorization header set with OpenAI API key. /// /// /// Indicates that OpenAI API key is not set in @@ -59,18 +65,18 @@ public OpenAiClient(string apiKey, string? host = DefaultHost) /// public OpenAiClient(HttpClient httpClient) { - _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); ValidateHttpClient(httpClient); - _isHttpClientInjected = true; + IsHttpClientInjected = true; } public void Dispose() { if (_disposed) return; _disposed = true; - if (!_isHttpClientInjected) + if (!IsHttpClientInjected) { - _httpClient.Dispose(); + HttpClient.Dispose(); } GC.SuppressFinalize(this); } @@ -89,8 +95,9 @@ private static Uri ValidateHost(string? host) if (host is null) return DefaultHostUri; if (!Uri.TryCreate(host, UriKind.Absolute, out var uri)) { - throw new ArgumentException("Host must be a valid absolute URI and end with a slash." + - $"For example: {DefaultHost}", nameof(host)); + throw new ArgumentException( + $"Host must be a valid absolute URI and end with a slash. Provided: {host}" + + $"\nCorrect example: {DefaultHost}", nameof(host)); } if(!host.EndsWith("/")) uri = new Uri(host + "/"); @@ -230,8 +237,8 @@ internal async Task GetChatCompletionsRaw( { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); - var response = await _httpClient.PostAsJsonAsync( - ChatCompletionsEndpoint, + var response = await HttpClient.PostAsJsonAsync( + GetChatCompletionsEndpoint(), request, cancellationToken: cancellationToken, options: _nullIgnoreSerializerOptions @@ -247,6 +254,11 @@ internal async Task GetChatCompletionsRaw( return jsonResponse; } + protected virtual string GetChatCompletionsEndpoint() + { + return ChatCompletionsEndpoint; + } + /// public IAsyncEnumerable StreamChatCompletions( IEnumerable messages, @@ -296,7 +308,7 @@ private static ChatCompletionRequest CreateChatCompletionRequest( Stream = stream, User = user, Temperature = temperature, - ResponseFormat = new ChatCompletionRequest.ChatCompletionResponseFormat(jsonMode), + ResponseFormat = jsonMode ? new ChatCompletionRequest.ChatCompletionResponseFormat(jsonMode) : null, Seed = seed, }; requestModifier?.Invoke(request); @@ -339,7 +351,7 @@ public async IAsyncEnumerable StreamChatCompletions( { ArgumentNullException.ThrowIfNull(request); if (request == null) throw new ArgumentNullException(nameof(request)); - EnsureJsonModeIsSupported(request.Model, request.ResponseFormat.Type == ChatCompletionRequest.ResponseTypes.JsonObject); + EnsureJsonModeIsSupported(request.Model, request.ResponseFormat?.Type == ChatCompletionRequest.ResponseTypes.JsonObject); ThrowIfDisposed(); request.Stream = true; await foreach (var response in StreamChatCompletionsRaw(request, cancellationToken)) @@ -355,10 +367,10 @@ public IAsyncEnumerable StreamChatCompletionsRaw( ChatCompletionRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); - EnsureJsonModeIsSupported(request.Model, request.ResponseFormat.Type == ChatCompletionRequest.ResponseTypes.JsonObject); + EnsureJsonModeIsSupported(request.Model, request.ResponseFormat?.Type == ChatCompletionRequest.ResponseTypes.JsonObject); ThrowIfDisposed(); request.Stream = true; - return _httpClient.StreamUsingServerSentEvents + return HttpClient.StreamUsingServerSentEvents ( ChatCompletionsEndpoint, request, diff --git a/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAI.ChatGpt.Modules.StructuredResponse.csproj b/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAI.ChatGpt.Modules.StructuredResponse.csproj index a094b97..d0d5098 100644 --- a/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAI.ChatGpt.Modules.StructuredResponse.csproj +++ b/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAI.ChatGpt.Modules.StructuredResponse.csproj @@ -2,7 +2,6 @@ enable enable - 11 Rodion Mostovoi true OpenAI.ChatGPT.Modules.StructuredResponse @@ -10,7 +9,6 @@ OpenAI ChatGPT structured response module The module for OpenAI ChatGPT that allows to retrive a structured response from ChatGPT. https://github.com/rodion-m/ChatGPT_API_dotnet - net6.0;net7.0 chatgpt, openai, sdk, api, chatcompletions, gpt3, gpt4, json, structured MIT OpenAI ChatGPT structured response @@ -31,7 +29,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAiClientExtensions.GetStructuredResponse.cs b/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAiClientExtensions.GetStructuredResponse.cs index 73c623c..eeeb954 100644 --- a/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAiClientExtensions.GetStructuredResponse.cs +++ b/src/modules/OpenAI.ChatGpt.Modules.StructuredResponse/OpenAiClientExtensions.GetStructuredResponse.cs @@ -15,7 +15,7 @@ public static class OpenAiClientExtensions { Nullability = Nullability.Disabled, PropertyOrder = PropertyOrder.AsDeclared, - PropertyNamingMethod = PropertyNamingMethods.AsDeclared + PropertyNameResolver = PropertyNameResolvers.AsDeclared }; private static readonly JsonSerializerOptions JsonSchemaSerializerOptions = new() { diff --git a/src/modules/OpenAI.ChatGpt.Modules.Translator/OpenAI.ChatGpt.Modules.Translator.csproj b/src/modules/OpenAI.ChatGpt.Modules.Translator/OpenAI.ChatGpt.Modules.Translator.csproj index 782d198..69eb558 100644 --- a/src/modules/OpenAI.ChatGpt.Modules.Translator/OpenAI.ChatGpt.Modules.Translator.csproj +++ b/src/modules/OpenAI.ChatGpt.Modules.Translator/OpenAI.ChatGpt.Modules.Translator.csproj @@ -3,7 +3,6 @@ enable enable - 11 Rodion Mostovoi true OpenAI.ChatGPT.Modules.Translator @@ -11,7 +10,6 @@ OpenAI ChatGPT based language translator OpenAI ChatGPT based language translator. https://github.com/rodion-m/ChatGPT_API_dotnet - net6.0;net7.0 chatgpt, openai, sdk, api, chatcompletions, gpt3, gpt4, translator MIT OpenAI ChatGPT based language translator diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs index cb897ec..2333cf0 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs @@ -1,6 +1,4 @@ -using OpenAI.Tests.Shared; - -namespace OpenAI.ChatGpt.IntegrationTests; +namespace OpenAI.ChatGpt.IntegrationTests; public class ChatGptTests { diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAI.ChatGpt.IntegrationTests.csproj b/tests/OpenAI.ChatGpt.IntegrationTests/OpenAI.ChatGpt.IntegrationTests.csproj index c348426..9544647 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAI.ChatGpt.IntegrationTests.csproj +++ b/tests/OpenAI.ChatGpt.IntegrationTests/OpenAI.ChatGpt.IntegrationTests.csproj @@ -1,23 +1,22 @@ - net7.0 + net8.0 enable enable - false true - - - - - + + + + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/AzureOpenAiClientTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/AzureOpenAiClientTests.cs new file mode 100644 index 0000000..52b686a --- /dev/null +++ b/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/AzureOpenAiClientTests.cs @@ -0,0 +1,25 @@ +namespace OpenAI.ChatGpt.IntegrationTests.OpenAiClientTests; + +public class AzureOpenAiClientTests +{ + private readonly ITestOutputHelper _outputHelper; + private readonly IOpenAiClient _client; + + public AzureOpenAiClientTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + var endpointUrl = Helpers.GetValueFromConfiguration("AZURE_OPENAI_ENDPOINT_URL"); + var azureKey = Helpers.GetValueFromConfiguration("AZURE_OPENAI_API_KEY"); + var deploymentName = Helpers.GetValueFromConfiguration("AZURE_OPENAI_DEPLOYMENT_NAME"); + _client = new AzureOpenAiClient(endpointUrl, deploymentName, azureKey, "2023-12-01-preview"); + } + + [Fact] + public async void Get_chatgpt_response_for_one_message_works() + { + string text = "Who are you? In two words."; + string response = await _client.GetChatCompletions(new UserMessage(text), 64); + _outputHelper.WriteLine(response); + response.Should().NotBeNullOrEmpty(); + } +} \ No newline at end of file diff --git a/tests/OpenAI.ChatGpt.UnitTests/ChatGptTranslatorServiceTests.cs b/tests/OpenAI.ChatGpt.UnitTests/ChatGptTranslatorServiceTests.cs index 8efda52..dfc723f 100644 --- a/tests/OpenAI.ChatGpt.UnitTests/ChatGptTranslatorServiceTests.cs +++ b/tests/OpenAI.ChatGpt.UnitTests/ChatGptTranslatorServiceTests.cs @@ -52,7 +52,7 @@ public async Task Translate_without_source_and_target_languages_uses_default_lan (IOpenAiClient) clientMock.Object, expectedSourceLanguage, expectedTargetLanguage, - null); + (string) null!); translatorServiceMock.Setup(service => service.CreateTextTranslationPrompt( It.IsAny(), It.IsAny())) diff --git a/tests/OpenAI.ChatGpt.UnitTests/OpenAI.ChatGpt.UnitTests.csproj b/tests/OpenAI.ChatGpt.UnitTests/OpenAI.ChatGpt.UnitTests.csproj index 415d312..e367160 100644 --- a/tests/OpenAI.ChatGpt.UnitTests/OpenAI.ChatGpt.UnitTests.csproj +++ b/tests/OpenAI.ChatGpt.UnitTests/OpenAI.ChatGpt.UnitTests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -10,14 +10,14 @@ - - - - - - - - + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/OpenAI.Tests.Shared/Helpers.cs b/tests/OpenAI.Tests.Shared/Helpers.cs index 1c1d6b3..cd3aec4 100644 --- a/tests/OpenAI.Tests.Shared/Helpers.cs +++ b/tests/OpenAI.Tests.Shared/Helpers.cs @@ -1,23 +1,36 @@ -namespace OpenAI.Tests.Shared; +using Microsoft.Extensions.Configuration; -public static class Helpers +namespace OpenAI.Tests.Shared { - public static string GetOpenAiKey() => GetKeyFromEnvironment("OPENAI_API_KEY"); - - public static string? NullIfEmpty(this string? str) + public static class Helpers { - return string.IsNullOrEmpty(str) ? null : str; - } - - public static string GetKeyFromEnvironment(string keyName) - { - if (keyName == null) throw new ArgumentNullException(nameof(keyName)); - var value = Environment.GetEnvironmentVariable(keyName); - if (value is null) + private static IConfiguration Configuration { get; set; } + + static Helpers() + { + Configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets(typeof(Helpers).Assembly) + .Build(); + } + + public static string GetOpenAiKey() => GetValueFromConfiguration("OPENAI_API_KEY"); + + public static string? NullIfEmpty(this string? str) { - throw new InvalidOperationException($"{keyName} is not set as environment variable"); + return string.IsNullOrEmpty(str) ? null : str; } - return value; + public static string GetValueFromConfiguration(string key) + { + ArgumentNullException.ThrowIfNull(key); + var value = Configuration[key]; + if (value is null) + { + throw new InvalidOperationException($"{key} is not set in configuration"); + } + + return value; + } } } \ No newline at end of file diff --git a/tests/OpenAI.Tests.Shared/OpenAI.Tests.Shared.csproj b/tests/OpenAI.Tests.Shared/OpenAI.Tests.Shared.csproj index e8c6a11..18979f4 100644 --- a/tests/OpenAI.Tests.Shared/OpenAI.Tests.Shared.csproj +++ b/tests/OpenAI.Tests.Shared/OpenAI.Tests.Shared.csproj @@ -1,11 +1,17 @@ - net7.0 + net8.0 enable enable - false + c1a19734-c162-40ca-8a60-a21b29a396cd + + + + + +