Skip to content

Commit

Permalink
Update OpenAI library with modified client creation and improved stru…
Browse files Browse the repository at this point in the history
…ctured response

This update makes several modifications to the OpenAI .NET library. First, the creation of OpenAI client instances was simplified by removing unnecessary calls to IHttpClientFactory, while credential setup was shifted to HttpClient extension for reusability. The ServiceCollectionExtensions.cs and ChatGPTFactory.cs were updated to reflect these changes.

In addition, support for multiple serialized examples was added to the GetStructuredResponse method extension for OpenAiClient, along with custom JSON serializer options. Now, the expected structured response can include several examples, providing more accurate API responses.

Lastly, the version of the library in Directory.Build.props was incremented to 2.9.1, reflecting these improvements.
  • Loading branch information
rodion-m committed Nov 3, 2023
1 parent 350ae6f commit 477ef3a
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 83 deletions.
5 changes: 5 additions & 0 deletions OpenAI_DotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI.ChatGpt.Modules.Stru
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{068E9E67-C2FC-4F8C-B27C-CB3A8FA44BD8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "configs", "configs", "{77B5B4CD-2299-4FEE-B6C3-1090A8A8F2C2}"
ProjectSection(SolutionItems) = preProject
src\Directory.Build.props = src\Directory.Build.props
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.9.0</Version>
<Version>2.9.1</Version>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
Expand Down
23 changes: 2 additions & 21 deletions src/OpenAI.ChatGpt.AspNetCore/ChatGPTFactory.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
using Microsoft.Extensions.Options;
using OpenAI.ChatGpt.AspNetCore.Models;

namespace OpenAI.ChatGpt.AspNetCore;

/// <summary>
/// Factory for creating <see cref="ChatGPT" /> instances from DI.
/// </summary>
/// <example>
/// builder.Services.AddHttpClient&lt;ChatGPTFactory&lt;("OpenAiClient")
/// .AddPolicyHandler(GetRetryPolicy())
/// .AddPolicyHandler(GetCircuitBreakerPolicy());
/// </example>
[Fody.ConfigureAwait(false)]
// ReSharper disable once InconsistentNaming
public class ChatGPTFactory : IDisposable
Expand All @@ -23,18 +17,15 @@ public class ChatGPTFactory : IDisposable
private volatile bool _ensureStorageCreatedCalled;

public ChatGPTFactory(
IHttpClientFactory httpClientFactory,
IOptions<OpenAICredentials> credentials,
IOpenAiClient client,
IOptions<ChatGPTConfig> config,
IChatHistoryStorage chatHistoryStorage,
ITimeProvider clock)
{
if (httpClientFactory == null) throw new ArgumentNullException(nameof(httpClientFactory));
if (credentials?.Value == null) throw new ArgumentNullException(nameof(credentials));
_config = config?.Value ?? throw new ArgumentNullException(nameof(config));
_chatHistoryStorage = chatHistoryStorage ?? throw new ArgumentNullException(nameof(chatHistoryStorage));
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
_client = CreateOpenAiClient(httpClientFactory, credentials);
_client = client ?? throw new ArgumentNullException(nameof(client));
_isHttpClientInjected = true;
}

Expand Down Expand Up @@ -65,16 +56,6 @@ public ChatGPTFactory(
_clock = clock ?? new TimeProviderUtc();
}

private OpenAiClient CreateOpenAiClient(
IHttpClientFactory httpClientFactory,
IOptions<OpenAICredentials> credentials)
{
var httpClient = httpClientFactory.CreateClient(OpenAiClient.HttpClientName);
httpClient.DefaultRequestHeaders.Authorization = credentials.Value.GetAuthHeader();
httpClient.BaseAddress = new Uri(credentials.Value.ApiHost);
return new OpenAiClient(httpClient);
}

public static ChatGPTFactory CreateInMemory(
string apiKey,
ChatGPTConfig? config = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenAI.ChatGpt.AspNetCore.Models;

namespace OpenAI.ChatGpt.AspNetCore.Extensions;

public static class ServiceCollectionExtensions
{
public const string CredentialsConfigSectionPathDefault = "OpenAICredentials";

// ReSharper disable once InconsistentNaming
public const string ChatGPTConfigSectionPathDefault = "ChatGPTConfig";
public static IServiceCollection AddChatGptInMemoryIntegration(

public static IHttpClientBuilder AddChatGptInMemoryIntegration(
this IServiceCollection services,
bool injectInMemoryChatService = true,
bool injectOpenAiClient = true,
string credentialsConfigSectionPath = CredentialsConfigSectionPathDefault,
string completionsConfigSectionPath = ChatGPTConfigSectionPathDefault)
{
Expand All @@ -23,36 +22,37 @@ public static IServiceCollection AddChatGptInMemoryIntegration(
throw new ArgumentException("Value cannot be null or whitespace.",
nameof(credentialsConfigSectionPath));
}

if (string.IsNullOrWhiteSpace(completionsConfigSectionPath))
{
throw new ArgumentException("Value cannot be null or whitespace.",
nameof(completionsConfigSectionPath));
}
services.AddChatGptIntegrationCore(
credentialsConfigSectionPath,
completionsConfigSectionPath,
injectOpenAiClient: injectOpenAiClient
);

services.AddSingleton<IChatHistoryStorage, InMemoryChatHistoryStorage>();
if(injectInMemoryChatService)
if (injectInMemoryChatService)
{
services.AddScoped<ChatService>(CreateChatService);
}

return services;

return services.AddChatGptIntegrationCore(
credentialsConfigSectionPath,
completionsConfigSectionPath
);
}

private static ChatService CreateChatService(IServiceProvider provider)
{
ArgumentNullException.ThrowIfNull(provider);
var userId = Guid.Empty.ToString();
var storage = provider.GetRequiredService<IChatHistoryStorage>();
if(storage is not InMemoryChatHistoryStorage)
if (storage is not InMemoryChatHistoryStorage)
{
throw new InvalidOperationException(
$"Chat injection is supported only with {nameof(InMemoryChatHistoryStorage)} " +
$"and is not supported for {storage.GetType().FullName}");
}

/*
* .GetAwaiter().GetResult() are safe here because we use sync in memory storage
*/
Expand All @@ -64,20 +64,20 @@ private static ChatService CreateChatService(IServiceProvider provider)
return chat;
}

public static IServiceCollection AddChatGptIntegrationCore(
this IServiceCollection services,
public static IHttpClientBuilder AddChatGptIntegrationCore(
this IServiceCollection services,
string credentialsConfigSectionPath = CredentialsConfigSectionPathDefault,
string completionsConfigSectionPath = ChatGPTConfigSectionPathDefault,
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped,
bool injectOpenAiClient = true
)
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped
)
{
ArgumentNullException.ThrowIfNull(services);
if (string.IsNullOrWhiteSpace(credentialsConfigSectionPath))
{
throw new ArgumentException("Value cannot be null or whitespace.",
nameof(credentialsConfigSectionPath));
}

if (string.IsNullOrWhiteSpace(completionsConfigSectionPath))
{
throw new ArgumentException("Value cannot be null or whitespace.",
Expand All @@ -94,33 +94,18 @@ public static IServiceCollection AddChatGptIntegrationCore(
.ValidateDataAnnotations()
.ValidateOnStart();

if (services.All(it => it.ServiceType != typeof(IHttpClientFactory)))
{
services.AddHttpClient(OpenAiClient.HttpClientName);
}

services.AddSingleton<ITimeProvider, TimeProviderUtc>();
services.Add(new ServiceDescriptor(typeof(ChatGPTFactory), typeof(ChatGPTFactory), serviceLifetime));

if (injectOpenAiClient)
{
AddOpenAiClient(services);
}

return services;
return AddOpenAiClient(services);
}

private static void AddOpenAiClient(IServiceCollection services)
private static IHttpClientBuilder AddOpenAiClient(IServiceCollection services)
{
services.AddSingleton<IOpenAiClient>(provider =>
return services.AddHttpClient<IOpenAiClient, OpenAiClient>((provider, httpClient) =>
{
var credentials = provider.GetRequiredService<IOptions<OpenAICredentials>>().Value;
var factory = provider.GetRequiredService<IHttpClientFactory>();
var httpClient = factory.CreateClient(OpenAiClient.HttpClientName);
httpClient.DefaultRequestHeaders.Authorization = credentials.GetAuthHeader();
httpClient.BaseAddress = new Uri(credentials.ApiHost);
var client = new OpenAiClient(httpClient);
return client;
credentials.SetupHttpClient(httpClient);
});
}
}
7 changes: 7 additions & 0 deletions src/OpenAI.ChatGpt.AspNetCore/Models/OpenAICredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,11 @@ public AuthenticationHeaderValue GetAuthHeader()
{
return new AuthenticationHeaderValue("Bearer", ApiKey);
}

public void SetupHttpClient(HttpClient httpClient)
{
ArgumentNullException.ThrowIfNull(httpClient);
httpClient.DefaultRequestHeaders.Authorization = GetAuthHeader();
httpClient.BaseAddress = new Uri(ApiHost);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ public static class ServiceCollectionExtensions
/// <summary>
/// Adds the <see cref="IChatHistoryStorage"/> implementation using Entity Framework Core.
/// </summary>
public static IServiceCollection AddChatGptEntityFrameworkIntegration(
public static IHttpClientBuilder AddChatGptEntityFrameworkIntegration(
this IServiceCollection services,
Action<DbContextOptionsBuilder> optionsAction,
string credentialsConfigSectionPath = CredentialsConfigSectionPathDefault,
string completionsConfigSectionPath = ChatGPTConfigSectionPathDefault,
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped,
bool injectOpenAiClient = true)
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(optionsAction);
Expand All @@ -30,8 +29,6 @@ public static IServiceCollection AddChatGptEntityFrameworkIntegration(
nameof(completionsConfigSectionPath));
}

services.AddChatGptIntegrationCore(
credentialsConfigSectionPath, completionsConfigSectionPath, serviceLifetime, injectOpenAiClient);
services.AddDbContext<ChatGptDbContext>(optionsAction, serviceLifetime);
switch (serviceLifetime)
{
Expand All @@ -48,6 +45,7 @@ public static IServiceCollection AddChatGptEntityFrameworkIntegration(
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, null);
}

return services;
return services.AddChatGptIntegrationCore(
credentialsConfigSectionPath, completionsConfigSectionPath, serviceLifetime);
}
}
Loading

0 comments on commit 477ef3a

Please sign in to comment.