@ -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 |
|||
@ -0,0 +1,127 @@ |
|||
# Setting Up Android Emulator Without Android Studio (Windows, macOS, Linux) |
|||
|
|||
This guide explains how to install and run an Android emulator **without Android Studio**, using only **Command Line Tools**. |
|||
|
|||
--- |
|||
|
|||
## 1. Download Required Tools |
|||
|
|||
Go to: [https://developer.android.com/studio#command-tools](https://developer.android.com/studio#command-tools) |
|||
Download the "Command line tools only" package for your OS: |
|||
|
|||
- **Windows:** `commandlinetools-win-*.zip` |
|||
- **macOS:** `commandlinetools-mac-*.zip` |
|||
- **Linux:** `commandlinetools-linux-*.zip` |
|||
|
|||
--- |
|||
|
|||
## 2. Create the Required Directory Structure |
|||
|
|||
### Windows: |
|||
``` |
|||
C:\Android\ |
|||
└── cmdline-tools\ |
|||
└── latest\ |
|||
└── [extract all files from the zip here] |
|||
``` |
|||
|
|||
### macOS / Linux: |
|||
``` |
|||
~/Android/ |
|||
└── cmdline-tools/ |
|||
└── latest/ |
|||
└── [extract all files from the zip here] |
|||
``` |
|||
|
|||
> You need to create the `latest` folder manually. |
|||
|
|||
--- |
|||
|
|||
## 3. Set Environment Variables |
|||
|
|||
### Windows (temporary for CMD session): |
|||
```cmd |
|||
set PATH=C:\Android\cmdline-tools\latest\bin;C:\Android\platform-tools;C:\Android\emulator;%PATH% |
|||
``` |
|||
|
|||
### macOS / Linux: |
|||
Add the following to your `.bashrc`, `.zshrc`, or `.bash_profile` file: |
|||
|
|||
```bash |
|||
export ANDROID_HOME=$HOME/Android |
|||
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin |
|||
export PATH=$PATH:$ANDROID_HOME/platform-tools |
|||
export PATH=$PATH:$ANDROID_HOME/emulator |
|||
``` |
|||
|
|||
> Apply the changes: |
|||
```bash |
|||
source ~/.zshrc # or ~/.bashrc if you're using bash |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 4. Install SDK Components |
|||
|
|||
Install platform tools, emulator, and a system image: |
|||
|
|||
```bash |
|||
sdkmanager --sdk_root=$ANDROID_HOME "platform-tools" "platforms;android-34" "system-images;android-34;google_apis;x86_64" "emulator" |
|||
``` |
|||
|
|||
> On Windows, replace `$ANDROID_HOME` with `--sdk_root=C:\Android`. |
|||
|
|||
--- |
|||
|
|||
## 5. Create an AVD (Android Virtual Device) |
|||
|
|||
### List available devices: |
|||
```bash |
|||
avdmanager list devices |
|||
``` |
|||
|
|||
### Create your AVD: |
|||
```bash |
|||
avdmanager create avd -n myEmu -k "system-images;android-34;google_apis;x86_64" --device "pixel" |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 6. Start the Emulator |
|||
|
|||
```bash |
|||
emulator -avd myEmu |
|||
``` |
|||
|
|||
The emulator window should open |
|||
|
|||
--- |
|||
|
|||
## Extra Tools and Commands |
|||
|
|||
### List connected devices with ADB: |
|||
```bash |
|||
adb devices |
|||
``` |
|||
|
|||
### Install an APK: |
|||
```bash |
|||
adb install myApp.apk |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## Troubleshooting |
|||
|
|||
| Problem | Explanation | |
|||
|--------|-------------| |
|||
| `sdkmanager not found` | Make sure `PATH` includes the `latest/bin` directory | |
|||
| `x86_64 system image not found` | Make sure you've downloaded it using `sdkmanager` | |
|||
| `emulator not found` | Add the `emulator` directory to `PATH` | |
|||
| `setx` truncates path (Windows) | Use GUI to update environment variables manually | |
|||
|
|||
--- |
|||
|
|||
## Summary |
|||
|
|||
You can now run an Android emulator without installing Android Studio, entirely through the command line. This emulator can be used for React Native or any mobile development framework. |
|||
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 5.4 MiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 948 KiB After Width: | Height: | Size: 4.9 MiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 682 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 664 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 669 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 98 KiB |
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -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> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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> |
|||
@ -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 |
|||
{ |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Microsoft.Extensions.AI; |
|||
|
|||
namespace Volo.Abp.AI; |
|||
|
|||
public interface IChatClient<TWorkSpace> : IChatClient |
|||
where TWorkSpace : class |
|||
{ |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
@ -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!; |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -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> |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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> |
|||
@ -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<>)); |
|||
} |
|||
} |
|||
@ -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}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.AI; |
|||
|
|||
public class AbpAIWorkspaceOptions |
|||
{ |
|||
public HashSet<string> ConfiguredWorkspaceNames { get; } = new(); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Microsoft.Extensions.AI; |
|||
|
|||
namespace Volo.Abp.AI; |
|||
|
|||
public class BuilderConfigurerList : NamedActionList<ChatClientBuilder> |
|||
{ |
|||
|
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Microsoft.SemanticKernel; |
|||
|
|||
namespace Volo.Abp.AI; |
|||
|
|||
public class KernelBuilderConfigurerList : NamedActionList<IKernelBuilder> |
|||
{ |
|||
} |
|||
|
|||
|
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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>())) |
|||
) |
|||
{ |
|||
} |
|||
} |
|||
@ -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>())); |
|||
} |
|||
} |
|||
|
|||
|
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.AI; |
|||
|
|||
public class NamedObjectList<T> : List<T> |
|||
where T : NamedObject |
|||
{ |
|||
|
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.Abp; |
|||
|
|||
public class NamedObject |
|||
{ |
|||
public string Name { get; } |
|||
|
|||
public NamedObject(string name) |
|||
{ |
|||
Name = Check.NotNullOrWhiteSpace(name, nameof(name)); |
|||
} |
|||
} |
|||