diff --git a/Directory.Packages.props b/Directory.Packages.props index 0c674bf741..bd0fa48a3e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -88,9 +88,7 @@ - - - + diff --git a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/AbpAIAbstractionsModule.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/AbpAIAbstractionsModule.cs index cc478d8503..b535cf3235 100644 --- a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/AbpAIAbstractionsModule.cs +++ b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/AbpAIAbstractionsModule.cs @@ -1,7 +1,4 @@ -using Microsoft.Extensions.AI; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Volo.Abp.Modularity; +using Volo.Abp.Modularity; namespace Volo.Abp.AI; diff --git a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClient.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClient.cs index e7458af657..8de74390ee 100644 --- a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClient.cs +++ b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClient.cs @@ -5,4 +5,5 @@ namespace Volo.Abp.AI; public interface IChatClient : IChatClient where TWorkSpace : class { + } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClientAccessor.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClientAccessor.cs new file mode 100644 index 0000000000..b57aeae64d --- /dev/null +++ b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClientAccessor.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.AI; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AI; + +public interface IChatClientAccessor +{ + IChatClient? ChatClient { get; } +} + +public interface IChatClientAccessor : IChatClientAccessor + where TWorkSpace : class +{ +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs index 7f55ad9e6f..4aae8fadb0 100644 --- a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs +++ b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs @@ -11,4 +11,4 @@ public interface IKernelAccessor public interface IKernelAccessor : IKernelAccessor where TWorkSpace : class { -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullChatClientAccessor.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullChatClientAccessor.cs new file mode 100644 index 0000000000..0aa1ba52fc --- /dev/null +++ b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullChatClientAccessor.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.AI; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AI; + +[Dependency(TryRegister = true)] +[ExposeServices(typeof(IChatClientAccessor))] +public class NullChatClientAccessor : IChatClientAccessor +{ + public IChatClient? ChatClient => null; +} + +[Dependency(TryRegister = true)] +[ExposeServices(typeof(IChatClientAccessor<>))] +public class NullChatClientAccessor : IChatClientAccessor + where TWorkSpace : class +{ + public IChatClient? ChatClient => null; +} diff --git a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullKernelAccessor.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullKernelAccessor.cs new file mode 100644 index 0000000000..e05ad69553 --- /dev/null +++ b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullKernelAccessor.cs @@ -0,0 +1,20 @@ + +using Microsoft.SemanticKernel; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AI; + +[Dependency(TryRegister = true)] +[ExposeServices(typeof(IKernelAccessor))] +public class NullKernelAccessor : IKernelAccessor +{ + public Kernel? Kernel => null; +} + +[Dependency(TryRegister = true)] +[ExposeServices(typeof(IKernelAccessor<>))] +public class NullKernelAccessor : IKernelAccessor + where TWorkSpace : class +{ + public Kernel? Kernel => null; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs index f13a6e6f02..81d3866199 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.ChatCompletion; using Volo.Abp.Modularity; namespace Volo.Abp.AI; @@ -17,80 +16,88 @@ public class AbpAIModule : AbpModule { public const string DefaultWorkspaceName = "Default"; - public override void PostConfigureServices(ServiceConfigurationContext context) + public override void ConfigureServices(ServiceConfigurationContext context) { - var options = context.Services.ExecutePreConfiguredActions(); + var options = context.Services.ExecutePreConfiguredActions(); - context.Services.Configure(workspaceOptions => + context.Services.Configure(workspaceOptions => { - workspaceOptions.ConfiguredWorkspaceNames.UnionWith(options.Workspaces.Select(x => x.Key).ToArray()); + workspaceOptions.ConfiguredWorkspaceNames.AddIfNotContains( + options.Workspaces.Select(x => x.Key) + ); }); foreach (var workspaceConfig in options.Workspaces.Values) { - if (workspaceConfig.ChatClient?.Builder is null) - { - continue; - } - - foreach (var builderConfigurer in workspaceConfig.ChatClient.BuilderConfigurers) - { - builderConfigurer.Action(workspaceConfig.ChatClient.Builder!); - } - - context.Services.AddKeyedChatClient( - AbpAIOptions.GetChatClientServiceKeyName(workspaceConfig.Name), - provider => workspaceConfig.ChatClient.Builder!.Build(provider), - ServiceLifetime.Transient + ConfigureChatClient(context, workspaceConfig); + ConfigureKernel(context, workspaceConfig); + } + + context.Services.TryAddTransient(typeof(IChatClient<>), typeof(TypedChatClient<>)); + context.Services.TryAddTransient(typeof(IKernelAccessor<>), typeof(KernelAccessor<>)); + } + + private static void ConfigureKernel(ServiceConfigurationContext context, WorkspaceConfiguration workspaceConfig) + { + if (workspaceConfig.Kernel.Builder is null) + { + return; + } + + foreach (var builderConfigurer in workspaceConfig.Kernel.BuilderConfigurers) + { + builderConfigurer.Action(workspaceConfig.Kernel.Builder!); + } + + // TODO: Check if we can use transient instead of singleton for Kernel + context.Services.AddKeyedTransient( + AbpAIWorkspaceOptions.GetKernelServiceKeyName(workspaceConfig.Name), + (provider, _) => workspaceConfig.Kernel.Builder!.Build()); + + if (workspaceConfig.Name == DefaultWorkspaceName) + { + context.Services.AddTransient(sp => sp.GetRequiredKeyedService( + AbpAIWorkspaceOptions.GetKernelServiceKeyName(workspaceConfig.Name) + ) ); + } - if (workspaceConfig.Name == DefaultWorkspaceName) - { - context.Services.AddTransient(sp => sp.GetRequiredKeyedService( - AbpAIOptions.GetChatClientServiceKeyName(workspaceConfig.Name) - ) - ); - } + if (workspaceConfig.ChatClient?.Builder is null) + { + context.Services.AddKeyedTransient( + AbpAIWorkspaceOptions.GetChatClientServiceKeyName(workspaceConfig.Name), + (sp, _) => sp.GetKeyedService(AbpAIWorkspaceOptions.GetKernelServiceKeyName(workspaceConfig.Name))? + .GetRequiredService() + ?? throw new InvalidOperationException("Kernel or IChatClient not found with workspace name: " + workspaceConfig.Name) + ); } + } - context.Services.TryAddTransient(typeof(IChatClient<>), typeof(TypedChatClient<>)); + private static void ConfigureChatClient(ServiceConfigurationContext context, WorkspaceConfiguration workspaceConfig) + { + if (workspaceConfig.ChatClient.Builder is null) + { + return; + } - foreach (var workspaceConfig in options.Workspaces.Values) + foreach (var builderConfigurer in workspaceConfig.ChatClient.BuilderConfigurers) { - if (workspaceConfig.Kernel?.Builder is null) - { - continue; - } - - foreach (var builderConfigurer in workspaceConfig.Kernel.BuilderConfigurers) - { - builderConfigurer.Action(workspaceConfig.Kernel.Builder!); - } - - // TODO: Check if we can use transient instead of singleton for Kernel - context.Services.AddKeyedTransient( - AbpAIOptions.GetKernelServiceKeyName(workspaceConfig.Name), - (provider, _) => workspaceConfig.Kernel.Builder!.Build()); - - if (workspaceConfig.Name == DefaultWorkspaceName) - { - context.Services.AddTransient(sp => sp.GetRequiredKeyedService( - AbpAIOptions.GetKernelServiceKeyName(workspaceConfig.Name) - ) - ); - } - - if (workspaceConfig.ChatClient?.Builder is null) - { - context.Services.AddKeyedTransient( - AbpAIOptions.GetChatClientServiceKeyName(workspaceConfig.Name), - (sp, _) => sp.GetKeyedService(AbpAIOptions.GetKernelServiceKeyName(workspaceConfig.Name))? - .GetRequiredService() - ?? throw new InvalidOperationException("Kernel or IChatClient not found with workspace name: " + workspaceConfig.Name) - ); - } + builderConfigurer.Action(workspaceConfig.ChatClient.Builder); } - context.Services.TryAddTransient(typeof(IKernelAccessor<>), typeof(TypedKernelAccessor<>)); + var serviceName = AbpAIWorkspaceOptions.GetChatClientServiceKeyName(workspaceConfig.Name); + + context.Services.AddKeyedChatClient( + serviceName, + provider => workspaceConfig.ChatClient.Builder.Build(provider), + ServiceLifetime.Transient + ); + + if (workspaceConfig.Name == DefaultWorkspaceName) + { + context.Services.AddTransient( + sp => sp.GetRequiredKeyedService(serviceName) + ); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIOptions.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIOptions.cs index b5639580d7..4efe59ed5c 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIOptions.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIOptions.cs @@ -1,19 +1,8 @@ +using System.Collections.Generic; + namespace Volo.Abp.AI; public class AbpAIOptions { - public const string ChatClientServiceKeyNamePrefix = "Abp.AI.ChatClient_"; - public const string KernelServiceKeyNamePrefix = "Abp.AI.Kernel_"; - - public WorkspaceConfigurationDictionary Workspaces { get; } = new(); - - public static string GetChatClientServiceKeyName(string name) - { - return $"{ChatClientServiceKeyNamePrefix}{name}"; - } - - public static string GetKernelServiceKeyName(string name) - { - return $"{KernelServiceKeyNamePrefix}{name}"; - } + public HashSet ConfiguredWorkspaceNames { get; } = new(); } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIWorkspaceOptions.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIWorkspaceOptions.cs index 1fad742c04..e027c185f6 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIWorkspaceOptions.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIWorkspaceOptions.cs @@ -1,8 +1,24 @@ -using System.Collections.Generic; - namespace Volo.Abp.AI; +/// +/// Pre-configured options for the AI workspaces. Not used via Options pattern. Use it with 'PreConfigure' method in a Module class. +/// In example: +/// PreConfigure<AbpAIWorkspaceOptions>(options => { }); +/// public class AbpAIWorkspaceOptions { - public HashSet ConfiguredWorkspaceNames { get; } = new(); + public const string ChatClientServiceKeyNamePrefix = "Abp.AI.ChatClient_"; + public const string KernelServiceKeyNamePrefix = "Abp.AI.Kernel_"; + + public WorkspaceConfigurationDictionary Workspaces { get; } = new(); + + public static string GetChatClientServiceKeyName(string name) + { + return $"{ChatClientServiceKeyNamePrefix}{name}"; + } + + public static string GetKernelServiceKeyName(string name) + { + return $"{KernelServiceKeyNamePrefix}{name}"; + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs new file mode 100644 index 0000000000..1a852838fc --- /dev/null +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AI; + +[Dependency(ReplaceServices = true, TryRegister = true)] +[ExposeServices(typeof(IChatClientAccessor))] +public class ChatClientAccessor : IChatClientAccessor +{ + public IChatClient? ChatClient { get; } + + public ChatClientAccessor(IServiceProvider serviceProvider) + { + ChatClient = serviceProvider.GetKeyedService( + AbpAIWorkspaceOptions.GetChatClientServiceKeyName( + AbpAIModule.DefaultWorkspaceName)); + } +} + +[Dependency(ReplaceServices = true, TryRegister = true)] +[ExposeServices(typeof(IChatClientAccessor))] +public class ChatClientAccessor : IChatClientAccessor + where TWorkSpace : class +{ + public IChatClient? ChatClient { get; } + + public ChatClientAccessor(IServiceProvider serviceProvider) + { + ChatClient = serviceProvider.GetKeyedService( + AbpAIWorkspaceOptions.GetChatClientServiceKeyName( + WorkspaceNameAttribute.GetWorkspaceName())); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientConfiguration.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientConfiguration.cs index 830d6fd87c..601a6bbe07 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientConfiguration.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientConfiguration.cs @@ -9,8 +9,6 @@ public class ChatClientConfiguration public BuilderConfigurerList BuilderConfigurers { get; } = new(); - // TODO: Base chat client (for inheriting a chat client configuration from some other one) - public void ConfigureBuilder(Action configureAction) { BuilderConfigurers.Add(configureAction); diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedKernelAccessor.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelAccessor.cs similarity index 63% rename from framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedKernelAccessor.cs rename to framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelAccessor.cs index 09d0132676..ba9d305312 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedKernelAccessor.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelAccessor.cs @@ -4,15 +4,15 @@ using Microsoft.SemanticKernel; namespace Volo.Abp.AI; -public class TypedKernelAccessor : IKernelAccessor +public class KernelAccessor : IKernelAccessor where TWorkSpace : class { public Kernel? Kernel { get; } - public TypedKernelAccessor(IServiceProvider serviceProvider) + public KernelAccessor(IServiceProvider serviceProvider) { Kernel = serviceProvider.GetKeyedService( - AbpAIOptions.GetKernelServiceKeyName( + AbpAIWorkspaceOptions.GetKernelServiceKeyName( WorkspaceNameAttribute.GetWorkspaceName())); } } diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs index c75d9b0c97..72ccfdaf38 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs @@ -10,7 +10,7 @@ public class TypedChatClient : DelegatingChatClient, IChatClient( - AbpAIOptions.GetChatClientServiceKeyName( + AbpAIWorkspaceOptions.GetChatClientServiceKeyName( WorkspaceNameAttribute.GetWorkspaceName())) ) {