Browse Source

Refactor system message handling and update docs

Removed ChatClientWithSystemMessage and its extension, consolidating system message logic into user-extensible patterns. Renamed ChatClientNameAttribute to WorkspaceNameAttribute and restricted its usage to classes. Added ConfigureDefault to WorkspaceConfigurationDictionary for easier default workspace setup. Updated documentation to reflect these changes and clarify configuration patterns.
pull/23533/head
enisn 11 months ago
parent
commit
5b20297bb0
No known key found for this signature in database GPG Key ID: A052619F04155D1C
  1. 110
      docs/en/framework/infrastructure/artificial-intelligence.md
  2. 52
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/Delegates/ChatClientWithSystemMessage.cs
  3. 9
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs
  4. 2
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/WorkspaceNameAttribute.cs
  5. 2
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs
  6. 18
      framework/src/Volo.Abp.AI/Volo/Abp/AI/DefaultKernelAccessor.cs
  7. 12
      framework/src/Volo.Abp.AI/Volo/Abp/AI/Extensions/ChatClientWithSystemMessageExtensions.cs
  8. 5
      framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfigurationDictionary.cs

110
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<AbpAIOptions>(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<AbpAIOptions>(options =>
{
options.Workspaces.Configure<ContentPlanner>(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<GreetingAssistant> _chatClient;
private readonly IKernelAccessor _kernelAccessor;
public MyService(IKernelAccessor kernelAccessor)
{
_kernelAccessor = kernelAccessor;
}
public GreetingService(IChatClient<GreetingAssistant> 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<string> 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<AbpAIOptions>(options =>
{
options.Workspaces.Configure<ContentPlanner>(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<TWorkspace>(Action<WorkspaceConfiguration>)`
- `Configure(string name, Action<WorkspaceConfiguration>)`
`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<ChatClientBuilder>)`
- `ConfigureBuilder(string name, Action<ChatClientBuilder>)` (named actions executed in order)
- `Kernel`: `KernelConfiguration`
- `Builder`: `IKernelBuilder?`
- `ConfigureBuilder(Action<IKernelBuilder>)`
- `ConfigureBuilder(string name, Action<IKernelBuilder>)`
`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<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> 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.

52
framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/Delegates/ChatClientWithSystemMessage.cs

@ -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<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
{
return base.GetResponseAsync(PrepareMessages(messages), options, cancellationToken);
}
public override IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
{
return base.GetStreamingResponseAsync(PrepareMessages(messages), options, cancellationToken);
}
protected virtual List<ChatMessage> PrepareMessages(IEnumerable<ChatMessage> 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;
}
}

9
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<TWorkSpace>
where TWorkSpace : class
public interface IKernelAccessor
{
Kernel? Kernel { get; }
}
public interface IKernelAccessor<TWorkSpace> : IKernelAccessor
where TWorkSpace : class
{
}

2
framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/ChatClientNameAttribute.cs → 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; }

2
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<>));
}
}

18
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<Kernel>(
AbpAIModule.DefaultWorkspaceName);
}
}

12
framework/src/Volo.Abp.AI/Volo/Abp/AI/Extensions/ChatClientWithSystemMessageExtensions.cs

@ -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));
}
}

5
framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfigurationDictionary.cs

@ -21,4 +21,9 @@ public class WorkspaceConfigurationDictionary : Dictionary<string, WorkspaceConf
configureAction?.Invoke(configuration);
}
public void ConfigureDefault(Action<WorkspaceConfiguration>? configureAction = null)
{
Configure(AbpAIModule.DefaultWorkspaceName, configureAction);
}
}

Loading…
Cancel
Save