diff --git a/Directory.Packages.props b/Directory.Packages.props index 60258d5f0..f7205a9ae 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,6 +14,7 @@ + @@ -105,6 +106,7 @@ + @@ -277,6 +279,10 @@ + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/FodyWeavers.xml b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/FodyWeavers.xsd b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN.Abp.AI.Agent.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN.Abp.AI.Agent.csproj new file mode 100644 index 000000000..4fac1853b --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN.Abp.AI.Agent.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0 + LINGYUN.Abp.AI.Agent + LINGYUN.Abp.AI.Agent + false + false + false + enable + + + + + + + + + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AbpAIAgentModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AbpAIAgentModule.cs new file mode 100644 index 000000000..6ae562ede --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AbpAIAgentModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.AI.Agent; + +[DependsOn(typeof(AbpAICoreModule))] +public class AbpAIAgentModule : AbpModule +{ + +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs new file mode 100644 index 000000000..62dd55dca --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs @@ -0,0 +1,80 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Localization; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Volo.Abp.AI; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Agent; +public class ChatClientAgentFactory : IChatClientAgentFactory, ISingletonDependency +{ + private readonly static ConcurrentDictionary _chatClientAgentCache = new(); + + private readonly IChatClientFactory _chatClientFactory; + private readonly IStringLocalizerFactory _stringLocalizerFactory; + private readonly IWorkspaceDefinitionManager _workspaceDefinitionManager; + public ChatClientAgentFactory( + IChatClientFactory chatClientFactory, + IStringLocalizerFactory stringLocalizerFactory, + IWorkspaceDefinitionManager workspaceDefinitionManager) + { + _chatClientFactory = chatClientFactory; + _stringLocalizerFactory = stringLocalizerFactory; + _workspaceDefinitionManager = workspaceDefinitionManager; + } + + public async virtual Task CreateAsync() + { + var workspace = WorkspaceNameAttribute.GetWorkspaceName(); + if (_chatClientAgentCache.TryGetValue(workspace, out var chatClientAgent)) + { + return chatClientAgent; + } + + var chatClient = await _chatClientFactory.CreateAsync(); + + var workspaceDefine = await _workspaceDefinitionManager.GetOrNullAsync(workspace); + + string? description = null; + if (workspaceDefine?.Description != null) + { + description = workspaceDefine.Description.Localize(_stringLocalizerFactory); + } + + chatClientAgent = chatClient.CreateAIAgent( + instructions: workspaceDefine?.SystemPrompt, + name: workspaceDefine?.Name, + description: description); + + _chatClientAgentCache.TryAdd(workspace, chatClientAgent); + + return chatClientAgent; + } + + public async virtual Task CreateAsync(string workspace) + { + if (_chatClientAgentCache.TryGetValue(workspace, out var chatClientAgent)) + { + return chatClientAgent; + } + var workspaceDefine = await _workspaceDefinitionManager.GetAsync(workspace); + var chatClient = await _chatClientFactory.CreateAsync(workspace); + + string? description = null; + if (workspaceDefine.Description != null) + { + description = workspaceDefine.Description.Localize(_stringLocalizerFactory); + } + + chatClientAgent = chatClient.CreateAIAgent( + instructions: workspaceDefine.SystemPrompt, + name: workspaceDefine.Name, + description: description); + + _chatClientAgentCache.TryAdd(workspace, chatClientAgent); + + return chatClientAgent; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs new file mode 100644 index 000000000..0d15511fd --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using Microsoft.Agents.AI; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Agent; +public interface IChatClientAgentFactory +{ + [NotNull] + Task CreateAsync(); + + [NotNull] + Task CreateAsync(string workspace); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/README.md b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/README.md new file mode 100644 index 000000000..5d2730e6c --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/README.md @@ -0,0 +1,98 @@ +# LINGYUN.Abp.AI.Agent + +[Abp AI Module](https://abp.io/docs/latest/framework/infrastructure/artificial-intelligence) 扩展. + +## 功能特性 + + +## 模块引用 + +```csharp +[DependsOn(typeof(AbpAIAgentModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` + +## 依赖模块 + +* [AbpAIModule](https://abp.io/docs/latest/framework/infrastructure/artificial-intelligence) +* [AbpLocalizationModule](https://abp.io/docs/latest/framework/fundamentals/localization) + +## 基本用法 + +> 兼容 `IKernelAccessor` 与 `IChatClientAccessor` 的语法. + +```csharp +using LINGYUN.Abp.AI; +using LINGYUN.Abp.AI.Agent; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Volo.Abp; + +var application = await AbpApplicationFactory.CreateAsync(options => +{ + options.UseAutofac(); +}); + +await application.InitializeAsync(); + +var chatClientAgentFactory = application.ServiceProvider.GetRequiredService(); + +var agent = await chatClientAgentFactory.CreateAsync(); + +var agentResponse = agent.RunStreamingAsync("解释一下线性代数"); + +await foreach (var item in agentResponse) +{ + Console.Write(item); +} +Console.WriteLine(); + +await application.ShutdownAsync(); + +Console.WriteLine(); +Console.WriteLine("AI Console completed!"); + +Console.ReadKey(); +``` + +> 支持动态工作区语法. + +```csharp +using LINGYUN.Abp.AI; +using LINGYUN.Abp.AI.Agent; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Volo.Abp; + +var application = await AbpApplicationFactory.CreateAsync(options => +{ + options.UseAutofac(); +}); + +await application.InitializeAsync(); + +var chatClientAgentFactory = application.ServiceProvider.GetRequiredService(); + +var agent = await chatClientAgentFactory.CreateAsync("YouWorkspace"); + +var agentResponse = agent.RunStreamingAsync("解释一下线性代数"); + +await foreach (var item in agentResponse) +{ + Console.Write(item); +} +Console.WriteLine(); + +await application.ShutdownAsync(); + +Console.WriteLine(); +Console.WriteLine("AI Console completed!"); + +Console.ReadKey(); + +``` \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/FodyWeavers.xml b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/FodyWeavers.xsd b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN.Abp.AI.Core.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN.Abp.AI.Core.csproj new file mode 100644 index 000000000..1c67a9757 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN.Abp.AI.Core.csproj @@ -0,0 +1,22 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0 + LINGYUN.Abp.AI.Core + LINGYUN.Abp.AI.Core + false + false + false + enable + + + + + + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs new file mode 100644 index 000000000..8718805c2 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs @@ -0,0 +1,52 @@ +using LINGYUN.Abp.AI.Localization; +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using Volo.Abp.AI; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.AI; + +[DependsOn( + typeof(AbpAIModule), + typeof(AbpLocalizationModule))] +public class AbpAICoreModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources.Add(); + }); + + Configure(options => + { + options.ChatClientProviders.Add(); + }); + } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistered(context => + { + if (typeof(IWorkspaceDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreOptions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreOptions.cs new file mode 100644 index 000000000..8297e1b58 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreOptions.cs @@ -0,0 +1,22 @@ +using LINGYUN.Abp.AI.Workspaces; +using System.Collections.Generic; +using Volo.Abp.Collections; + +namespace LINGYUN.Abp.AI; +public class AbpAICoreOptions +{ + public ITypeList DefinitionProviders { get; } + public ITypeList ChatClientProviders { get; } + public ITypeList KernelProviders { get; } + + public HashSet DeletedWorkspaces { get; } + + public AbpAICoreOptions() + { + DefinitionProviders = new TypeList(); + ChatClientProviders = new TypeList(); + KernelProviders = new TypeList(); + + DeletedWorkspaces = new HashSet(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs new file mode 100644 index 000000000..bbdfa9c90 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs @@ -0,0 +1,82 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.AI; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI; +public class ChatClientFactory : IChatClientFactory, ISingletonDependency +{ + private readonly static ConcurrentDictionary _chatClientCache = new(); + protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } + protected IChatClientProviderManager ChatClientProviderManager { get; } + protected IServiceProvider ServiceProvider { get; } + + public ChatClientFactory( + IWorkspaceDefinitionManager workspaceDefinitionManager, + IChatClientProviderManager chatClientProviderManager, + IServiceProvider serviceProvider) + { + WorkspaceDefinitionManager = workspaceDefinitionManager; + ChatClientProviderManager = chatClientProviderManager; + ServiceProvider = serviceProvider; + } + + public async virtual Task CreateAsync() + { + var workspace = WorkspaceNameAttribute.GetWorkspaceName(); + if (_chatClientCache.TryGetValue(workspace, out var chatClient)) + { + return chatClient; + } + + var chatClientAccessorType = typeof(IChatClientAccessor<>).MakeGenericType(typeof(TWorkspace)); + var chatClientAccessor = ServiceProvider.GetService(chatClientAccessorType); + if (chatClientAccessor != null && + chatClientAccessor is IChatClientAccessor accessor && + accessor.ChatClient != null) + { + chatClient = accessor.ChatClient; + _chatClientCache.TryAdd(workspace, chatClient); + } + else + { + chatClient = await CreateAsync(workspace); + } + return chatClient; + } + + public async virtual Task CreateAsync(string workspace) + { + if (_chatClientCache.TryGetValue(workspace, out var chatClient)) + { + return chatClient; + } + + var workspaceDefine = await WorkspaceDefinitionManager.GetAsync(workspace); + + chatClient = await CreateChatClientAsync(workspaceDefine); + + _chatClientCache.TryAdd(workspace, chatClient); + + return chatClient; + } + + protected async virtual Task CreateChatClientAsync(WorkspaceDefinition workspace) + { + foreach (var provider in ChatClientProviderManager.Providers) + { + if (!string.Equals(provider.Name, workspace.Provider)) + { + continue; + } + + return await provider.CreateAsync(workspace); + } + + throw new AbpException($"The ChatClient provider implementation named {workspace.Provider} was not found"); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProviderManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProviderManager.cs new file mode 100644 index 000000000..8d3cc80ec --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProviderManager.cs @@ -0,0 +1,45 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI; +public class ChatClientProviderManager : IChatClientProviderManager, ISingletonDependency +{ + public List Providers => _lazyProviders.Value; + + protected AbpAICoreOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + private readonly Lazy> _lazyProviders; + + public ChatClientProviderManager( + IServiceProvider serviceProvider, + IOptions options) + { + + Options = options.Value; + ServiceProvider = serviceProvider; + + _lazyProviders = new Lazy>(GetProviders, true); + } + + protected virtual List GetProviders() + { + var providers = Options + .ChatClientProviders + .Select(type => (ServiceProvider.GetRequiredService(type) as IChatClientProvider)!) + .ToList(); + + var multipleProviders = providers.GroupBy(p => p.Name).FirstOrDefault(x => x.Count() > 1); + if (multipleProviders != null) + { + throw new AbpException($"Duplicate ChatClient provider name detected: {multipleProviders.Key}. Providers:{Environment.NewLine}{multipleProviders.Select(p => p.GetType().FullName!).JoinAsString(Environment.NewLine)}"); + } + + return providers; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs new file mode 100644 index 000000000..3ab6e99bb --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.AI; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI; +public interface IChatClientFactory +{ + [NotNull] + Task CreateAsync(); + + [NotNull] + Task CreateAsync(string workspace); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs new file mode 100644 index 000000000..bdf5aeeb7 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs @@ -0,0 +1,11 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI; +public interface IChatClientProvider +{ + string Name { get; } + + Task CreateAsync(WorkspaceDefinition workspace); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProviderManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProviderManager.cs new file mode 100644 index 000000000..f3f6536ca --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProviderManager.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI; +public interface IChatClientProviderManager +{ + List Providers { get; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelFactory.cs new file mode 100644 index 000000000..c25d272bf --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelFactory.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using Microsoft.SemanticKernel; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI; +public interface IKernelFactory +{ + [NotNull] + Task CreateAsync(); + + [NotNull] + Task CreateAsync(string workspace); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelProvider.cs new file mode 100644 index 000000000..56b34a4ca --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelProvider.cs @@ -0,0 +1,11 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.SemanticKernel; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI; +public interface IKernelProvider +{ + string Name { get; } + + Task CreateAsync(WorkspaceDefinition workspace); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelProviderManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelProviderManager.cs new file mode 100644 index 000000000..a726f7073 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IKernelProviderManager.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI; +public interface IKernelProviderManager +{ + List Providers { get; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs new file mode 100644 index 000000000..5709d9127 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs @@ -0,0 +1,82 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.SemanticKernel; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.AI; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI; +public class KernelFactory : IKernelFactory, ISingletonDependency +{ + private readonly static ConcurrentDictionary _kernelCache = new(); + protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } + protected IKernelProviderManager KernelProviderManager { get; } + protected IServiceProvider ServiceProvider { get; } + + public KernelFactory( + IWorkspaceDefinitionManager workspaceDefinitionManager, + IKernelProviderManager kernelProviderManager, + IServiceProvider serviceProvider) + { + WorkspaceDefinitionManager = workspaceDefinitionManager; + KernelProviderManager = kernelProviderManager; + ServiceProvider = serviceProvider; + } + + public async virtual Task CreateAsync() + { + var workspace = WorkspaceNameAttribute.GetWorkspaceName(); + if (_kernelCache.TryGetValue(workspace, out var kernel)) + { + return kernel; + } + + var kernelAccessorType = typeof(IKernelAccessor<>).MakeGenericType(typeof(TWorkspace)); + var kernelAccessor = ServiceProvider.GetService(kernelAccessorType); + if (kernelAccessor != null && + kernelAccessor is IKernelAccessor accessor + && accessor.Kernel != null) + { + kernel = accessor.Kernel; + _kernelCache.TryAdd(workspace, kernel); + } + else + { + kernel = await CreateAsync(workspace); + } + return kernel; + } + + public async virtual Task CreateAsync(string workspace) + { + if (_kernelCache.TryGetValue(workspace, out var kernel)) + { + return kernel; + } + + var workspaceDefine = await WorkspaceDefinitionManager.GetAsync(workspace); + + kernel = await CreateKernelAsync(workspaceDefine); + + _kernelCache.TryAdd(workspace, kernel); + + return kernel; + } + + protected async virtual Task CreateKernelAsync(WorkspaceDefinition workspace) + { + foreach (var provider in KernelProviderManager.Providers) + { + if (!string.Equals(provider.Name, workspace.Provider)) + { + continue; + } + + return await provider.CreateAsync(workspace); + } + + throw new AbpException($"The Kernel provider implementation named {workspace.Provider} was not found"); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProviderManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProviderManager.cs new file mode 100644 index 000000000..f2beb0025 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProviderManager.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI; +public class KernelProviderManager : IKernelProviderManager, ISingletonDependency +{ + public List Providers => _lazyProviders.Value; + + protected AbpAICoreOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + private readonly Lazy> _lazyProviders; + + public KernelProviderManager( + IServiceProvider serviceProvider, + IOptions options) + { + + Options = options.Value; + ServiceProvider = serviceProvider; + + _lazyProviders = new Lazy>(GetProviders, true); + } + + protected virtual List GetProviders() + { + var providers = Options + .ChatClientProviders + .Select(type => (ServiceProvider.GetRequiredService(type) as IKernelProvider)!) + .ToList(); + + var multipleProviders = providers.GroupBy(p => p.Name).FirstOrDefault(x => x.Count() > 1); + if (multipleProviders != null) + { + throw new AbpException($"Duplicate Kernel provider name detected: {multipleProviders.Key}. Providers:{Environment.NewLine}{multipleProviders.Select(p => p.GetType().FullName!).JoinAsString(Environment.NewLine)}"); + } + + return providers; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Localization/AbpAIResource.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Localization/AbpAIResource.cs new file mode 100644 index 000000000..9566818d4 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Localization/AbpAIResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.AI.Localization; + +[LocalizationResourceName("AbpAI")] +public class AbpAIResource +{ +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs new file mode 100644 index 000000000..f8d145334 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs @@ -0,0 +1,36 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using OpenAI; +using System; +using System.ClientModel; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI; +public class OpenAIChatClientProvider : IChatClientProvider, ITransientDependency +{ + private const string DefaultEndpoint = "https://api.openai.com/v1"; + public const string ProviderName = "OpenAI"; + + public virtual string Name => ProviderName; + + public virtual Task CreateAsync(WorkspaceDefinition workspace) + { + Check.NotNull(workspace, nameof(workspace)); + Check.NotNullOrWhiteSpace(workspace.ApiKey, nameof(WorkspaceDefinition.ApiKey)); + + var openAIClient = new OpenAIClient( + new ApiKeyCredential(workspace.ApiKey), + new OpenAIClientOptions + { + Endpoint = new Uri(workspace.ApiBaseUrl ?? DefaultEndpoint), + }); + + var chatClient = openAIClient + .GetChatClient(workspace.ModelName) + .AsIChatClient(); + + return Task.FromResult(chatClient); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IDynamicWorkspaceDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IDynamicWorkspaceDefinitionStore.cs new file mode 100644 index 000000000..d3b9aa5e9 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IDynamicWorkspaceDefinitionStore.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Workspaces; +public interface IDynamicWorkspaceDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync([NotNull] string name); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IStaticWorkspaceDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IStaticWorkspaceDefinitionStore.cs new file mode 100644 index 000000000..542ae0e6f --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IStaticWorkspaceDefinitionStore.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Workspaces; +public interface IStaticWorkspaceDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync([NotNull] string name); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionContext.cs new file mode 100644 index 000000000..4ab567639 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionContext.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI.Workspaces; +public interface IWorkspaceDefinitionContext +{ + WorkspaceDefinition? GetOrNull(string name); + + IReadOnlyList GetAll(); + + void Add(params WorkspaceDefinition[] definitions); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionManager.cs new file mode 100644 index 000000000..82bd1c224 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionManager.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Workspaces; +public interface IWorkspaceDefinitionManager +{ + [ItemNotNull] + Task GetAsync([NotNull] string name); + + [ItemNotNull] + Task> GetAllAsync(); + + [ItemCanBeNull] + Task GetOrNullAsync([NotNull] string name); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionProvider.cs new file mode 100644 index 000000000..69452746a --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/IWorkspaceDefinitionProvider.cs @@ -0,0 +1,5 @@ +namespace LINGYUN.Abp.AI.Workspaces; +public interface IWorkspaceDefinitionProvider +{ + void Define(IWorkspaceDefinitionContext context); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/NullDynamicWorkspaceDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/NullDynamicWorkspaceDefinitionStore.cs new file mode 100644 index 000000000..dd6953ba7 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/NullDynamicWorkspaceDefinitionStore.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Workspaces; +public class NullDynamicWorkspaceDefinitionStore : IDynamicWorkspaceDefinitionStore, ISingletonDependency +{ + private readonly static Task CachedNullableWorkspaceResult = Task.FromResult((WorkspaceDefinition?)null); + private readonly static Task CachedWorkspaceResult = Task.FromResult((WorkspaceDefinition)null!); + + private readonly static Task> CachedWorkspacesResult = Task.FromResult( + (IReadOnlyList)Array.Empty().ToImmutableList()); + + public Task GetAsync(string name) + { + return CachedWorkspaceResult; + } + + public Task> GetAllAsync() + { + return CachedWorkspacesResult; + } + + public Task GetOrNullAsync(string name) + { + return CachedNullableWorkspaceResult; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/StaticWorkspaceDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/StaticWorkspaceDefinitionStore.cs new file mode 100644 index 000000000..2d711257d --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/StaticWorkspaceDefinitionStore.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.StaticDefinitions; + +namespace LINGYUN.Abp.AI.Workspaces; +public class StaticWorkspaceDefinitionStore : IStaticWorkspaceDefinitionStore, ISingletonDependency +{ + protected IServiceProvider ServiceProvider { get; } + protected AbpAICoreOptions Options { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } + + public StaticWorkspaceDefinitionStore( + IServiceProvider serviceProvider, + IOptions options, + IStaticDefinitionCache> definitionCache) + { + ServiceProvider = serviceProvider; + Options = options.Value; + DefinitionCache = definitionCache; + } + + public virtual async Task GetAsync(string name) + { + Check.NotNull(name, nameof(name)); + + var workspace = await GetOrNullAsync(name); + + if (workspace == null) + { + throw new AbpException("Undefined workspace: " + name); + } + + return workspace; + } + + public virtual async Task> GetAllAsync() + { + var defs = await GetWorkspaceDefinitionsAsync(); + return defs.Values.ToImmutableList(); + } + + public virtual async Task GetOrNullAsync(string name) + { + var defs = await GetWorkspaceDefinitionsAsync(); + return defs.GetOrDefault(name); + } + + protected virtual async Task> GetWorkspaceDefinitionsAsync() + { + return await DefinitionCache.GetOrCreateAsync(CreateWorkspaceDefinitionsAsync); + } + + protected virtual Task> CreateWorkspaceDefinitionsAsync() + { + var workspaces = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as IWorkspaceDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider?.Define(new WorkspaceDefinitionContext(workspaces)); + } + } + + return Task.FromResult(workspaces); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs new file mode 100644 index 000000000..56f37e127 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs @@ -0,0 +1,104 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.Localization; +using Volo.Abp.SimpleStateChecking; + +namespace LINGYUN.Abp.AI.Workspaces; +/// +/// 工作区定义 +/// +public class WorkspaceDefinition : IHasSimpleStateCheckers +{ + /// + /// 名称 + /// + [NotNull] + public string Name { get; } + /// + /// AI提供者名称 + /// + [NotNull] + public string Provider { get; } + /// + /// 模型名称 + /// + [NotNull] + public string ModelName { get; } + /// + /// 显示名称 + /// + [NotNull] + public ILocalizableString DisplayName { + get => _displayName; + set => _displayName = Check.NotNull(value, nameof(value)); + } + private ILocalizableString _displayName = default!; + /// + /// 描述 + /// + public ILocalizableString? Description { get; set; } + /// + /// API 身份验证密钥 + /// + public string? ApiKey { get; set; } + /// + /// 自定义端点 URL + /// + public string? ApiBaseUrl { get; set; } + /// + /// 系统提示词 + /// + public string? SystemPrompt { get; set; } + /// + /// 启用/禁用工作区 + /// + public bool IsEnabled { get; set; } + + [NotNull] + public Dictionary Properties { get; } + + public List> StateCheckers { get; } + + public WorkspaceDefinition( + string name, + string provider, + string modelName, + ILocalizableString displayName, + ILocalizableString? description = null) + { + Name = name; + Provider = provider; + ModelName = modelName; + _displayName = displayName; + _displayName = displayName; + Description = description; + + IsEnabled = true; + Properties = new Dictionary(); + StateCheckers = new List>(); + } + + public virtual WorkspaceDefinition WithApiBaseUrl(string apiBaseUrl) + { + ApiBaseUrl = apiBaseUrl; + return this; + } + + public virtual WorkspaceDefinition WithApiKey(string apiKey) + { + ApiKey = apiKey; + return this; + } + + public virtual WorkspaceDefinition WithProperty(string key, object value) + { + Properties[key] = value; + return this; + } + + public override string ToString() + { + return $"[{nameof(WorkspaceDefinition)} {Name}]"; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionContext.cs new file mode 100644 index 000000000..73919b9c5 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionContext.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace LINGYUN.Abp.AI.Workspaces; +public class WorkspaceDefinitionContext : IWorkspaceDefinitionContext +{ + protected Dictionary Workspaces { get; } + + public WorkspaceDefinitionContext(Dictionary workspaces) + { + Workspaces = workspaces; + } + + public virtual WorkspaceDefinition? GetOrNull(string name) + { + return Workspaces.GetOrDefault(name); + } + + public virtual IReadOnlyList GetAll() + { + return Workspaces.Values.ToImmutableList(); + } + + public virtual void Add(params WorkspaceDefinition[] definitions) + { + if (definitions.IsNullOrEmpty()) + { + return; + } + + foreach (var definition in definitions) + { + Workspaces[definition.Name] = definition; + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionManager.cs new file mode 100644 index 000000000..8b5b8048d --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionManager.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Workspaces; +public class WorkspaceDefinitionManager : IWorkspaceDefinitionManager, ISingletonDependency +{ + protected readonly IStaticWorkspaceDefinitionStore StaticStore; + protected readonly IDynamicWorkspaceDefinitionStore DynamicStore; + + public WorkspaceDefinitionManager( + IStaticWorkspaceDefinitionStore staticStore, + IDynamicWorkspaceDefinitionStore dynamicStore) + { + StaticStore = staticStore; + DynamicStore = dynamicStore; + } + + public virtual async Task GetAsync(string name) + { + var workspace = await GetOrNullAsync(name); + if (workspace == null) + { + throw new AbpException("Undefined Workspace: " + name); + } + + return workspace; + } + + public virtual async Task GetOrNullAsync(string name) + { + Check.NotNull(name, nameof(name)); + + return await StaticStore.GetOrNullAsync(name) ?? await DynamicStore.GetOrNullAsync(name); + } + + public virtual async Task> GetAllAsync() + { + var staticWorkspaces = await StaticStore.GetAllAsync(); + var staticWorkspaceNames = staticWorkspaces + .Select(p => p.Name) + .ToImmutableHashSet(); + + var dynamicWorkspaces = await DynamicStore.GetAllAsync(); + + return staticWorkspaces.Concat(dynamicWorkspaces.Where(d => !staticWorkspaceNames.Contains(d.Name))) + .ToImmutableList(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionProvider.cs new file mode 100644 index 000000000..5f9e662d0 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinitionProvider.cs @@ -0,0 +1,8 @@ +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Workspaces; + +public abstract class WorkspaceDefinitionProvider : IWorkspaceDefinitionProvider, ITransientDependency +{ + public abstract void Define(IWorkspaceDefinitionContext context); +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/README.md b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/README.md new file mode 100644 index 000000000..4a4fa8227 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/README.md @@ -0,0 +1,164 @@ +# LINGYUN.Abp.AI.Core + +[Abp AI Module](https://abp.io/docs/latest/framework/infrastructure/artificial-intelligence) 扩展. + +## 功能特性 + + +## 模块引用 + +```csharp +[DependsOn(typeof(AbpAICoreModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` + +## 依赖模块 + +* [AbpAIModule](https://abp.io/docs/latest/framework/infrastructure/artificial-intelligence) +* [AbpLocalizationModule](https://abp.io/docs/latest/framework/fundamentals/localization) + +## 基本用法 + +> 定义系统工作区 +```csharp +[DependsOn(typeof(AbpAICoreModule))] +public class YouProjectModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.Workspaces.Configure(workspace => + { + workspace.ConfigureChatClient(config => + { + config.Builder = new ChatClientBuilder( + sp => new OpenAIClient( + new ApiKeyCredential("YouApiKey"), + new OpenAIClientOptions + { + Endpoint = new Uri("https://api.openai.com/v1"), + }).GetChatClient("GPT-4").AsIChatClient()); + }); + + workspace.ConfigureKernel(config => + { + config.Builder = Kernel.CreateBuilder() + .AddOpenAIChatClient( + modelId: "GPT-4", + openAIClient: new OpenAIClient( + new ApiKeyCredential("YouApiKey"), + new OpenAIClientOptions + { + Endpoint = new Uri("https://api.openai.com/v1"), + })); + }); + }); + }); + } +} +``` + +> 兼容 `IKernelAccessor` 与 `IChatClientAccessor` 的语法. +```csharp +using LINGYUN.Abp.AI; +using LINGYUN.Abp.AI.Agent; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Volo.Abp; + +var application = await AbpApplicationFactory.CreateAsync(options => +{ + options.UseAutofac(); +}); + +await application.InitializeAsync(); + +// 使用 IKernelFactory +Console.WriteLine("Use Microsoft.SemanticKernel:"); +var kernelFactory = application.ServiceProvider.GetRequiredService(); + +var kernel = await kernelFactory.CreateAsync(); +var kernelResponse = kernel.InvokePromptStreamingAsync("如何优化 C# 代码性能?"); +await foreach (var item in kernelResponse) +{ + Console.Write(item); +} +Console.WriteLine(); + +// 使用 IKernelAccessor +var kernelWithAbp = application.ServiceProvider.GetRequiredService>(); +var kernelWithAbpResponse = kernelWithAbp.InvokePromptStreamingAsync("如何优化 C# 代码性能?"); +await foreach (var item in kernelWithAbpResponse) +{ + Console.Write(item); +} +Console.WriteLine(); + +await application.ShutdownAsync(); + +Console.WriteLine(); +Console.WriteLine("AI Console completed!"); + +Console.ReadKey(); +``` + +> 定义动态工作区 +```csharp +public class YouWorkspaceDefinitionProvider : WorkspaceDefinitionProvider +{ + public override void Define(IWorkspaceDefinitionContext context) + { + context.Add( + new WorkspaceDefinition( + "YouWorkspace", + OpenAIChatClientProvider.ProviderName, + "GPT-4", + new FixedLocalizableString("YouWorkspace")) + .WithApiKey("YouApiKey") + .WithApiBaseUrl("https://api.openai.com/v1")); + } +} + +``` + +> 支持动态工作区语法. +```csharp +using LINGYUN.Abp.AI; +using LINGYUN.Abp.AI.Agent; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Volo.Abp; + +var application = await AbpApplicationFactory.CreateAsync(options => +{ + options.UseAutofac(); +}); + +await application.InitializeAsync(); + +// Microsoft.SemanticKernel +Console.WriteLine("Use Microsoft.SemanticKernel:"); +var kernelFactory = application.ServiceProvider.GetRequiredService(); + +var kernel = await kernelFactory.CreateAsync("YouWorkspace"); +var kernelResponse = kernel.InvokePromptStreamingAsync("如何优化 C# 代码性能?"); +await foreach (var item in kernelResponse) +{ + Console.Write(item); +} +Console.WriteLine(); + +await application.ShutdownAsync(); + +Console.WriteLine(); +Console.WriteLine("AI Console completed!"); + +Console.ReadKey(); + +``` \ No newline at end of file