Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented a ClientFactory to obtain a new instance of a ChatGptClient #45

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion samples/ChatGptBlazor.Wasm/Components/ChatGptStream.razor
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
@using ChatGptNet.Extensions;
@using ChatGptNet.Models;
@using ChatGptNet;
@using ChatGptNet.ServiceConfigurations
@using Markdig;

@inject IChatGptClient chatGptClient
@inject IChatGptClientFactory chatGptClientFactory
@inject IJSRuntime jsRuntime

<div class="h-100" style="position: relative;">
<div class="w-100 h-100" style="position: absolute;">
<div class="h-100 d-flex flex-column" style="max-height: 100%">

<div class="row mt-4">
<div class="col">
<input type="text" class="form-control" @bind="azureResource" placeholder="Azure OpenAI Resource" />
</div>
<div class="col">
<input type="password" class="form-control" @bind="apiKey" placeholder="Api Key" />
</div>
<div class="col">
<input type="text" class="form-control" @bind="model" placeholder="Model Name" />
</div>
<div class="col">
<button type="button" class="btn btn-primary" @onclick="Connect">
Connect
</button>
</div>
</div>

<div class="my-4" style="height:100%; max-height: 100%; overflow-y: auto;">

@foreach (var message in messages)
Expand Down Expand Up @@ -62,6 +81,12 @@
private string? message;
private bool isAsking;

private string azureResource;
private string apiKey;
private string model;

private IChatGptClient? chatGptClient = null;

private bool CanSendMessage()
=> !string.IsNullOrWhiteSpace(message) && !isAsking;

Expand All @@ -73,6 +98,17 @@
}
}

private void Connect()
{
chatGptClient = chatGptClientFactory.CreateClient(options =>
{
options.UseAzure(azureResource, apiKey, authenticationType: AzureAuthenticationType.ApiKey);
options.DefaultModel = model;
options.MessageLimit = 16; // Default: 10
options.MessageExpiration = TimeSpan.FromMinutes(5); // Default: 1 hour
});
}

private async Task AskAsync()
{
if (!isAsking)
Expand All @@ -82,7 +118,7 @@
messages.Add(new ChatEntry { Sender = ChatGptRoles.User, Content = message! });
var responseMessage = new ChatEntry { Sender = ChatGptRoles.Assistant, Content = string.Empty };

var responseStream = chatGptClient.AskStreamAsync(conversationId, message!);

Check warning on line 121 in samples/ChatGptBlazor.Wasm/Components/ChatGptStream.razor

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

message = string.Empty;

Expand Down
24 changes: 2 additions & 22 deletions samples/ChatGptBlazor.Wasm/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ChatGptBlazor.Wasm;
using ChatGptNet;
using ChatGptNet.ServiceConfigurations;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

Expand All @@ -10,27 +11,6 @@
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

// Adds ChatGPT service and configure options via code.
builder.Services.AddChatGpt(options =>
{
// OpenAI.
//options.UseOpenAI(apiKey: "", organization: "");

// Azure OpenAI Service.
//options.UseAzure(resourceName: "", apiKey: "", authenticationType: AzureAuthenticationType.ApiKey);

options.DefaultModel = "my-model";
options.MessageLimit = 16; // Default: 10
options.MessageExpiration = TimeSpan.FromMinutes(5); // Default: 1 hour
},
httpClient =>
{
// Configures retry policy on the inner HttpClient using Polly.
httpClient.AddStandardResilienceHandler(options =>
{
options.AttemptTimeout.Timeout = TimeSpan.FromMinutes(1);
options.CircuitBreaker.SamplingDuration = TimeSpan.FromMinutes(3);
options.TotalRequestTimeout.Timeout = TimeSpan.FromMinutes(3);
});
});
builder.Services.AddChatGptClientFactory();

await builder.Build().RunAsync();
40 changes: 16 additions & 24 deletions samples/ChatGptBlazor.Wasm/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,32 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8206",
"sslPort": 44385
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5297",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"dotnetRunMessages": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5297"
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7021;http://localhost:5297",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
},
"dotnetRunMessages": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
"applicationUrl": "https://localhost:7021;http://localhost:5297"
}
},
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8206",
"sslPort": 44385
}
}
}
}
38 changes: 38 additions & 0 deletions src/ChatGptNet/ChatGptClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.Extensions.Caching.Memory;

