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