Browse Source

Refactor AI workspace options and add chat client accessors

Introduces IChatClientAccessor and related implementations for workspace-based chat client resolution. Refactors AbpAIOptions and AbpAIWorkspaceOptions, moving workspace configuration logic and service key generation to AbpAIWorkspaceOptions. Updates AbpAIModule to use new accessors and configuration structure, and adds ChatClientAccessor and NullChatClientAccessor classes for dependency injection. Improves separation of concerns and prepares for more flexible workspace management.
pull/23782/head
enisn 12 months ago
parent
commit
5d79851c93
No known key found for this signature in database GPG Key ID: A052619F04155D1C
  1. 14
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClientAccessor.cs
  2. 2
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs
  3. 19
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullChatClientAccessor.cs
  4. 20
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/NullKernelAccessor.cs
  5. 125
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs
  6. 19
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIOptions.cs
  7. 24
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIWorkspaceOptions.cs
  8. 35
      framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientAccessor.cs
  9. 2
      framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelAccessor.cs
  10. 2
      framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs

14
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<TWorkSpace> : IChatClientAccessor
where TWorkSpace : class
{
}

2
framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs

@ -11,4 +11,4 @@ public interface IKernelAccessor
public interface IKernelAccessor<TWorkSpace> : IKernelAccessor
where TWorkSpace : class
{
}
}

19
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<TWorkSpace> : IChatClientAccessor<TWorkSpace>
where TWorkSpace : class
{
public IChatClient? ChatClient => null;
}

20
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<TWorkSpace> : IKernelAccessor<TWorkSpace>
where TWorkSpace : class
{
public Kernel? Kernel => null;
}

125
framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs

@ -18,83 +18,86 @@ public class AbpAIModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
var options = context.Services.ExecutePreConfiguredActions<AbpAIOptions>();
var options = context.Services.ExecutePreConfiguredActions<AbpAIWorkspaceOptions>();
context.Services.Configure<AbpAIWorkspaceOptions>(workspaceOptions =>
context.Services.Configure<AbpAIOptions>(workspaceOptions =>
{
workspaceOptions.ConfiguredWorkspaceNames.AddIfNotContains(
options.Workspaces.Select(x => x.Key)
);
});
//TODO: Refactor & merge foreach loops
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);
}
var serviceName = AbpAIOptions.GetChatClientServiceKeyName(workspaceConfig.Name);
context.Services.AddKeyedChatClient(
serviceName,
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<Kernel>(
AbpAIWorkspaceOptions.GetKernelServiceKeyName(workspaceConfig.Name),
(provider, _) => workspaceConfig.Kernel.Builder!.Build());
if (workspaceConfig.Name == DefaultWorkspaceName)
{
context.Services.AddTransient<Kernel>(sp => sp.GetRequiredKeyedService<Kernel>(
AbpAIWorkspaceOptions.GetKernelServiceKeyName(workspaceConfig.Name)
)
);
}
if (workspaceConfig.Name == DefaultWorkspaceName)
{
context.Services.AddTransient<IChatClient>(
sp => sp.GetRequiredKeyedService<IChatClient>(serviceName)
);
}
if (workspaceConfig.ChatClient?.Builder is null)
{
context.Services.AddKeyedTransient<IChatClient>(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(workspaceConfig.Name),
(sp, _) => sp.GetKeyedService<Kernel>(AbpAIWorkspaceOptions.GetKernelServiceKeyName(workspaceConfig.Name))?
.GetRequiredService<IChatClient>()
?? 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<Kernel>(
AbpAIOptions.GetKernelServiceKeyName(workspaceConfig.Name),
(provider, _) => workspaceConfig.Kernel.Builder!.Build());
if (workspaceConfig.Name == DefaultWorkspaceName)
{
context.Services.AddTransient<Kernel>(sp => sp.GetRequiredKeyedService<Kernel>(
AbpAIOptions.GetKernelServiceKeyName(workspaceConfig.Name)
)
);
}
if (workspaceConfig.ChatClient?.Builder is null)
{
context.Services.AddKeyedTransient<IChatClient>(
AbpAIOptions.GetChatClientServiceKeyName(workspaceConfig.Name),
(sp, _) => sp.GetKeyedService<Kernel>(AbpAIOptions.GetKernelServiceKeyName(workspaceConfig.Name))?
.GetRequiredService<IChatClient>()
?? throw new InvalidOperationException("Kernel or IChatClient not found with workspace name: " + workspaceConfig.Name)
);
}
builderConfigurer.Action(workspaceConfig.ChatClient.Builder);
}
context.Services.TryAddTransient(typeof(IKernelAccessor<>), typeof(KernelAccessor<>));
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<IChatClient>(
sp => sp.GetRequiredKeyedService<IChatClient>(serviceName)
);
}
}
}

19
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 //TODO: Rename to AbpAIWorkspaceOptions
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<string> ConfiguredWorkspaceNames { get; } = new();
}

24
framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIWorkspaceOptions.cs

@ -1,8 +1,24 @@
using System.Collections.Generic;
namespace Volo.Abp.AI;
public class AbpAIWorkspaceOptions //TODO: Rename to AbpAIOptions
/// <summary>
/// Pre-configured options for the AI workspaces. Not used via Options pattern. Use it with 'PreConfigure' method in a Module class.
/// In example:
/// <code>PreConfigure&lt;AbpAIWorkspaceOptions&gt;(options => { });</code>
/// </summary>
public class AbpAIWorkspaceOptions
{
public HashSet<string> 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}";
}
}

35
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<IChatClient>(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(
AbpAIModule.DefaultWorkspaceName));
}
}
[Dependency(ReplaceServices = true, TryRegister = true)]
[ExposeServices(typeof(IChatClientAccessor))]
public class ChatClientAccessor<TWorkSpace> : IChatClientAccessor<TWorkSpace>
where TWorkSpace : class
{
public IChatClient? ChatClient { get; }
public ChatClientAccessor(IServiceProvider serviceProvider)
{
ChatClient = serviceProvider.GetKeyedService<IChatClient>(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(
WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>()));
}
}

2
framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelAccessor.cs

@ -12,7 +12,7 @@ public class KernelAccessor<TWorkSpace> : IKernelAccessor<TWorkSpace>
public KernelAccessor(IServiceProvider serviceProvider)
{
Kernel = serviceProvider.GetKeyedService<Kernel>(
AbpAIOptions.GetKernelServiceKeyName(
AbpAIWorkspaceOptions.GetKernelServiceKeyName(
WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>()));
}
}

2
framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs

@ -10,7 +10,7 @@ public class TypedChatClient<TWorkSpace> : DelegatingChatClient, IChatClient<TWo
public TypedChatClient(IServiceProvider serviceProvider)
: base(
serviceProvider.GetRequiredKeyedService<IChatClient>(
AbpAIOptions.GetChatClientServiceKeyName(
AbpAIWorkspaceOptions.GetChatClientServiceKeyName(
WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>()))
)
{

Loading…
Cancel
Save