namespace ChatGptNet;

internal class ChatGptClientFactory : IChatGptClientFactory
{
private readonly IServiceProvider services;
private readonly IChatGptCache chatGptCache;
private readonly ChatGptOptionsBuilder defaultOptions;

public ChatGptClientFactory(IServiceProvider services, IChatGptCache chatGptCache, ChatGptOptionsBuilder defaultOptions)
{
this.services = services;
this.chatGptCache = chatGptCache;
this.defaultOptions = defaultOptions;
}

public IChatGptClient CreateClient(Action<IServiceProvider, ChatGptOptionsBuilder>? setupAction)
{
var options = defaultOptions with { };

if (setupAction is not null)
setupAction(services, options);

return new ChatGptClient(new HttpClient(), chatGptCache, options.Build());
}
public IChatGptClient CreateClient(Action<ChatGptOptionsBuilder>? setupAction)
{
if (setupAction is null)
return CreateClient();

return CreateClient((s, o) => setupAction(o));
}
public IChatGptClient CreateClient()
{
return CreateClient((Action<IServiceProvider, ChatGptOptionsBuilder>?)null);
}
}
106 changes: 106 additions & 0 deletions src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ChatGptNet.Models;
using ChatGptNet.ServiceConfigurations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace ChatGptNet;
/// <summary>
/// Provides extension methods for adding ChatGPT Client Factory support in NET applications.
/// </summary>
public static class ChatGptFactoryServiceCollectionExtensions
{
/// <summary>
/// Registers a <see cref="ChatGptClientFactory"/> instance with the specified options.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <param name="setupAction">The <see cref="Action{ChatGptOptions}"/> to configure the provided <see cref="ChatGptOptions"/>.</param>

Check warning on line 22 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has a param tag for 'setupAction', but there is no parameter by that name

Check warning on line 22 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has a param tag for 'setupAction', but there is no parameter by that name

Check warning on line 22 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

XML comment has a param tag for 'setupAction', but there is no parameter by that name
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddChatGptClientFactory(this IServiceCollection services, Action<ChatGptOptionsBuilder> builder)

Check warning on line 24 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Parameter 'builder' has no matching param tag in the XML comment for 'ChatGptFactoryServiceCollectionExtensions.AddChatGptClientFactory(IServiceCollection, Action<ChatGptOptionsBuilder>)' (but other parameters do)

Check warning on line 24 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Parameter 'builder' has no matching param tag in the XML comment for 'ChatGptFactoryServiceCollectionExtensions.AddChatGptClientFactory(IServiceCollection, Action<ChatGptOptionsBuilder>)' (but other parameters do)

Check warning on line 24 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Parameter 'builder' has no matching param tag in the XML comment for 'ChatGptFactoryServiceCollectionExtensions.AddChatGptClientFactory(IServiceCollection, Action<ChatGptOptionsBuilder>)' (but other parameters do)
{
ArgumentNullException.ThrowIfNull(services);

var options = new ChatGptOptionsBuilder();
if (builder is not null)
builder.Invoke(options);

return AddChatGptClientFactoryCore(services, options);
}

/// <summary>
/// Registers a <see cref="ChatGptClientFactory"/> instance.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddChatGptClientFactory(this IServiceCollection services)
{
return services.AddChatGptClientFactory(null);

Check warning on line 42 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot convert null literal to non-nullable reference type.

Check warning on line 42 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot convert null literal to non-nullable reference type.

Check warning on line 42 in src/ChatGptNet/ChatGptFactoryServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot convert null literal to non-nullable reference type.
}

private static IServiceCollection AddChatGptClientFactoryCore(this IServiceCollection services, ChatGptOptionsBuilder deafultOptions)
{
services.AddMemoryCache();
services.AddSingleton<IChatGptCache, ChatGptMemoryCache>();

var httpClientBuilder = services.AddHttpClient<IChatGptClientFactory, ChatGptClientFactory>();
services.AddSingleton<IChatGptClientFactory, ChatGptClientFactory>(
s => new ChatGptClientFactory(s, s.GetRequiredService<IChatGptCache>(), deafultOptions)
);

return services;
}

private static void SetMissingDefaults(ChatGptOptions options)
{
// If the provider is OpenAI and no default model has been specified, uses gpt-3.5-turbo by default.
if (options.ServiceConfiguration is OpenAIChatGptServiceConfiguration && string.IsNullOrWhiteSpace(options.DefaultModel))
{
options.DefaultModel = OpenAIChatGptModels.Gpt35Turbo;
}
}
}

