Skip to content

Commit

Permalink
Implemented a ClientFactory to obtain a new instance of a ChatGptClie…
Browse files Browse the repository at this point in the history
…nt (#45)
  • Loading branch information
marcominerva committed Feb 12, 2024
2 parents 6a7192c + dd71418 commit c853466
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 49 deletions.
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 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();

}

0 comments on commit c853466

Please sign in to comment.