Skip to content

Commit 78e1ae8

Browse files
committed
Relocate test and refine structure for ai-client multi-provider support
The diff relocates the ChatGptEntityFrameworkIntegrationTests from the IntegrationTests project to the UnitTests project. This was necessary to prevent parallel execution. The diff also introduces minor structural changes, validating base addresses and authorization headers, as necessary, and adds a new method for aiClient creation for ubiquitous usage within services. This refactoring provides better support for handling multiple providers. Appropriate changes in variable lifetimes, argument orderings, exception messages fixes, and encapsulation of shared procedures are addressed. The update is designed to enhance code design and maintainability. In the interest of structured code design and future assimilation of probable changes, the Commit clears any unused/unnecessary method arguments, deals with boolean flag adjustments, and refactors/overloads methods. The diff modifies IConfiguration object flow and moves AzureOpenAiClient's Settings validations for better code organization improving code aesthetics, clarity, and logic flow.
1 parent 86fd5b7 commit 78e1ae8

File tree

14 files changed

+310
-141
lines changed

14 files changed

+310
-141
lines changed

samples/ChatGpt.TelegramBotExample/Helpers.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace ChatGpt.TelegramBotExample;
1+
using OpenAI.ChatGpt;
2+
3+
namespace ChatGpt.TelegramBotExample;
24

35
public static class Helpers
46
{

src/Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>4.0.0</Version>
3+
<Version>4.0.0-alpha</Version>
44
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>

src/OpenAI.ChatGpt.AspNetCore/AiClientFromConfiguration.cs

+18-9
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ private static void ThrowUnkownProviderException(string provider)
4545
}
4646