/// <summary>
/// Represents the default builder for configuring ChatGPT client factory.
/// </summary>
/// <seealso cref="IChatGptBuilder"/>"/>
public class ChatGptClientFactoryBuilder : IChatGptClientFactoryBuilder
{
/// <summary>
/// Gets the <see cref="IServiceCollection"/> where ChatGPT services are registered.
/// </summary>
public IServiceCollection Services { get; }

/// <inheritdoc/>
/// <summary>
/// Gets the <see cref="IHttpClientBuilder"/> used to configure the <see cref="HttpClient"/> used by ChatGPT.
/// </summary>
public IHttpClientBuilder HttpClientBuilder { get; }

internal ChatGptClientFactoryBuilder(IServiceCollection services, IHttpClientBuilder httpClientBuilder)
{
Services = services;
HttpClientBuilder = httpClientBuilder;
}
}

/// <summary>
/// Represents a builder for configuring ChatGPT client factory.
/// </summary>
public interface IChatGptClientFactoryBuilder
{
/// <summary>
/// Gets the <see cref="IServiceCollection"/> where ChatGPT services are registered.
/// </summary>
IServiceCollection Services { get; }

/// <summary>
/// Gets the <see cref="IHttpClientBuilder"/> used to configure the <see cref="HttpClient"/> used by ChatGPT.
/// </summary>
IHttpClientBuilder HttpClientBuilder { get; }
}
2 changes: 1 addition & 1 deletion src/ChatGptNet/ChatGptOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace ChatGptNet;
/// <summary>
/// Options class that provides settings for configuring ChatGPT.
/// </summary>
public class ChatGptOptions
public record ChatGptOptions
{
/// <summary>
/// Gets or sets the configuration settings for accessing the service.
Expand Down
2 changes: 1 addition & 1 deletion src/ChatGptNet/ChatGptOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace ChatGptNet;
/// <summary>
/// Builder class to define settings for configuring ChatGPT.
/// </summary>
public class ChatGptOptionsBuilder
public record ChatGptOptionsBuilder
{
/// <summary>
/// Gets or sets the configuration settings for accessing the service.
Expand Down
28 changes: 28 additions & 0 deletions src/ChatGptNet/IChatGptClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace ChatGptNet;

/// <summary>
/// Provides methods to create new instances of <see cref="IChatGptClient"/> at runtime
/// </summary>
public interface IChatGptClientFactory
{
/// <summary>
/// Creates a new instance of a ChatGptClient configured with the supplied action.
/// </summary>
/// <param name="setupAction">The <see cref="Action{ChatGptOptions}"/> to configure the provided <see cref="ChatGptOptions"/>.</param>
/// <returns>A new <see cref="IChatGptClient"/></returns>
IChatGptClient CreateClient(Action<IServiceProvider, ChatGptOptionsBuilder>? setupAction);

/// <summary>
/// Creates a new instance of a ChatGptClient configured with the supplied action.
/// </summary>
/// <param name="setupAction">The <see cref="Action{ChatGptOptions}"/> to configure the provided <see cref="ChatGptOptions"/>.</param>
/// <returns>A new <see cref="IChatGptClient"/></returns>
IChatGptClient CreateClient(Action<ChatGptOptionsBuilder>? setupAction);

/// <summary>
/// Creates a new instance of a ChatGptClient.
/// </summary>
/// <returns>A new <see cref="IChatGptClient"/></returns>
IChatGptClient CreateClient();

}
Loading