Browse Source

Merge pull request #23533 from abpframework/ai

AI Workspaces
pull/23785/head
Halil İbrahim Kalkan 5 months ago
committed by GitHub
parent
commit
f712c521b0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      Directory.Packages.props
  2. 307
      docs/en/framework/infrastructure/artificial-intelligence.md
  3. 1
      docs/en/framework/infrastructure/index.md
  4. 1970
      framework/Volo.Abp.sln
  5. 1
      framework/Volo.Abp.sln.DotSettings
  6. 3
      framework/src/Volo.Abp.AI.Abstractions/FodyWeavers.xml
  7. 30
      framework/src/Volo.Abp.AI.Abstractions/FodyWeavers.xsd
  8. 3
      framework/src/Volo.Abp.AI.Abstractions/Volo.Abp.AI.Abstractions.abppkg
  9. 26
      framework/src/Volo.Abp.AI.Abstractions/Volo.Abp.AI.Abstractions.csproj
  10. 10
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/AbpAIAbstractionsModule.cs
  11. 8
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClient.cs
  12. 14
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IKernelAccessor.cs
  13. 38
      framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/WorkspaceNameAttribute.cs
  14. 3
      framework/src/Volo.Abp.AI/FodyWeavers.xml
  15. 30
      framework/src/Volo.Abp.AI/FodyWeavers.xsd
  16. 3
      framework/src/Volo.Abp.AI/Volo.Abp.AI.abppkg
  17. 27
      framework/src/Volo.Abp.AI/Volo.Abp.AI.csproj
  18. 96
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIModule.cs
  19. 19
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIOptions.cs
  20. 8
      framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIWorkspaceOptions.cs
  21. 8
      framework/src/Volo.Abp.AI/Volo/Abp/AI/BuilderConfigurerList.cs
  22. 23
      framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientConfiguration.cs
  23. 18
      framework/src/Volo.Abp.AI/Volo/Abp/AI/DefaultKernelAccessor.cs
  24. 9
      framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelBuilderConfigurerList.cs
  25. 21
      framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelConfiguration.cs
  26. 18
      framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedChatClient.cs
  27. 20
      framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedKernelAccessor.cs
  28. 28
      framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfiguration.cs
  29. 29
      framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfigurationDictionary.cs
  30. 14
      framework/src/Volo.Abp.Core/Volo/Abp/Collections/NamedActionList.cs
  31. 9
      framework/src/Volo.Abp.Core/Volo/Abp/Collections/NamedObjectList.cs
  32. 14
      framework/src/Volo.Abp.Core/Volo/Abp/NamedAction.cs
  33. 11
      framework/src/Volo.Abp.Core/Volo/Abp/NamedObject.cs
  34. 2
      nupkg/common.ps1

6
Directory.Packages.props

@ -86,7 +86,11 @@
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.7.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.7.1" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.61.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Abstractions" Version="1.61.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="9.4.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.5" />

307
docs/en/framework/infrastructure/artificial-intelligence.md