4747
/// <inheritdoc />
48-
public Task<string> GetChatCompletions(UserOrSystemMessage dialog,
48+
public Task<string> GetChatCompletions(
49+
UserOrSystemMessage dialog,
4950
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
5051
string model = ChatCompletionModels.Default, float temperature = ChatCompletionTemperatures.Default,
5152
string? user = null, bool jsonMode = false, long? seed = null,
@@ -57,7 +58,8 @@ public Task<string> GetChatCompletions(UserOrSystemMessage dialog,
5758
}
5859

5960
/// <inheritdoc />
60-
public Task<string> GetChatCompletions(IEnumerable<ChatCompletionMessage> messages,
61+
public Task<string> GetChatCompletions(
62+
IEnumerable<ChatCompletionMessage> messages,
6163
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
6264
string model = ChatCompletionModels.Default, float temperature = ChatCompletionTemperatures.Default,
6365
string? user = null, bool jsonMode = false, long? seed = null,
@@ -69,7 +71,8 @@ public Task<string> GetChatCompletions(IEnumerable<ChatCompletionMessage> messag
6971
}
7072

7173
/// <inheritdoc />
72-
public Task<ChatCompletionResponse> GetChatCompletionsRaw(IEnumerable<ChatCompletionMessage> messages,
74+
public Task<ChatCompletionResponse> GetChatCompletionsRaw(
75+
IEnumerable<ChatCompletionMessage> messages,
7376
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
7477
string model = ChatCompletionModels.Default, float temperature = ChatCompletionTemperatures.Default,
7578
string? user = null, bool jsonMode = false, long? seed = null,
@@ -81,7 +84,8 @@ public Task<ChatCompletionResponse> GetChatCompletionsRaw(IEnumerable<ChatComple
8184
}
8285

8386
/// <inheritdoc />
84-
public IAsyncEnumerable<string> StreamChatCompletions(IEnumerable<ChatCompletionMessage> messages,
87+
public IAsyncEnumerable<string> StreamChatCompletions(
88+
IEnumerable<ChatCompletionMessage> messages,
8589
int maxTokens = ChatCompletionRequest.MaxTokensDefault,
8690
string model = ChatCompletionModels.Default, float temperature = ChatCompletionTemperatures.Default,
8791
string? user = null, bool jsonMode = false, long? seed = null,
@@ -93,27 +97,32 @@ public IAsyncEnumerable<string> StreamChatCompletions(IEnumerable<ChatCompletion
9397
}
9498

9599
/// <inheritdoc />
96-
public IAsyncEnumerable<string> StreamChatCompletions(UserOrSystemMessage messages,
100+
public IAsyncEnumerable<string> StreamChatCompletions(
101+
UserOrSystemMessage messages,
97102
int maxTokens = ChatCompletionRequest.MaxTokensDefault, string model = ChatCompletionModels.Default,
98103
float temperature = ChatCompletionTemperatures.Default, string? user = null, bool jsonMode = false,
99104
long? seed = null, Action<ChatCompletionRequest>? requestModifier = null,
100105
CancellationToken cancellationToken = default)
101106
{
102-
return _client.StreamChatCompletions(messages, maxTokens, model, temperature, user, jsonMode, seed,
103-
requestModifier, cancellationToken);
107+
return _client.StreamChatCompletions(
108+
messages, maxTokens, model, temperature, user, jsonMode, seed, requestModifier, cancellationToken);
104109
}
105110

106111
/// <inheritdoc />
107-
public IAsyncEnumerable<string> StreamChatCompletions(ChatCompletionRequest request,
112+
public IAsyncEnumerable<string> StreamChatCompletions(
113+
ChatCompletionRequest request,
108114
CancellationToken cancellationToken = default)
109115
{
110116
return _client.StreamChatCompletions(request, cancellationToken);
111117
}
112118

113119
/// <inheritdoc />
114-
public IAsyncEnumerable<ChatCompletionResponse> StreamChatCompletionsRaw(ChatCompletionRequest request,
120+
public IAsyncEnumerable<ChatCompletionResponse> StreamChatCompletionsRaw(
121+
ChatCompletionRequest request,
115122
CancellationToken cancellationToken = default)
116123
{
117124
return _client.StreamChatCompletionsRaw(request, cancellationToken);
118125
}
126+
127+
internal IAiClient GetInnerClient() => _client;
119128
}

src/OpenAI.ChatGpt.AspNetCore/AiClientStartupValidationBackgroundService.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ namespace OpenAI.ChatGpt.AspNetCore;
44

55
internal class AiClientStartupValidationBackgroundService : BackgroundService
66
{
7-
private readonly AiClientFromConfiguration _aiClient;
87

9-
public AiClientStartupValidationBackgroundService(AiClientFromConfiguration aiClient)
8+
public AiClientStartupValidationBackgroundService(AiClientFromConfiguration _)
109
{
11-
_aiClient = aiClient ?? throw new ArgumentNullException(nameof(aiClient));
1210
}
1311

1412
protected override Task ExecuteAsync(CancellationToken stoppingToken) => Task.CompletedTask;

src/OpenAI.ChatGpt.AspNetCore/Extensions/ServiceCollectionExtensions.cs

+71-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using Microsoft.Extensions.DependencyInjection;
2-
using Microsoft.Extensions.Options;
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
33

44
namespace OpenAI.ChatGpt.AspNetCore.Extensions;
55

@@ -14,12 +14,16 @@ public static class ServiceCollectionExtensions
1414

1515
public static IServiceCollection AddChatGptInMemoryIntegration(
1616
this IServiceCollection services,
17+
IConfiguration configuration,
1718
bool injectInMemoryChatService = true,
18-
string credentialsConfigSectionPath = OpenAiCredentialsConfigSectionPathDefault,
1919
string completionsConfigSectionPath = ChatGPTConfigSectionPathDefault,
20+
string credentialsConfigSectionPath = OpenAiCredentialsConfigSectionPathDefault,
21+
string azureOpenAiCredentialsConfigSectionPath = AzureOpenAiCredentialsConfigSectionPathDefault,
22+
string openRouterCredentialsConfigSectionPath = OpenRouterCredentialsConfigSectionPathDefault,
2023
bool validateAiClientProviderOnStart = true)
2124
{
2225
ArgumentNullException.ThrowIfNull(services);
26+
ArgumentNullException.ThrowIfNull(configuration);
2327
if (string.IsNullOrWhiteSpace(credentialsConfigSectionPath))
2428
{
2529
throw new ArgumentException("Value cannot be null or whitespace.",
@@ -32,15 +36,29 @@ public static IServiceCollection AddChatGptInMemoryIntegration(
3236
nameof(completionsConfigSectionPath));
3337
}
3438

39+
if (string.IsNullOrWhiteSpace(azureOpenAiCredentialsConfigSectionPath))
40+
{
41+
throw new ArgumentException("Value cannot be null or whitespace.",
42+
nameof(azureOpenAiCredentialsConfigSectionPath));
43+
}
44+
if (string.IsNullOrWhiteSpace(openRouterCredentialsConfigSectionPath))
45+
{
46+
throw new ArgumentException("Value cannot be null or whitespace.",
47+
nameof(openRouterCredentialsConfigSectionPath));
48+
}
49+
3550
services.AddSingleton<IChatHistoryStorage, InMemoryChatHistoryStorage>();
3651
if (injectInMemoryChatService)
3752
{
3853
services.AddScoped<ChatService>(CreateChatService);
3954
}
4055

4156
return services.AddChatGptIntegrationCore(
42-
credentialsConfigSectionPath: credentialsConfigSectionPath,
57+
configuration,
4358
completionsConfigSectionPath: completionsConfigSectionPath,
59+
credentialsConfigSectionPath: credentialsConfigSectionPath,
60+
azureOpenAiCredentialsConfigSectionPath,
61+
openRouterCredentialsConfigSectionPath,
4462
validateAiClientProviderOnStart: validateAiClientProviderOnStart
4563
);
4664
}
@@ -69,14 +87,16 @@ private static ChatService CreateChatService(IServiceProvider provider)
6987
}
7088

7189
public static IServiceCollection AddChatGptIntegrationCore(this IServiceCollection services,
72-
string credentialsConfigSectionPath = OpenAiCredentialsConfigSectionPathDefault,
90+
IConfiguration configuration,
7391
string completionsConfigSectionPath = ChatGPTConfigSectionPathDefault,
92+
string credentialsConfigSectionPath = OpenAiCredentialsConfigSectionPathDefault,
7493
string azureOpenAiCredentialsConfigSectionPath = AzureOpenAiCredentialsConfigSectionPathDefault,
7594
string openRouterCredentialsConfigSectionPath = OpenRouterCredentialsConfigSectionPathDefault,
7695
ServiceLifetime gptFactoryLifetime = ServiceLifetime.Scoped,
7796
bool validateAiClientProviderOnStart = true)
7897
{
7998
ArgumentNullException.ThrowIfNull(services);
99+
ArgumentNullException.ThrowIfNull(configuration);
80100
if (string.IsNullOrWhiteSpace(credentialsConfigSectionPath))
81101
{
82102
throw new ArgumentException("Value cannot be null or whitespace.",
@@ -89,12 +109,55 @@ public static IServiceCollection AddChatGptIntegrationCore(this IServiceCollecti
89109
nameof(completionsConfigSectionPath));
90110
}
91111

112+
if (string.IsNullOrWhiteSpace(azureOpenAiCredentialsConfigSectionPath))
113+
{
114+
throw new ArgumentException("Value cannot be null or whitespace.",
115+
nameof(azureOpenAiCredentialsConfigSectionPath));
116+
}
117+
if (string.IsNullOrWhiteSpace(openRouterCredentialsConfigSectionPath))
118+
{
119+
throw new ArgumentException("Value cannot be null or whitespace.",
120+
nameof(openRouterCredentialsConfigSectionPath));
121+
}
92122

123+
services.AddOptions<ChatGPTConfig>()
124+
.BindConfiguration(completionsConfigSectionPath)
125+
.Configure(_ => { }) //make optional
126+
.ValidateDataAnnotations()
127+
.ValidateOnStart();
128+
129+
services.AddSingleton<ITimeProvider, TimeProviderUtc>();
130+
services.Add(new ServiceDescriptor(typeof(ChatGPTFactory), typeof(ChatGPTFactory), gptFactoryLifetime));
131+
132+
services.AddAiClient(configuration, credentialsConfigSectionPath, azureOpenAiCredentialsConfigSectionPath, openRouterCredentialsConfigSectionPath, validateAiClientProviderOnStart);
133+
134+
return services;
135+
}
136+
137+
internal static void AddAiClient(
138+
this IServiceCollection services,
139+
IConfiguration configuration,
140+
string credentialsConfigSectionPath,
141+
string azureOpenAiCredentialsConfigSectionPath,
142+
string openRouterCredentialsConfigSectionPath,
143+
bool validateAiClientProviderOnStart)
144+
{
145+
ArgumentNullException.ThrowIfNull(services);
146+
ArgumentNullException.ThrowIfNull(configuration);
147+
if (string.IsNullOrWhiteSpace(credentialsConfigSectionPath))
148+
throw new ArgumentException("Value cannot be null or whitespace.", nameof(credentialsConfigSectionPath));
149+
if (string.IsNullOrWhiteSpace(azureOpenAiCredentialsConfigSectionPath))
150+
throw new ArgumentException("Value cannot be null or whitespace.",
151+
nameof(azureOpenAiCredentialsConfigSectionPath));
152+
if (string.IsNullOrWhiteSpace(openRouterCredentialsConfigSectionPath))
153+
throw new ArgumentException("Value cannot be null or whitespace.",
154+
nameof(openRouterCredentialsConfigSectionPath));
155+
93156
services.AddOptions<OpenAICredentials>()
94157
.BindConfiguration(credentialsConfigSectionPath)
95158
.Configure(_ => { }) //make optional
96159
.ValidateDataAnnotations()
97-
.ValidateOnStart();
160+
.ValidateOnStart();
98161
services.AddOptions<AzureOpenAICredentials>()
99162
.BindConfiguration(azureOpenAiCredentialsConfigSectionPath)
100163
.Configure(_ => { }) //make optional
@@ -105,30 +168,21 @@ public static IServiceCollection AddChatGptIntegrationCore(this IServiceCollecti
105168
.Configure(_ => { }) //make optional
106169
.ValidateDataAnnotations()
107170
.ValidateOnStart();
108-
109-
services.AddOptions<ChatGPTConfig>()
110-
.BindConfiguration(completionsConfigSectionPath)
111-
.Configure(_ => { }) //make optional
112-
.ValidateDataAnnotations()
113-
.ValidateOnStart();
114-
115-
services.AddSingleton<ITimeProvider, TimeProviderUtc>();
116-
services.Add(new ServiceDescriptor(typeof(ChatGPTFactory), typeof(ChatGPTFactory), gptFactoryLifetime));
117171

118172
services.AddHttpClient(nameof(OpenAiClient));
119173
services.AddHttpClient(nameof(AzureOpenAiClient));
120174
services.AddHttpClient(nameof(OpenRouterClient));
121175

122176
services.AddSingleton<IAiClient, AiClientFromConfiguration>();
177+
services.AddSingleton<AiClientFactory>();
123178
#pragma warning disable CS0618 // Type or member is obsolete
179+
// will be removed in 5.0
124180
services.AddSingleton<IOpenAiClient, AiClientFromConfiguration>();
125181
#pragma warning restore CS0618 // Type or member is obsolete
126182

127183
if (validateAiClientProviderOnStart)
128184
{
129185
services.AddHostedService<AiClientStartupValidationBackgroundService>();
130186
}
131-
132-
return services;
133187
}
134188
}

src/OpenAI.ChatGpt.EntityFrameworkCore/Extensions/ServiceCollectionExtensions.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.Configuration;
23
using Microsoft.Extensions.DependencyInjection;
34
using static OpenAI.ChatGpt.AspNetCore.Extensions.ServiceCollectionExtensions;
45

@@ -9,17 +10,20 @@ public static class ServiceCollectionExtensions
910
/// <summary>
1011
/// Adds the <see cref="IChatHistoryStorage"/> implementation using Entity Framework Core.
1112
/// </summary>
12-
public static IServiceCollection AddChatGptEntityFrameworkIntegration(this IServiceCollection services,
13+
public static IServiceCollection AddChatGptEntityFrameworkIntegration(
14+
this IServiceCollection services,
1315
Action<DbContextOptionsBuilder> optionsAction,
14-
string credentialsConfigSectionPath = OpenAiCredentialsConfigSectionPathDefault,
16+
IConfiguration configuration,
1517
string completionsConfigSectionPath = ChatGPTConfigSectionPathDefault,
18+
string credentialsConfigSectionPath = OpenAiCredentialsConfigSectionPathDefault,
1619
string azureOpenAiCredentialsConfigSectionPath = AzureOpenAiCredentialsConfigSectionPathDefault,
1720
string openRouterCredentialsConfigSectionPath = OpenRouterCredentialsConfigSectionPathDefault,
1821
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped,
1922
bool validateAiClientProviderOnStart = true)
2023
{
2124
ArgumentNullException.ThrowIfNull(services);
2225
ArgumentNullException.ThrowIfNull(optionsAction);
26+
ArgumentNullException.ThrowIfNull(configuration);
2327
if (string.IsNullOrWhiteSpace(credentialsConfigSectionPath))
2428
{
2529
throw new ArgumentException("Value cannot be null or whitespace.",
@@ -48,8 +52,10 @@ public static IServiceCollection AddChatGptEntityFrameworkIntegration(this IServ
4852
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, null);
4953
}
5054

51-
return services.AddChatGptIntegrationCore(credentialsConfigSectionPath: credentialsConfigSectionPath,
55+
return services.AddChatGptIntegrationCore(
56+
configuration,
5257
completionsConfigSectionPath: completionsConfigSectionPath,
58+
credentialsConfigSectionPath: credentialsConfigSectionPath,
5359
azureOpenAiCredentialsConfigSectionPath: azureOpenAiCredentialsConfigSectionPath,
5460
openRouterCredentialsConfigSectionPath: openRouterCredentialsConfigSectionPath,
5561
serviceLifetime,

src/OpenAI.ChatGpt/AzureOpenAiClient.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ internal static void SetupHttpClient(HttpClient httpClient, string endpointUrl,
4848
httpClient.DefaultRequestHeaders.Add("api-key", azureKey);
4949
}
5050

51-
public AzureOpenAiClient(HttpClient httpClient, string apiVersion) : base(httpClient)
51+
public AzureOpenAiClient(HttpClient httpClient, string apiVersion)
52+
: base(httpClient, validateAuthorizationHeader: false, validateBaseAddress: true)
5253
{
5354
_apiVersion = apiVersion ?? throw new ArgumentNullException(nameof(apiVersion));
5455
}
5556

56-
public AzureOpenAiClient(HttpClient httpClient) : base(httpClient)
57+
public AzureOpenAiClient(HttpClient httpClient)
58+
: base(httpClient, validateAuthorizationHeader: false, validateBaseAddress: true)
5759
{
5860
_apiVersion = DefaultApiVersion;
5961
}

src/OpenAI.ChatGpt/Models/ChatGPTConfig.cs

+3-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public class ChatGPTConfig
1414
};
1515

1616
private int? _maxTokens;
17-
private string? _model;
1817
private float? _temperature;
1918

2019
/// <summary>
@@ -75,7 +74,7 @@ public int? MaxTokens
7574
{
7675
if (value is { } maxTokens)
7776
{
78-
if (_model is { } model)
77+
if (Model is { } model)
7978
{
8079
ChatCompletionModels.EnsureMaxTokensIsSupported(model, maxTokens);
8180
}
@@ -93,11 +92,7 @@ public int? MaxTokens
9392
/// ID of the model to use. One of: <see cref="ChatCompletionModels"/>
9493
/// Maps to: <see cref="ChatCompletionRequest.Model"/>
9594
/// </summary>
96-
public string? Model
97-
{
98-
get => _model;
99-
set => _model = value;
100-
}
95+
public string? Model { get; set; }
10196

10297
/// <summary>
10398
/// What sampling temperature to use, between 0 and 2.
@@ -161,7 +156,7 @@ internal void ModifyRequest(ChatCompletionRequest request)
161156
(not null, null) => baseConfig,
162157
_ => new ChatGPTConfig()
163158
{
164-
_model = config._model ?? baseConfig._model,
159+
Model = config.Model ?? baseConfig.Model,
165160
_maxTokens = config._maxTokens ?? baseConfig._maxTokens,
166161
_temperature = config._temperature ?? baseConfig._temperature,
167162
PassUserIdToOpenAiRequests = config.PassUserIdToOpenAiRequests ??

0 commit comments

Comments
 (0)