diff --git a/docs/en/framework/infrastructure/artificial-intelligence.md b/docs/en/framework/infrastructure/artificial-intelligence.md index 6af5cf589c..652c7c8233 100644 --- a/docs/en/framework/infrastructure/artificial-intelligence.md +++ b/docs/en/framework/infrastructure/artificial-intelligence.md @@ -43,7 +43,7 @@ public class MyProjectModule : AbpModule #### Default configuration (quick start) -Configure the special workspace named `"Default"` to inject `IChatClient` by default. +Configure the default workspace to inject `IChatClient` directly. ```csharp using Microsoft.Extensions.AI; @@ -57,20 +57,13 @@ public class MyProjectModule : AbpModule { context.Services.PreConfigure(options => { - options.Workspaces.Configure(AbpAIModule.DefaultWorkspaceName, configuration => + options.Workspaces.ConfigureDefault(configuration => { configuration.ConfigureChatClient(chatClientConfiguration => { chatClientConfiguration.Builder = new ChatClientBuilder( sp => new OllamaApiClient("http://localhost:11434", "mistral") ); - - chatClientConfiguration.BuilderConfigurers.Add(builder => - { - builder.UseSystemMessage( - "You are a helpful assistant that greets users in a friendly manner with their names." - ); - }); }); // Chat client only in this quick start @@ -80,12 +73,6 @@ public class MyProjectModule : AbpModule } ``` -Notes: - -- Prefer `ConfigureChatClient(...)` / `ConfigureKernel(...)` methods for configuration. -- Set the `Builder` and then use `BuilderConfigurers.Add(...)` to apply incremental changes. -- If a workspace configures only the Kernel, a chat client may still be exposed for that workspace through the Kernel’s service provider (when available). - Once configured, inject the default chat client: ```csharp @@ -113,11 +100,6 @@ using Volo.Abp.AI; public class GreetingAssistant // ChatClient-only workspace { } - -[WorkspaceName("ContentPlanner")] -public class ContentPlanner // Kernel-only workspace -{ -} ``` Configure a ChatClient workspace: @@ -139,9 +121,8 @@ public class MyProjectModule : AbpModule chatClientConfiguration.BuilderConfigurers.Add(builder => { - builder.UseSystemMessage( - "You are a helpful assistant that greets users in a friendly manner with their names." - ); + // Anything you want to do with the builder: + // builder.UseFunctionInvocation().UseLogging(); // For example }); }); }); @@ -154,6 +135,7 @@ public class MyProjectModule : AbpModule #### Default configuration + ```csharp public class MyProjectModule : AbpModule { @@ -161,7 +143,7 @@ public class MyProjectModule : AbpModule { context.Services.PreConfigure(options => { - options.Workspaces.Configure(configuration => + options.Workspaces.ConfigureDefault(configuration => { configuration.ConfigureKernel(kernelConfiguration => { @@ -175,32 +157,52 @@ public class MyProjectModule : AbpModule } ``` -#### Workspace configuration +Once configured, inject the default kernel: ```csharp -using Microsoft.Extensions.AI; +using System.Threading.Tasks; using Volo.Abp.AI; -public class GreetingService +public class MyService { - private readonly IChatClient _chatClient; + private readonly IKernelAccessor _kernelAccessor; + public MyService(IKernelAccessor kernelAccessor) + { + _kernelAccessor = kernelAccessor; + } - public GreetingService(IChatClient chatClient) + public async Task DoSomethingAsync() { - _chatClient = chatClient; + var kernel = _kernelAccessor.Kernel; // Kernel might be null if no workspace is configured. + + var result = await kernel.InvokeAsync(/*... */); } +} +``` + +#### Workspace configuration - public async Task GreetAsync(string name) +```csharp +public class MyProjectModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) { - var response = await _chatClient.GetResponseAsync( - [new ChatMessage(ChatRole.User, $"Greet {name}")] - ); - return response?.Message?.Text ?? string.Empty; + context.Services.PreConfigure(options => + { + options.Workspaces.Configure(configuration => + { + configuration.ConfigureKernel(kernelConfiguration => + { + kernelConfiguration.Builder = Kernel.CreateBuilder() + .AddOpenAIChatCompletion("...", "..."); + }); + }); + }); } } ``` -#### Resolve +#### Workspace usage ```csharp using Microsoft.Extensions.AI; @@ -235,21 +237,12 @@ public class PlanningService ## Options -- `AbpAIOptions.Workspaces`: A `WorkspaceConfigurationDictionary` used to configure workspaces. - - `Configure(Action)` - - `Configure(string name, Action)` +`AbpAIOptions` configuration pattern offers `ConfigureChatClient(...)` and `ConfigureKernel(...)` methods for configuration. These methods are defined in the `WorkspaceConfiguration` class. They are used to configure the `ChatClient` and `Kernel` respectively. -- `WorkspaceConfiguration` per workspace: - - `ChatClient`: `ChatClientConfiguration` - - `Builder`: `ChatClientBuilder?` - - `ConfigureBuilder(Action)` - - `ConfigureBuilder(string name, Action)` (named actions executed in order) - - `Kernel`: `KernelConfiguration` - - `Builder`: `IKernelBuilder?` - - `ConfigureBuilder(Action)` - - `ConfigureBuilder(string name, Action)` +`Builder` is set once and is used to build the `ChatClient` or `Kernel` instance. `BuilderConfigurers` is a list of actions that are applied to the `Builder` instance for incremental changes. These actions are executed in the order they are added. + +If a workspace configures only the Kernel, a chat client may still be exposed for that workspace through the Kernel’s service provider (when available). -- `AbpAIWorkspaceOptions.ConfiguredWorkspaceNames`: Automatically set of workspace names configured during startup. Useful for diagnostics. ## Advanced Usage and Customizations @@ -262,9 +255,14 @@ Example sketch: ```csharp using Microsoft.Extensions.AI; -public class MyPolicyChatClient : DelegatingChatClient +public class SystemMessageChatClient : DelegatingChatClient { - public MyPolicyChatClient(IChatClient inner) : base(inner) { } + public SystemMessageChatClient(IChatClient inner, string systemMessage) : base(inner) + { + SystemMessage = systemMessage; + } + + public string SystemMessage { get; set; } public override Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) { @@ -273,25 +271,23 @@ public class MyPolicyChatClient : DelegatingChatClient } } -public static class MyPolicyChatClientExtensions +public static class SystemMessageChatClientExtensions { - public static ChatClientBuilder UseMyPolicy(this ChatClientBuilder builder) + public static ChatClientBuilder UseSystemMessage(this ChatClientBuilder builder, string systemMessage) { - return builder.Use(client => new MyPolicyChatClient(client)); + return builder.Use(client => new SystemMessageChatClient(client, systemMessage)); } } ``` -It'll have similar usage with `.UseSystemMessage(...)` extension while configuring a chat client (see configuration examples above). ```cs chatClientConfiguration.BuilderConfigurers.Add(builder => { - builder.UseMyPolicy(); + builder.UseSystemMessage("You are a helpful assistant that greets users in a friendly manner with their names."); }); ``` - ## Technical Anatomy - `AbpAIModule`: Wires up configured workspaces, registers keyed services and default services for the `"Default"` workspace. diff --git a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/Delegates/ChatClientWithSystemMessage.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/Delegates/ChatClientWithSystemMessage.cs deleted file mode 100644 index ccd6f1aa68..0000000000 --- a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/Delegates/ChatClientWithSystemMessage.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.AI; - -namespace Volo.Abp.AI.Delegates; - -public class ChatClientWithSystemMessage : DelegatingChatClient -{ - public string SystemMessage { get; private set; } - - public ChatClientWithSystemMessage(IChatClient innerClient, string systemMessage) : base(innerClient) - { - SystemMessage = systemMessage; - } - - public override Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) - { - return base.GetResponseAsync(PrepareMessages(messages), options, cancellationToken); - } - - public override IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) - { - return base.GetStreamingResponseAsync(PrepareMessages(messages), options, cancellationToken); - } - - protected virtual List PrepareMessages(IEnumerable messages) - { - var messagesList = messages.ToList(); - - if(messagesList.Any(x => x.Role == ChatRole.System)) - { - // If there is a system message, skip it. It might be continued conversation. - // No need to add a new one to prevent duplication. - - // If developer provided system message, then it's overridden, still skipping. - - // Logger.LogWarning("System message is not supported in ChatClientWithSystemMessage. Skipping."); - - return messagesList; - } - - if(!SystemMessage.IsNullOrEmpty()) - { - messagesList.Insert(0, new ChatMessage(ChatRole.System, SystemMessage)); - } - - return messagesList; - } -} \ 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 a63efe8186..7f55ad9e6f 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 @@ -1,9 +1,14 @@ + using Microsoft.SemanticKernel; namespace Volo.Abp.AI; -public interface IKernelAccessor - where TWorkSpace : class +public interface IKernelAccessor { Kernel? Kernel { get; } +} + +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/ChatClientNameAttribute.cs b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/WorkspaceNameAttribute.cs similarity index 90% rename from framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/ChatClientNameAttribute.cs rename to framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/WorkspaceNameAttribute.cs index 70e55fdf57..1cf34d0781 100644 --- a/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/ChatClientNameAttribute.cs +++ b/framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/WorkspaceNameAttribute.cs @@ -4,7 +4,7 @@ using System.Collections.Concurrent; namespace Volo.Abp.AI; -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct)] +[AttributeUsage(AttributeTargets.Class)] public class WorkspaceNameAttribute : Attribute { public string Name { get; } 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 aa6f368fec..f13a6e6f02 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs @@ -92,7 +92,5 @@ public class AbpAIModule : AbpModule } context.Services.TryAddTransient(typeof(IKernelAccessor<>), typeof(TypedKernelAccessor<>)); - - } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/DefaultKernelAccessor.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/DefaultKernelAccessor.cs new file mode 100644 index 0000000000..5370a41dd1 --- /dev/null +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/DefaultKernelAccessor.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AI; + +[ExposeServices(typeof(IKernelAccessor))] +public class DefaultKernelAccessor : IKernelAccessor, ITransientDependency +{ + public Kernel? Kernel { get; } + + public DefaultKernelAccessor(IServiceProvider serviceProvider) + { + Kernel = serviceProvider.GetKeyedService( + AbpAIModule.DefaultWorkspaceName); + } +} diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/Extensions/ChatClientWithSystemMessageExtensions.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/Extensions/ChatClientWithSystemMessageExtensions.cs deleted file mode 100644 index 59eadfd2c6..0000000000 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/Extensions/ChatClientWithSystemMessageExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.AI; -using Volo.Abp.AI.Delegates; - -namespace Volo.Abp.AI.Extensions; - -public static class ChatClientWithSystemMessageExtensions -{ - public static ChatClientBuilder UseSystemMessage(this ChatClientBuilder builder, string systemMessage) - { - return builder.Use(client => new ChatClientWithSystemMessage(client, systemMessage)); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfigurationDictionary.cs b/framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfigurationDictionary.cs index 14808f7bc6..6a5c77d7d0 100644 --- a/framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfigurationDictionary.cs +++ b/framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfigurationDictionary.cs @@ -21,4 +21,9 @@ public class WorkspaceConfigurationDictionary : Dictionary? configureAction = null) + { + Configure(AbpAIModule.DefaultWorkspaceName, configureAction); + } }