@ -0,0 +1,307 @@
# Artificial Intelligence
ABP provides a simple way to integrate AI capabilities into your applications by unifying two popular .NET AI stacks under a common concept called a "workspace":
- Microsoft.Extensions.AI `IChatClient`
- Microsoft.SemanticKernel `Kernel`
A workspace is just a named scope. You configure providers per workspace and then resolve either default services (for the "Default" workspace) or workspace-scoped services.
## Installation
> This package is not included by default. Install it to enable AI features.
It is suggested to use the ABP CLI to install the package. Open a command line window in the folder of the project (.csproj file) and type the following command:
```bash
abp add-package Volo.Abp.AI
```
### Manual Installation
Add nuget package to your project:
```bash
dotnet add package Volo.Abp.AI
```
Then add the module dependency to your module class:
```csharp
using Volo.Abp.AI;
using Volo.Abp.Modularity;
[DependsOn(typeof(AbpAIModule))]
public class MyProjectModule : AbpModule
{
}
```
## Usage
### Chat Client
#### Default configuration (quick start)
Configure the default workspace to inject `IChatClient` directly.
```csharp
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;
using Volo.Abp.AI;
using Volo.Abp.Modularity;
public class MyProjectModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpAIOptions>(options =>
{
options.Workspaces.ConfigureDefault(configuration =>
{
configuration.ConfigureChatClient(chatClientConfiguration =>
{
chatClientConfiguration.Builder = new ChatClientBuilder(
sp => new OllamaApiClient("http://localhost:11434", "mistral")
);
});
// Chat client only in this quick start
});
});
}
}
```
Once configured, inject the default chat client:
```csharp
using Microsoft.Extensions.AI;
public class MyService
{
private readonly IChatClient _chatClient; // default chat client
public MyService(IChatClient chatClient)
{
_chatClient = chatClient;
}
}
```
#### Workspace configuration
Workspaces allow multiple, isolated AI configurations. Define workspace types (optionally decorated with `WorkspaceNameAttribute`). If omitted, the type’s full name is used.
```csharp
using Volo.Abp.AI;
[WorkspaceName("GreetingAssistant")]
public class GreetingAssistant // ChatClient-only workspace
{
}
```
Configure a ChatClient workspace:
```csharp
public class MyProjectModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpAIOptions>(options =>
{
options.Workspaces.Configure<GreetingAssistant>(configuration =>
{
configuration.ConfigureChatClient(chatClientConfiguration =>
{
chatClientConfiguration.Builder = new ChatClientBuilder(
sp => new OllamaApiClient("http://localhost:11434", "mistral")
);
chatClientConfiguration.BuilderConfigurers.Add(builder =>
{
// Anything you want to do with the builder:
// builder.UseFunctionInvocation().UseLogging(); // For example
});
});
});
});
}
}
```
### Semantic Kernel
#### Default configuration
```csharp
public class MyProjectModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpAIOptions>(options =>
{
options.Workspaces.ConfigureDefault(configuration =>
{
configuration.ConfigureKernel(kernelConfiguration =>
{
kernelConfiguration.Builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatClient("...", "...");
});
// Note: Chat client is not configured here
});
});
}
}
```
Once configured, inject the default kernel:
```csharp
using System.Threading.Tasks;
using Volo.Abp.AI;
public class MyService
{
private readonly IKernelAccessor _kernelAccessor;
public MyService(IKernelAccessor kernelAccessor)
{
_kernelAccessor = kernelAccessor;
}
public async Task DoSomethingAsync()
{
var kernel = _kernelAccessor.Kernel; // Kernel might be null if no workspace is configured.
var result = await kernel.InvokeAsync(/*... */);
}
}
```
#### Workspace configuration
```csharp
public class MyProjectModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpAIOptions>(options =>
{
options.Workspaces.Configure<ContentPlanner>(configuration =>
{
configuration.ConfigureKernel(kernelConfiguration =>
{
kernelConfiguration.Builder = Kernel.CreateBuilder()
.AddOpenAIChatCompletion("...", "...");
});
});
});
}
}
```
#### Workspace usage
```csharp
using Microsoft.Extensions.AI;
using Volo.Abp.AI;
using Microsoft.SemanticKernel;
public class PlanningService
{
private readonly IKernelAccessor<ContentPlanner> _kernelAccessor;
private readonly IChatClient<ContentPlanner> _chatClient; // available even if only Kernel is configured
public PlanningService(
IKernelAccessor<ContentPlanner> kernelAccessor,
IChatClient<ContentPlanner> chatClient)
{
_kernelAccessor = kernelAccessor;
_chatClient = chatClient;
}
public async Task<string> PlanAsync(string topic)
{
var kernel = _kernelAccessor.Kernel; // Microsoft.SemanticKernel.Kernel
// Use Semantic Kernel APIs if needed...
var response = await _chatClient.GetResponseAsync(
[new ChatMessage(ChatRole.User, $"Create a content plan for: {topic}")]
);
return response?.Message?.Text ?? string.Empty;
}
}
```
## Options
`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.
`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).
## Advanced Usage and Customizations
### Addding Your Own DelegatingChatClient
If you want to build your own decorator, implement a `DelegatingChatClient` derivative and provide an extension method that adds it to the `ChatClientBuilder` using `builder.Use(...)`.
Example sketch:
```csharp
using Microsoft.Extensions.AI;
public class SystemMessageChatClient : DelegatingChatClient
{
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)
{
// Mutate messages/options as needed, then call base
return base.GetResponseAsync(messages, options, cancellationToken);
}
}
public static class SystemMessageChatClientExtensions
{
public static ChatClientBuilder UseSystemMessage(this ChatClientBuilder builder, string systemMessage)
{
return builder.Use(client => new SystemMessageChatClient(client, systemMessage));
}
}
```
```cs
chatClientConfiguration.BuilderConfigurers.Add(builder =>
{
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.
- `AbpAIOptions`: Holds `Workspaces` and provides helper methods for internal keyed service naming.
- `WorkspaceConfigurationDictionary` and `WorkspaceConfiguration`: Configure per-workspace Chat Client and Kernel.
- `ChatClientConfiguration` and `KernelConfiguration`: Hold builders and a list of ordered builder configurers.
- `WorkspaceNameAttribute`: Names a workspace; falls back to the type’s full name if not specified.
- `IChatClient<TWorkspace>`: Typed chat client for a workspace.
- `IKernelAccessor<TWorkspace>`: Provides access to the workspace’s `Kernel` instance if configured.
- `AbpAIWorkspaceOptions`: Exposes `ConfiguredWorkspaceNames` for diagnostics.
There are no database tables for this feature; it is a pure configuration and DI integration layer.
## See Also
- Microsoft.Extensions.AI (Chat Client)
- Microsoft Semantic Kernel

1
docs/en/framework/infrastructure/index.md

@ -3,6 +3,7 @@
ABP provides a complete infrastructure for creating real world software solutions with modern architectures based on the .NET platform. Each of the following documents explains an infrastructure feature:
* [Audit Logging](./audit-logging.md)
* [Artificial Intelligence](./artificial-intelligence.md)
* [Background Jobs](./background-jobs/index.md)
* [Background Workers](./background-workers/index.md)
* [BLOB Storing](./blob-storing/index.md)

1970
framework/Volo.Abp.sln

File diff suppressed because it is too large

1
framework/Volo.Abp.sln.DotSettings

@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=E193A13E6CDABC4687BFBCAD92DEDC16/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=E193A13E6CDABC4687BFBCAD92DEDC16/AbsolutePath/@EntryValue">D:\Github\abp\common.DotSettings</s:String>

3
framework/src/Volo.Abp.AI.Abstractions/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.AI.Abstractions/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.AI.Abstractions/Volo.Abp.AI.Abstractions.abppkg

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

26
framework/src/Volo.Abp.AI.Abstractions/Volo.Abp.AI.Abstractions.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<PackageId>Volo.Abp.AI.Abstractions</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel.Abstractions" />
</ItemGroup>
</Project>

10
framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/AbpAIAbstractionsModule.cs

@ -0,0 +1,10 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Modularity;
namespace Volo.Abp.AI;
public class AbpAIAbstractionsModule : AbpModule
{
}

8
framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/IChatClient.cs

@ -0,0 +1,8 @@
using Microsoft.Extensions.AI;
namespace Volo.Abp.AI;
public interface IChatClient<TWorkSpace> : IChatClient
where TWorkSpace : class
{
}

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

@ -0,0 +1,14 @@
using Microsoft.SemanticKernel;
namespace Volo.Abp.AI;
public interface IKernelAccessor
{
Kernel? Kernel { get; }
}
public interface IKernelAccessor<TWorkSpace> : IKernelAccessor
where TWorkSpace : class
{
}

38
framework/src/Volo.Abp.AI.Abstractions/Volo/Abp/AI/WorkspaceNameAttribute.cs

@ -0,0 +1,38 @@
using System;
using System.Linq;
using System.Collections.Concurrent;
namespace Volo.Abp.AI;
[AttributeUsage(AttributeTargets.Class)]
public class WorkspaceNameAttribute : Attribute
{
public string Name { get; }
public WorkspaceNameAttribute(string name)
{
Check.NotNull(name, nameof(name));
Name = name;
}
private static readonly ConcurrentDictionary<Type, string> _nameCache = new();
public static string GetWorkspaceName<TWorkspace>()
{
return GetWorkspaceName(typeof(TWorkspace));
}
public static string GetWorkspaceName(Type workspaceType)
{
return _nameCache.GetOrAdd(workspaceType, type =>
{
var workspaceNameAttribute = type
.GetCustomAttributes(true)
.OfType<WorkspaceNameAttribute>()
.FirstOrDefault();
return workspaceNameAttribute?.Name ?? type.FullName!;
});
}
}

3
framework/src/Volo.Abp.AI/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.AI/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.AI/Volo.Abp.AI.abppkg

@ -0,0 +1,3 @@
{
"role": "lib.framework"
}

27
framework/src/Volo.Abp.AI/Volo.Abp.AI.csproj

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<PackageId>Volo.Abp.AI</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AI.Abstractions\Volo.Abp.AI.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" />
</ItemGroup>
</Project>

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

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
[DependsOn(
typeof(AbpAIAbstractionsModule)
)]
public class AbpAIModule : AbpModule
{
public const string DefaultWorkspaceName = "Default";
public override void PostConfigureServices(ServiceConfigurationContext context)
{
var options = context.Services.ExecutePreConfiguredActions<AbpAIOptions>();
context.Services.Configure<AbpAIWorkspaceOptions>(workspaceOptions =>
{
workspaceOptions.ConfiguredWorkspaceNames.UnionWith(options.Workspaces.Select(x => x.Key).ToArray());
});
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
);
if (workspaceConfig.Name == DefaultWorkspaceName)
{
context.Services.AddTransient<IChatClient>(sp => sp.GetRequiredKeyedService<IChatClient>(
AbpAIOptions.GetChatClientServiceKeyName(workspaceConfig.Name)
)
);
}
}
context.Services.TryAddTransient(typeof(IChatClient<>), typeof(TypedChatClient<>));
foreach (var workspaceConfig in options.Workspaces.Values)
{
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)
);
}
}
context.Services.TryAddTransient(typeof(IKernelAccessor<>), typeof(TypedKernelAccessor<>));
}
}

19
framework/src/Volo.Abp.AI/Volo/Abp/AI/AbpAIOptions.cs

@ -0,0 +1,19 @@
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}";
}
}

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

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Volo.Abp.AI;
public class AbpAIWorkspaceOptions
{
public HashSet<string> ConfiguredWorkspaceNames { get; } = new();
}

8
framework/src/Volo.Abp.AI/Volo/Abp/AI/BuilderConfigurerList.cs

@ -0,0 +1,8 @@
using Microsoft.Extensions.AI;
namespace Volo.Abp.AI;
public class BuilderConfigurerList : NamedActionList<ChatClientBuilder>
{
}

23
framework/src/Volo.Abp.AI/Volo/Abp/AI/ChatClientConfiguration.cs

@ -0,0 +1,23 @@
using System;
using Microsoft.Extensions.AI;
namespace Volo.Abp.AI;
public class ChatClientConfiguration
{
public ChatClientBuilder? Builder { get; set; }
public BuilderConfigurerList BuilderConfigurers { get; } = new();
// TODO: Base chat client (for inheriting a chat client configuration from some other one)
public void ConfigureBuilder(Action<ChatClientBuilder> configureAction)
{
BuilderConfigurers.Add(configureAction);
}
public void ConfigureBuilder(string name, Action<ChatClientBuilder> configureAction)
{
BuilderConfigurers.Add(name, configureAction);
}
}

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

9
framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelBuilderConfigurerList.cs

@ -0,0 +1,9 @@
using Microsoft.SemanticKernel;
namespace Volo.Abp.AI;
public class KernelBuilderConfigurerList : NamedActionList<IKernelBuilder>
{
}

21
framework/src/Volo.Abp.AI/Volo/Abp/AI/KernelConfiguration.cs

@ -0,0 +1,21 @@
using System;
using Microsoft.SemanticKernel;
namespace Volo.Abp.AI;
public class KernelConfiguration
{
public IKernelBuilder? Builder { get; set; }
public KernelBuilderConfigurerList BuilderConfigurers { get; } = new();
public void ConfigureBuilder(Action<IKernelBuilder> configureAction)
{
BuilderConfigurers.Add(configureAction);
}
public void ConfigureBuilder(string name, Action<IKernelBuilder> configureAction)
{
BuilderConfigurers.Add(name, configureAction);
}
}

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

@ -0,0 +1,18 @@
using System;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.AI;
public class TypedChatClient<TWorkSpace> : DelegatingChatClient, IChatClient<TWorkSpace>
where TWorkSpace : class
{
public TypedChatClient(IServiceProvider serviceProvider)
: base(
serviceProvider.GetRequiredKeyedService<IChatClient>(
AbpAIOptions.GetChatClientServiceKeyName(
WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>()))
)
{
}
}

20
framework/src/Volo.Abp.AI/Volo/Abp/AI/TypedKernelAccessor.cs

@ -0,0 +1,20 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
namespace Volo.Abp.AI;
public class TypedKernelAccessor<TWorkSpace> : IKernelAccessor<TWorkSpace>
where TWorkSpace : class
{
public Kernel? Kernel { get; }
public TypedKernelAccessor(IServiceProvider serviceProvider)
{
Kernel = serviceProvider.GetKeyedService<Kernel>(
AbpAIOptions.GetKernelServiceKeyName(
WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>()));
}
}

28
framework/src/Volo.Abp.AI/Volo/Abp/AI/WorkspaceConfiguration.cs

@ -0,0 +1,28 @@
using System;
namespace Volo.Abp.AI;
public class WorkspaceConfiguration
{
public string Name { get; }
public ChatClientConfiguration ChatClient { get; } = new();
public KernelConfiguration Kernel { get; } = new();
public WorkspaceConfiguration(string name)
{
Name = name;
}
public WorkspaceConfiguration ConfigureChatClient(Action<ChatClientConfiguration> configureAction)
{
configureAction(ChatClient);
return this;
}
public WorkspaceConfiguration ConfigureKernel(Action<KernelConfiguration> configureAction)
{
configureAction(Kernel);
return this;
}
}

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

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.AI;
public class WorkspaceConfigurationDictionary : Dictionary<string, WorkspaceConfiguration>
{
public void Configure<TWorkSpace>(Action<WorkspaceConfiguration>? configureAction = null)
where TWorkSpace : class
{
Configure(WorkspaceNameAttribute.GetWorkspaceName<TWorkSpace>(), configureAction);
}
public void Configure(string name, Action<WorkspaceConfiguration>? configureAction = null)
{
if (!TryGetValue(name, out var configuration))
{
configuration = new WorkspaceConfiguration(name);
this[name] = configuration;
}
configureAction?.Invoke(configuration);
}
public void ConfigureDefault(Action<WorkspaceConfiguration>? configureAction = null)
{
Configure(AbpAIModule.DefaultWorkspaceName, configureAction);
}
}

14
framework/src/Volo.Abp.Core/Volo/Abp/Collections/NamedActionList.cs

@ -0,0 +1,14 @@
using System;
namespace Volo.Abp.AI;
public class NamedActionList<T> : NamedObjectList<NamedAction<T>>
{
public void Add(Action<T> action)
{
this.Add(Guid.NewGuid().ToString("N"), action);
}
public void Add(string name, Action<T> action)
=> this.Add(new NamedAction<T>(name, action));
}

9
framework/src/Volo.Abp.Core/Volo/Abp/Collections/NamedObjectList.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Volo.Abp.AI;
public class NamedObjectList<T> : List<T>
where T : NamedObject
{
}

14
framework/src/Volo.Abp.Core/Volo/Abp/NamedAction.cs

@ -0,0 +1,14 @@
using System;
namespace Volo.Abp;
public class NamedAction<T> : NamedObject
{
public Action<T> Action { get; set; }
public NamedAction(string name, Action<T> action)
: base(name)
{
Action = Check.NotNull(action, nameof(action));
}
}

11
framework/src/Volo.Abp.Core/Volo/Abp/NamedObject.cs

@ -0,0 +1,11 @@
namespace Volo.Abp;
public class NamedObject
{
public string Name { get; }
public NamedObject(string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
}
}

2
nupkg/common.ps1

@ -93,6 +93,8 @@ $solutions = (
$projects = (
# framework
"framework/src/Volo.Abp.AI.Abstractions",
"framework/src/Volo.Abp.AI",
"framework/src/Volo.Abp.ApiVersioning.Abstractions",
"framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer",
"framework/src/Volo.Abp.AspNetCore.Authentication.OAuth",

Loading…
Cancel
Save