Skip to content

Commit

Permalink
Rename configuration class; Add Chat.LastResponse property; Release 2…
Browse files Browse the repository at this point in the history
….2.0
  • Loading branch information
rodion-m committed Apr 19, 2023
1 parent a035c2b commit 664dfd6
Show file tree
Hide file tree
Showing 18 changed files with 159 additions and 92 deletions.
28 changes: 14 additions & 14 deletions OpenAI.ChatGpt.AspNetCore/ChatGPTFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace OpenAI.ChatGpt.AspNetCore;
/// <example>
/// builder.Services.AddHttpClient&lt;ChatGPTFactory&lt;(client =>
/// {
/// client.DefaultRequestHeaders.Authorization = builder.Configuration["ChatGPTCredentials:ApiKey"];
/// client.DefaultRequestHeaders.Authorization = builder.Configuration["OpenAICredentials:ApiKey"];
/// })
/// .AddPolicyHandler(GetRetryPolicy())
/// .AddPolicyHandler(GetCircuitBreakerPolicy());
Expand All @@ -19,16 +19,16 @@ namespace OpenAI.ChatGpt.AspNetCore;
public class ChatGPTFactory : IDisposable
{
private readonly OpenAiClient _client;
private readonly ChatCompletionsConfig _config;
private readonly ChatGPTConfig _config;
private readonly IChatHistoryStorage _chatHistoryStorage;
private readonly ITimeProvider _clock;
private bool _ensureStorageCreatedCalled;
private readonly bool _isHttpClientInjected;

public ChatGPTFactory(
IHttpClientFactory httpClientFactory,
IOptions<ChatGptCredentials> credentials,
IOptions<ChatCompletionsConfig> config,
IOptions<OpenAICredentials> credentials,
IOptions<ChatGPTConfig> config,
IChatHistoryStorage chatHistoryStorage,
ITimeProvider clock)
{
Expand All @@ -42,8 +42,8 @@ public ChatGPTFactory(
}

internal ChatGPTFactory(
IOptions<ChatGptCredentials> credentials,
IOptions<ChatCompletionsConfig> config,
IOptions<OpenAICredentials> credentials,
IOptions<ChatGPTConfig> config,
IChatHistoryStorage chatHistoryStorage,
ITimeProvider clock)
{
Expand All @@ -58,34 +58,34 @@ public ChatGPTFactory(
string apiKey,
IChatHistoryStorage chatHistoryStorage,
ITimeProvider? clock = null,
ChatCompletionsConfig? config = null)
ChatGPTConfig? config = null)
{
if (apiKey == null) throw new ArgumentNullException(nameof(apiKey));
_client = new OpenAiClient(apiKey);
_config = config ?? ChatCompletionsConfig.Default;
_config = config ?? ChatGPTConfig.Default;
_chatHistoryStorage = chatHistoryStorage ?? throw new ArgumentNullException(nameof(chatHistoryStorage));
_clock = clock ?? new TimeProviderUtc();
}

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

public static ChatGPTFactory CreateInMemory(string apiKey, ChatCompletionsConfig? config = null)
public static ChatGPTFactory CreateInMemory(string apiKey, ChatGPTConfig? config = null)
{
if (apiKey == null) throw new ArgumentNullException(nameof(apiKey));
return new ChatGPTFactory(apiKey, new InMemoryChatHistoryStorage(), new TimeProviderUtc(), config);
}

public async Task<ChatGPT> Create(
string userId,
ChatCompletionsConfig? config = null,
ChatGPTConfig? config = null,
bool ensureStorageCreated = true,
CancellationToken cancellationToken = default)
{
Expand All @@ -100,12 +100,12 @@ public async Task<ChatGPT> Create(
_chatHistoryStorage,
_clock,
userId,
ChatCompletionsConfig.Combine(_config, config)
ChatGPTConfig.Combine(_config, config)
);
}

public async Task<ChatGPT> Create(
ChatCompletionsConfig? config = null,
ChatGPTConfig? config = null,
bool ensureStorageCreated = true,
CancellationToken cancellationToken = default)
{
Expand All @@ -117,7 +117,7 @@ public async Task<ChatGPT> Create(
_client,
_chatHistoryStorage,
_clock,
ChatCompletionsConfig.Combine(_config, config)
ChatGPTConfig.Combine(_config, config)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ namespace OpenAI.ChatGpt.AspNetCore.Extensions;

public static class ServiceCollectionExtensions
{
public const string CredentialsConfigSectionPathDefault = "ChatGptCredentials";
public const string CompletionsConfigSectionPathDefault = "ChatCompletionsConfig";
public const string CredentialsConfigSectionPathDefault = "OpenAICredentials";
// ReSharper disable once InconsistentNaming
public const string CchatGPTConfigSectionPathDefault = "ChatGPTConfig";

public static IServiceCollection AddChatGptInMemoryIntegration(
this IServiceCollection services,
bool injectInMemoryChat = true,
string credentialsConfigSectionPath = CredentialsConfigSectionPathDefault,
string completionsConfigSectionPath = CompletionsConfigSectionPathDefault)
string completionsConfigSectionPath = CchatGPTConfigSectionPathDefault)
{
ArgumentNullException.ThrowIfNull(services);
if (string.IsNullOrWhiteSpace(credentialsConfigSectionPath))
Expand Down Expand Up @@ -60,7 +61,7 @@ private static Chat CreateChatGptChat(IServiceProvider provider)
public static IServiceCollection AddChatGptIntegrationCore(
this IServiceCollection services,
string credentialsConfigSectionPath = CredentialsConfigSectionPathDefault,
string completionsConfigSectionPath = CompletionsConfigSectionPathDefault)
string completionsConfigSectionPath = CchatGPTConfigSectionPathDefault)
{
ArgumentNullException.ThrowIfNull(services);
if (string.IsNullOrWhiteSpace(credentialsConfigSectionPath))
Expand All @@ -74,11 +75,11 @@ public static IServiceCollection AddChatGptIntegrationCore(
nameof(completionsConfigSectionPath));
}

services.AddOptions<ChatGptCredentials>()
services.AddOptions<OpenAICredentials>()
.BindConfiguration(credentialsConfigSectionPath)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions<ChatCompletionsConfig>()
services.AddOptions<ChatGPTConfig>()
.BindConfiguration(completionsConfigSectionPath)
.Configure(_ => { }) //optional
.ValidateDataAnnotations()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

namespace OpenAI.ChatGpt.AspNetCore.Models;

public class ChatGptCredentials
// ReSharper disable once InconsistentNaming
public class OpenAICredentials
{
/// <summary>
/// OpenAI API key. Can be issued here: https://platform.openai.com/account/api-keys
Expand Down
2 changes: 1 addition & 1 deletion OpenAI.ChatGpt.AspNetCore/OpenAI.ChatGpt.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<PackageId>OpenAI.ChatGPT.AspNetCore</PackageId>
<PackageProjectUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</PackageProjectUrl>
<Product>OpenAI ChatGPT integration for .NET with DI</Product>
<Version>2.1.0</Version>
<Version>2.2.0</Version>
<Description>OpenAI Chat Completions API (ChatGPT) integration with easy DI supporting (Microsoft.Extensions.DependencyInjection). It allows you to use the API in your .NET applications. Also, the client supports streaming responses (like ChatGPT) via async streams.</Description>
<RepositoryUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</RepositoryUrl>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static IServiceCollection AddChatGptEntityFrameworkIntegration(
this IServiceCollection services,
Action<DbContextOptionsBuilder> optionsAction,
string credentialsConfigSectionPath = CredentialsConfigSectionPathDefault,
string completionsConfigSectionPath = CompletionsConfigSectionPathDefault)
string completionsConfigSectionPath = CchatGPTConfigSectionPathDefault)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(optionsAction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageId>OpenAI.ChatGPT.EntityFrameworkCore</PackageId>
<PackageProjectUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</PackageProjectUrl>
<Product>OpenAI ChatGPT integration for .NET with EF Core storage</Product>
<Version>2.1.0</Version>
<Version>2.2.0</Version>
<Description>OpenAI Chat Completions API (ChatGPT) integration with DI and EF Core supporting. It allows you to use the API in your .NET applications. Also, the client supports streaming responses (like ChatGPT) via async streams.</Description>
<RepositoryUrl>https://github.com/rodion-m/ChatGPT_API_dotnet</RepositoryUrl>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
Expand Down
73 changes: 50 additions & 23 deletions OpenAI.ChatGpt/Chat.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using OpenAI.ChatGpt.Interfaces;
using OpenAI.ChatGpt.Internal;
using OpenAI.ChatGpt.Models;
using OpenAI.ChatGpt.Models.ChatCompletion;
using OpenAI.ChatGpt.Models.ChatCompletion.Messaging;

namespace OpenAI.ChatGpt;
Expand All @@ -11,13 +13,16 @@ namespace OpenAI.ChatGpt;
/// Used for communication between a user and the assistant (ChatGPT).
/// </summary>
/// <remarks>Not thread-safe. Use one instance per user.</remarks>
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class Chat : IDisposable, IAsyncDisposable
{
public Topic Topic { get; }
public string UserId { get; }
public Guid TopicId => Topic.Id;
public bool IsWriting { get; private set; }
public bool IsCancelled => _cts?.IsCancellationRequested ?? false;

public ChatCompletionResponse? LastResponse { get; private set; }

private readonly IChatHistoryStorage _chatHistoryStorage;
private readonly ITimeProvider _clock;
Expand All @@ -44,6 +49,26 @@ internal Chat(
_clearOnDisposal = clearOnDisposal;
}

public void Dispose()
{
_cts?.Dispose();
if (_clearOnDisposal)
{
// TODO: log warning about sync disposal
_chatHistoryStorage.DeleteTopic(UserId, TopicId, default)
.GetAwaiter().GetResult();
}
}

public async ValueTask DisposeAsync()
{
_cts?.Dispose();
if (_clearOnDisposal)
{
await _chatHistoryStorage.DeleteTopic(UserId, TopicId, default);
}
}

public Task<string> GetNextMessageResponse(
string message,
CancellationToken cancellationToken = default)
Expand All @@ -57,26 +82,29 @@ private async Task<string> GetNextMessageResponse(
UserOrSystemMessage message,
CancellationToken cancellationToken)
{
_cts?.Dispose();
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_cts.Token.Register(() => IsWriting = false);

var history = await LoadHistory(cancellationToken);
var messages = history.Append(message);

IsWriting = true;
var response = await _client.GetChatCompletions(
IsWriting = true; //TODO set false on exception
var response = await _client.GetChatCompletionsRaw(
messages,
user: Topic.Config.PassUserIdToOpenAiRequests is true ? UserId : null,
requestModifier: Topic.Config.ModifyRequest,
cancellationToken: _cts.Token
);
SetLastResponse(response);

var assistantMessage = response.GetMessageContent();
await _chatHistoryStorage.SaveMessages(
UserId, TopicId, message, response, _clock.GetCurrentTime(), _cts.Token);
UserId, TopicId, message, assistantMessage, _clock.GetCurrentTime(), _cts.Token);
IsWriting = false;
_isNew = false;

return response;
return assistantMessage;
}

public IAsyncEnumerable<string> StreamNextMessageResponse(
Expand All @@ -95,14 +123,15 @@ private async IAsyncEnumerable<string> StreamNextMessageResponse(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var originalCancellationToken = cancellationToken;
_cts?.Dispose();
_cts = CancellationTokenSource.CreateLinkedTokenSource(originalCancellationToken);
cancellationToken = _cts.Token;
cancellationToken.Register(() => IsWriting = false);

var history = await LoadHistory(cancellationToken);
var messages = history.Append(message);
var sb = new StringBuilder();
IsWriting = true;
IsWriting = true; //TODO set false on exception
var stream = _client.StreamChatCompletions(
messages,
user: Topic.Config.PassUserIdToOpenAiRequests is true ? UserId : null,
Expand All @@ -118,7 +147,12 @@ private async IAsyncEnumerable<string> StreamNextMessageResponse(
}

if(cancellationToken.IsCancellationRequested && !throwOnCancellation)
{
IsWriting = false;
yield break;
}

SetLastResponse(null);

await _chatHistoryStorage.SaveMessages(
UserId, TopicId, message, sb.ToString(), _clock.GetCurrentTime(), cancellationToken);
Expand All @@ -131,29 +165,22 @@ private async Task<IEnumerable<ChatCompletionMessage>> LoadHistory(CancellationT
if (_isNew) return Enumerable.Empty<ChatCompletionMessage>();
return await _chatHistoryStorage.GetMessages(UserId, TopicId, cancellationToken);
}

public void Stop()


/// <summary> Returns topic messages history. </summary>
public Task<IEnumerable<PersistentChatMessage>> GetMessages(
CancellationToken cancellationToken = default)
{
_cts?.Cancel();
return _chatHistoryStorage.GetMessages(UserId, TopicId, cancellationToken);
}

public void Dispose()
private void SetLastResponse(ChatCompletionResponse? response)
{
_cts?.Dispose();
if (_clearOnDisposal)
{
// TODO: log warning about sync disposal
_chatHistoryStorage.DeleteTopic(UserId, TopicId, default)
.GetAwaiter().GetResult();
}
LastResponse = response;
}

public async ValueTask DisposeAsync()
public void Stop()
{
_cts?.Dispose();
if (_clearOnDisposal)
{
await _chatHistoryStorage.DeleteTopic(UserId, TopicId, default);
}
_cts?.Cancel();
}
}
12 changes: 6 additions & 6 deletions OpenAI.ChatGpt/ChatGPT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class ChatGPT : IDisposable
private readonly string _userId;
private readonly IChatHistoryStorage _chatHistoryStorage;
private readonly ITimeProvider _clock;
private readonly ChatCompletionsConfig? _config;
private readonly ChatGPTConfig? _config;
private readonly OpenAiClient _client;
private Chat? _currentChat;

Expand All @@ -25,7 +25,7 @@ public ChatGPT(
IChatHistoryStorage chatHistoryStorage,
ITimeProvider clock,
string userId,
ChatCompletionsConfig? config)
ChatGPTConfig? config)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_userId = userId ?? throw new ArgumentNullException(nameof(userId));
Expand All @@ -41,7 +41,7 @@ public ChatGPT(
OpenAiClient client,
IChatHistoryStorage chatHistoryStorage,
ITimeProvider clock,
ChatCompletionsConfig? config)
ChatGPTConfig? config)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_chatHistoryStorage = chatHistoryStorage ?? throw new ArgumentNullException(nameof(chatHistoryStorage));
Expand All @@ -55,7 +55,7 @@ public ChatGPT(
/// </summary>
public static Task<Chat> CreateInMemoryChat(
string apiKey,
ChatCompletionsConfig? config = null,
ChatGPTConfig? config = null,
UserOrSystemMessage? initialDialog = null,
ITimeProvider? clock = null)
{
Expand Down Expand Up @@ -85,12 +85,12 @@ public async Task<Chat> ContinueOrStartNewTopic(
/// <summary> Starts a new topic. </summary>
public async Task<Chat> StartNewTopic(
string? name = null,
ChatCompletionsConfig? config = null,
ChatGPTConfig? config = null,
UserOrSystemMessage? initialDialog = null,
bool clearOnDisposal = false,
CancellationToken cancellationToken = default)
{
config = ChatCompletionsConfig.CombineOrDefault(_config, config);
config = ChatGPTConfig.CombineOrDefault(_config, config);
var topic = new Topic(_chatHistoryStorage.NewTopicId(), _userId, name, _clock.GetCurrentTime(), config);
await _chatHistoryStorage.AddTopic(topic, cancellationToken);
initialDialog ??= config.GetInitialDialogOrNull();
Expand Down
Loading

0 comments on commit 664dfd6

Please sign in to comment.