diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 975e26e..def2249 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 3.2.0 + 3.3.0 net6.0;net7.0;net8.0 enable enable diff --git a/src/OpenAI.ChatGpt.AspNetCore/Extensions/ServiceCollectionExtensions.cs b/src/OpenAI.ChatGpt.AspNetCore/Extensions/ServiceCollectionExtensions.cs index c73c09a..b260297 100644 --- a/src/OpenAI.ChatGpt.AspNetCore/Extensions/ServiceCollectionExtensions.cs +++ b/src/OpenAI.ChatGpt.AspNetCore/Extensions/ServiceCollectionExtensions.cs @@ -90,7 +90,7 @@ public static IHttpClientBuilder AddChatGptIntegrationCore( .ValidateOnStart(); services.AddOptions() .BindConfiguration(completionsConfigSectionPath) - .Configure(_ => { }) //optional + .Configure(_ => { }) //make optional .ValidateDataAnnotations() .ValidateOnStart(); diff --git a/src/OpenAI.ChatGpt/ChatService.cs b/src/OpenAI.ChatGpt/ChatService.cs index a755f5d..a654839 100644 --- a/src/OpenAI.ChatGpt/ChatService.cs +++ b/src/OpenAI.ChatGpt/ChatService.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text; +using OpenAI.ChatGpt.Extensions; using OpenAI.ChatGpt.Interfaces; using OpenAI.ChatGpt.Internal; using OpenAI.ChatGpt.Models; diff --git a/src/OpenAI.ChatGpt/AsyncEnumerableExtensions.cs b/src/OpenAI.ChatGpt/Extensions/AsyncEnumerableExtensions.cs similarity index 96% rename from src/OpenAI.ChatGpt/AsyncEnumerableExtensions.cs rename to src/OpenAI.ChatGpt/Extensions/AsyncEnumerableExtensions.cs index 5d9b22e..63f56ed 100644 --- a/src/OpenAI.ChatGpt/AsyncEnumerableExtensions.cs +++ b/src/OpenAI.ChatGpt/Extensions/AsyncEnumerableExtensions.cs @@ -1,7 +1,7 @@ -namespace OpenAI.ChatGpt; +namespace OpenAI.ChatGpt.Extensions; [Fody.ConfigureAwait(false)] -public static class AsyncEnumerableExtensions +internal static class AsyncEnumerableExtensions { internal static async IAsyncEnumerable ConfigureExceptions( this IAsyncEnumerable stream, diff --git a/src/OpenAI.ChatGpt/HttpClientExtensions.cs b/src/OpenAI.ChatGpt/Extensions/HttpClientExtensions.cs similarity index 99% rename from src/OpenAI.ChatGpt/HttpClientExtensions.cs rename to src/OpenAI.ChatGpt/Extensions/HttpClientExtensions.cs index b87e210..44729f4 100644 --- a/src/OpenAI.ChatGpt/HttpClientExtensions.cs +++ b/src/OpenAI.ChatGpt/Extensions/HttpClientExtensions.cs @@ -4,7 +4,7 @@ using System.Text.Json; using OpenAI.ChatGpt.Exceptions; -namespace OpenAI.ChatGpt; +namespace OpenAI.ChatGpt.Extensions; [Fody.ConfigureAwait(false)] internal static class HttpClientExtensions diff --git a/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionModels.cs b/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionModels.cs index a3e564a..8a98d76 100644 --- a/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionModels.cs +++ b/src/OpenAI.ChatGpt/Models/ChatCompletion/ChatCompletionModels.cs @@ -195,11 +195,15 @@ public static string FromString(string model) return model; } + // TODO move to IOpenAiClient + [Obsolete("This method will be removed in the next major version. Use DisableModelNameValidation from IOpenAiClient instead.")] public static void DisableModelNameValidation() { Interlocked.CompareExchange(ref _validateModelName, 0, 1); } + // TODO move to IOpenAiClient + [Obsolete("This method will be removed in the next major version. Use EnableModelNameValidation from IOpenAiClient instead.")] public static void EnableModelNameValidation() { Interlocked.CompareExchange(ref _validateModelName, 1, 0); @@ -211,8 +215,13 @@ public static void EnsureMaxTokensIsSupported(string model, int maxTokens) if (maxTokens < 1) throw new ArgumentOutOfRangeException(nameof(maxTokens)); if (!MaxTokensLimits.TryGetValue(model, out var limit)) { - throw new ArgumentException($"Invalid model: {model}", nameof(model)); + if (_validateModelName == 1) + { + throw new ArgumentException($"Invalid model: {model}", nameof(model)); + } + return; } + if (maxTokens > limit) { throw new ArgumentOutOfRangeException( diff --git a/src/OpenAI.ChatGpt/Models/ChatCompletion/Messaging/ChatCompletionMessage.cs b/src/OpenAI.ChatGpt/Models/ChatCompletion/Messaging/ChatCompletionMessage.cs index 0ccacb1..d2af0ca 100644 --- a/src/OpenAI.ChatGpt/Models/ChatCompletion/Messaging/ChatCompletionMessage.cs +++ b/src/OpenAI.ChatGpt/Models/ChatCompletion/Messaging/ChatCompletionMessage.cs @@ -19,7 +19,7 @@ public class ChatCompletionMessage public string Role { get; init; } /// The message text - [JsonPropertyName("content")] + [JsonPropertyName("content"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string Content { get; set; } private List? _messages; diff --git a/src/OpenAI.ChatGpt/Models/PersistentChatMessage.cs b/src/OpenAI.ChatGpt/Models/PersistentChatMessage.cs index d8b42b6..2f33068 100644 --- a/src/OpenAI.ChatGpt/Models/PersistentChatMessage.cs +++ b/src/OpenAI.ChatGpt/Models/PersistentChatMessage.cs @@ -2,39 +2,32 @@ namespace OpenAI.ChatGpt.Models; -public class PersistentChatMessage : ChatCompletionMessage -{ - public Guid Id { get; init; } - public string UserId { get; set; } - public Guid TopicId { get; set; } - public DateTimeOffset CreatedAt { get; set; } - - public PersistentChatMessage( - Guid id, +public class PersistentChatMessage(Guid id, string userId, Guid topicId, DateTimeOffset createdAt, string role, - string content) : base(role, content) - { - if (role == null) throw new ArgumentNullException(nameof(role)); - if (content == null) throw new ArgumentNullException(nameof(content)); - Id = id; - UserId = userId ?? throw new ArgumentNullException(nameof(userId)); - TopicId = topicId; - CreatedAt = createdAt; - } - + string content) + : ChatCompletionMessage(role, content) +{ + public Guid Id { get; init; } = id; + public string UserId { get; set; } = userId ?? throw new ArgumentNullException(nameof(userId)); + public Guid TopicId { get; set; } = topicId; + public DateTimeOffset CreatedAt { get; set; } = createdAt; + public PersistentChatMessage( Guid id, string userId, Guid topicId, DateTimeOffset createdAt, - ChatCompletionMessage message) : base(message.Role, message.Content) + ChatCompletionMessage message) + : this( + id, + userId ?? throw new ArgumentNullException(nameof(userId)), + topicId, + createdAt, + message.Role, + message.Content) { - Id = id; - UserId = userId ?? throw new ArgumentNullException(nameof(userId)); - TopicId = topicId; - CreatedAt = createdAt; } } \ No newline at end of file diff --git a/src/OpenAI.ChatGpt/OpenAiClient.cs b/src/OpenAI.ChatGpt/OpenAiClient.cs index b430fbe..6a73f8b 100644 --- a/src/OpenAI.ChatGpt/OpenAiClient.cs +++ b/src/OpenAI.ChatGpt/OpenAiClient.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using OpenAI.ChatGpt.Exceptions; +using OpenAI.ChatGpt.Extensions; using OpenAI.ChatGpt.Models.ChatCompletion; using OpenAI.ChatGpt.Models.ChatCompletion.Messaging; diff --git a/src/OpenAI.ChatGpt/OpenRouterClient.cs b/src/OpenAI.ChatGpt/OpenRouterClient.cs new file mode 100644 index 0000000..1c0072a --- /dev/null +++ b/src/OpenAI.ChatGpt/OpenRouterClient.cs @@ -0,0 +1,29 @@ +using OpenAI.ChatGpt.Models.ChatCompletion; + +namespace OpenAI.ChatGpt; + +/// +/// OpenRouter.ai client +/// +/// +/// Docs: https://openrouter.ai/docs +/// +public class OpenRouterClient : OpenAiClient +{ + private const string DefaultHost = "https://openrouter.ai/api/v1/"; + + public OpenRouterClient(string apiKey, string? host = DefaultHost) + : base(apiKey, host ?? DefaultHost) + { +#pragma warning disable CS0618 // Type or member is obsolete + ChatCompletionModels.DisableModelNameValidation(); +#pragma warning restore CS0618 // Type or member is obsolete + } + + public OpenRouterClient(HttpClient httpClient) : base(httpClient) + { +#pragma warning disable CS0618 // Type or member is obsolete + ChatCompletionModels.DisableModelNameValidation(); +#pragma warning restore CS0618 // Type or member is obsolete + } +} \ No newline at end of file diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptEntityFrameworkIntegrationTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptEntityFrameworkIntegrationTests.cs index eb437a7..e7cfe08 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptEntityFrameworkIntegrationTests.cs +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptEntityFrameworkIntegrationTests.cs @@ -5,6 +5,7 @@ namespace OpenAI.ChatGpt.IntegrationTests; +[Collection("OpenAiTestCollection")] //to prevent parallel execution public class ChatGptEntityFrameworkIntegrationTests { [Fact] diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs index 2333cf0..ff3fa5a 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTests.cs @@ -1,5 +1,6 @@ namespace OpenAI.ChatGpt.IntegrationTests; +[Collection("OpenAiTestCollection")] //to prevent parallel execution public class ChatGptTests { [Fact] diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTranslatorServiceTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTranslatorServiceTests.cs index 57ce0fb..1f97931 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTranslatorServiceTests.cs +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ChatGptTranslatorServiceTests.cs @@ -1,13 +1,20 @@ +using OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; using OpenAI.ChatGpt.Modules.Translator; namespace OpenAI.ChatGpt.IntegrationTests; -public class ChatGptTranslatorServiceTests +[Collection("OpenAiTestCollection")] //to prevent parallel execution +public class ChatGptTranslatorServiceTests : IClassFixture { - private readonly IOpenAiClient _client = new OpenAiClient(Helpers.GetOpenAiKey()); - + private readonly IOpenAiClient _client; + private const string GtpModel = ChatCompletionModels.Gpt4Turbo; + public ChatGptTranslatorServiceTests(OpenAiClientFixture fixture) + { + _client = fixture.Client; + } + [Fact] public async Task Translate_from_English_to_Russian() { diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/AzureOpenAiClientTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/AzureOpenAiClientTests.cs similarity index 53% rename from tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/AzureOpenAiClientTests.cs rename to tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/AzureOpenAiClientTests.cs index 52b686a..7e1c854 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/AzureOpenAiClientTests.cs +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/AzureOpenAiClientTests.cs @@ -1,17 +1,16 @@ -namespace OpenAI.ChatGpt.IntegrationTests.OpenAiClientTests; +using OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; -public class AzureOpenAiClientTests +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests; + +public class AzureOpenAiClientTests : IClassFixture { private readonly ITestOutputHelper _outputHelper; private readonly IOpenAiClient _client; - public AzureOpenAiClientTests(ITestOutputHelper outputHelper) + public AzureOpenAiClientTests(ITestOutputHelper outputHelper, AzureOpenAiClientFixture fixture) { _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"); + _client = fixture.Client; } [Fact] diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/ChatCompletionsApiTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/ChatCompletionsApiTests.cs similarity index 88% rename from tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/ChatCompletionsApiTests.cs rename to tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/ChatCompletionsApiTests.cs index a00412e..08a8580 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/ChatCompletionsApiTests.cs +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/ChatCompletionsApiTests.cs @@ -1,15 +1,17 @@ -namespace OpenAI.ChatGpt.IntegrationTests.OpenAiClientTests; +using OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; + +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests; [Collection("OpenAiTestCollection")] //to prevent parallel execution -public class ChatCompletionsApiTests +public class ChatCompletionsApiTests : IClassFixture { private readonly ITestOutputHelper _outputHelper; - private readonly OpenAiClient _client; + private readonly IOpenAiClient _client; - public ChatCompletionsApiTests(ITestOutputHelper outputHelper) + public ChatCompletionsApiTests(ITestOutputHelper outputHelper, OpenAiClientFixture fixture) { _outputHelper = outputHelper; - _client = new OpenAiClient(Helpers.GetOpenAiKey()); + _client = fixture.Client; } [Fact] diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/ChatCompletionsVisionTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/ChatCompletionsVisionTests.cs new file mode 100644 index 0000000..131621e --- /dev/null +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/ChatCompletionsVisionTests.cs @@ -0,0 +1,15 @@ +using OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; + +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests; + +public class ChatCompletionsVisionTests : IClassFixture +{ + private readonly ITestOutputHelper _outputHelper; + private readonly IOpenAiClient _client; + + public ChatCompletionsVisionTests(ITestOutputHelper outputHelper, OpenAiClientFixture fixture) + { + _outputHelper = outputHelper; + _client = fixture.Client; + } +} \ No newline at end of file diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/AzureOpenAiClientFixture.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/AzureOpenAiClientFixture.cs new file mode 100644 index 0000000..54b2b5d --- /dev/null +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/AzureOpenAiClientFixture.cs @@ -0,0 +1,14 @@ +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; + +public class AzureOpenAiClientFixture +{ + public IOpenAiClient Client { get; } + + public AzureOpenAiClientFixture() + { + var endpointUrl = Helpers.GetRequiredValueFromConfiguration("AZURE_OPENAI_ENDPOINT_URL"); + var azureKey = Helpers.GetRequiredValueFromConfiguration("AZURE_OPENAI_API_KEY"); + var deploymentName = Helpers.GetRequiredValueFromConfiguration("AZURE_OPENAI_DEPLOYMENT_NAME"); + Client = new AzureOpenAiClient(endpointUrl, deploymentName, azureKey, "2023-12-01-preview"); + } +} \ No newline at end of file diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/OpenAiClientFixture.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/OpenAiClientFixture.cs new file mode 100644 index 0000000..288c1aa --- /dev/null +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/OpenAiClientFixture.cs @@ -0,0 +1,7 @@ +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; + +public class OpenAiClientFixture +{ + public IOpenAiClient Client { get; private set; } + = new OpenAiClient(Helpers.GetOpenAiKey()); +} \ No newline at end of file diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/OpenRouterClientFixture.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/OpenRouterClientFixture.cs new file mode 100644 index 0000000..0e49a68 --- /dev/null +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/Fixtures/OpenRouterClientFixture.cs @@ -0,0 +1,7 @@ +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; + +public class OpenRouterClientFixture +{ + public IOpenAiClient Client { get; private set; } + = new OpenRouterClient(Helpers.GetOpenRouterKey()); +} \ No newline at end of file diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/OpenAiClient_GetStructuredResponse.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/OpenAiClient_GetStructuredResponse.cs similarity index 87% rename from tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/OpenAiClient_GetStructuredResponse.cs rename to tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/OpenAiClient_GetStructuredResponse.cs index d921901..702f058 100644 --- a/tests/OpenAI.ChatGpt.IntegrationTests/OpenAiClientTests/OpenAiClient_GetStructuredResponse.cs +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/OpenAiClient_GetStructuredResponse.cs @@ -1,10 +1,17 @@ -using OpenAI.ChatGpt.Modules.StructuredResponse; +using OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; +using OpenAI.ChatGpt.Modules.StructuredResponse; -namespace OpenAI.ChatGpt.IntegrationTests.OpenAiClientTests; +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests; -public class OpenAiClientGetStructuredResponseTests +[Collection("OpenAiTestCollection")] //to prevent parallel execution +public class OpenAiClientGetStructuredResponseTests : IClassFixture { - private readonly OpenAiClient _client = new(Helpers.GetOpenAiKey()); + private readonly IOpenAiClient _client; + + public OpenAiClientGetStructuredResponseTests(OpenAiClientFixture clientFixture) + { + _client = clientFixture.Client; + } [Theory] [InlineData(ChatCompletionModels.Gpt3_5_Turbo)] diff --git a/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/OpenRouterClientTests.cs b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/OpenRouterClientTests.cs new file mode 100644 index 0000000..759f48e --- /dev/null +++ b/tests/OpenAI.ChatGpt.IntegrationTests/ClientTests/OpenRouterClientTests.cs @@ -0,0 +1,28 @@ +using OpenAI.ChatGpt.IntegrationTests.ClientTests.Fixtures; + +namespace OpenAI.ChatGpt.IntegrationTests.ClientTests; + +public class OpenRouterClientTests +{ + public class ChatCompletionsApiTests : IClassFixture + { + private readonly ITestOutputHelper _outputHelper; + private readonly IOpenAiClient _client; + + public ChatCompletionsApiTests(ITestOutputHelper outputHelper, OpenRouterClientFixture fixture) + { + _outputHelper = outputHelper; + _client = fixture.Client; + } + + [Fact] + public async void Get_response_from_mistral7B_for_one_message() + { + string model = "mistralai/mistral-7b-instruct"; + var dialog = Dialog.StartAsUser("Who are you?"); + string response = await _client.GetChatCompletions(dialog, 80, model: model); + _outputHelper.WriteLine(response); + response.Should().NotBeNullOrEmpty(); + } + } +} \ No newline at end of file diff --git a/tests/OpenAI.Tests.Shared/Helpers.cs b/tests/OpenAI.Tests.Shared/Helpers.cs index d088bc1..e7b1746 100644 --- a/tests/OpenAI.Tests.Shared/Helpers.cs +++ b/tests/OpenAI.Tests.Shared/Helpers.cs @@ -1,36 +1,39 @@ using Microsoft.Extensions.Configuration; -namespace OpenAI.Tests.Shared +namespace OpenAI.Tests.Shared; + +public static class Helpers { - public static class Helpers + private static IConfiguration Configuration { get; set; } + + static Helpers() { - private static IConfiguration Configuration { get; set; } + Configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets(typeof(Helpers).Assembly) + .Build(); + } - static Helpers() - { - Configuration = new ConfigurationBuilder() - .AddEnvironmentVariables() - .AddUserSecrets(typeof(Helpers).Assembly) - .Build(); - } + public static string GetOpenAiKey() + => GetRequiredValueFromConfiguration("OPENAI_API_KEY"); + public static string GetOpenRouterKey() + => GetRequiredValueFromConfiguration("OPENROUTER_API_KEY"); - public static string GetOpenAiKey() => GetValueFromConfiguration("OPENAI_API_KEY"); - public static string? NullIfEmpty(this string? str) - { - return string.IsNullOrEmpty(str) ? null : str; - } + public static string? NullIfEmpty(this string? str) + { + return string.IsNullOrEmpty(str) ? null : str; + } - public static string GetValueFromConfiguration(string key) + public static string GetRequiredValueFromConfiguration(string key) + { + ArgumentNullException.ThrowIfNull(key); + var value = Configuration[key]; + if (value is null or { Length: 0 }) { - ArgumentNullException.ThrowIfNull(key); - var value = Configuration[key]; - if (value is null or { Length: 0 }) - { - throw new InvalidOperationException($"{key} is not set in configuration"); - } - - return value; + throw new InvalidOperationException($"{key} is not set in configuration"); } + + return value; } } \ No newline at end of file