diff --git a/Directory.Packages.props b/Directory.Packages.props index 9964f7847..df0b5e2c1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -348,6 +348,8 @@ + + diff --git a/aspnet-core/LINGYUN.MicroService.Aspire.slnx b/aspnet-core/LINGYUN.MicroService.Aspire.slnx index ff2345acb..5a0522e3b 100644 --- a/aspnet-core/LINGYUN.MicroService.Aspire.slnx +++ b/aspnet-core/LINGYUN.MicroService.Aspire.slnx @@ -284,6 +284,7 @@ + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs index e18ca18d8..6d3dc38f6 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs @@ -1,4 +1,6 @@ -using LINGYUN.Abp.AIManagement; +using LINGYUN.Abp.AI.Ollama; +using LINGYUN.Abp.AI.Tools; +using LINGYUN.Abp.AIManagement; using LINGYUN.Abp.AspNetCore.HttpOverrides; using LINGYUN.Abp.AspNetCore.Mvc.Localization; using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; @@ -46,6 +48,8 @@ namespace LINGYUN.Abp.MicroService.AIService; typeof(AbpIdentitySessionAspNetCoreModule), + typeof(AbpAIOllamaModule), + typeof(AbpAIToolsModule), typeof(AbpAIManagementApplicationModule), typeof(AbpAIManagementHttpApiModule), typeof(AIServiceMigrationsEntityFrameworkCoreModule), diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/LINGYUN.Abp.MicroService.AIService.csproj b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/LINGYUN.Abp.MicroService.AIService.csproj index 828674405..994165395 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/LINGYUN.Abp.MicroService.AIService.csproj +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/LINGYUN.Abp.MicroService.AIService.csproj @@ -56,6 +56,8 @@ + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AbpAIAgentOptions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AbpAIAgentOptions.cs new file mode 100644 index 000000000..78986a1dd --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AbpAIAgentOptions.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Agents.AI; +using System; +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI.Agent; +public class AbpAIAgentOptions +{ + public List> AgentBuildActions { get; } + public List> AgentOptionActions { get; } + public AbpAIAgentOptions() + { + AgentBuildActions = new List>(); + AgentOptionActions = new List>(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentFactory.cs index 0a89027e3..110f5695f 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentFactory.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentFactory.cs @@ -2,6 +2,7 @@ using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; using System; using System.Threading.Tasks; using Volo.Abp.AI; @@ -12,6 +13,7 @@ namespace LINGYUN.Abp.AI.Agent; public class AgentFactory : IAgentFactory, IScopedDependency { protected IServiceProvider ServiceProvider { get; } + protected AbpAIAgentOptions AgentOptions { get; } protected IChatClientFactory ChatClientFactory { get; } protected IStringLocalizerFactory StringLocalizerFactory { get; } protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } @@ -19,10 +21,12 @@ public class AgentFactory : IAgentFactory, IScopedDependency public AgentFactory( IServiceProvider serviceProvider, IChatClientFactory chatClientFactory, + IOptions agentOptions, IStringLocalizerFactory stringLocalizerFactory, IWorkspaceDefinitionManager workspaceDefinitionManager, ISimpleStateCheckerManager stateCheckerManager) { + AgentOptions = agentOptions.Value; ServiceProvider = serviceProvider; ChatClientFactory = chatClientFactory; StringLocalizerFactory = stringLocalizerFactory; @@ -75,8 +79,21 @@ public class AgentFactory : IAgentFactory, IScopedDependency Description = description, }; - var aiAgent = chatClient.CreateAIAgent(clientAgentOptions) - .AsBuilder() + foreach (var handlerAction in AgentOptions.AgentOptionActions) + { + handlerAction(workspace, clientAgentOptions); + } + + var aiAgentBuilder = chatClient + .CreateAIAgent(clientAgentOptions) + .AsBuilder(); + + foreach (var handlerAction in AgentOptions.AgentBuildActions) + { + handlerAction(workspace, aiAgentBuilder); + } + + var aiAgent = aiAgentBuilder .UseLogging() .UseOpenTelemetry() .Build(ServiceProvider); 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 index 8297e1b58..93b597a09 100644 --- 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 @@ -1,5 +1,9 @@ using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using Microsoft.SemanticKernel; +using System; using System.Collections.Generic; +using System.Threading.Tasks; using Volo.Abp.Collections; namespace LINGYUN.Abp.AI; @@ -9,6 +13,9 @@ public class AbpAICoreOptions public ITypeList ChatClientProviders { get; } public ITypeList KernelProviders { get; } + public List> KernelBuildActions { get; } + public List> ChatClientBuildActions { get; } + public HashSet DeletedWorkspaces { get; } public AbpAICoreOptions() @@ -18,5 +25,7 @@ public class AbpAICoreOptions KernelProviders = new TypeList(); DeletedWorkspaces = new HashSet(); + KernelBuildActions = new List>(); + ChatClientBuildActions = new List>(); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIChatClientProvider.cs index 7e3bd4047..3d413b26f 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIChatClientProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIChatClientProvider.cs @@ -1,6 +1,8 @@ using LINGYUN.Abp.AI.Models; using LINGYUN.Abp.AI.Workspaces; using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using OpenAI; using System; using System.ClientModel; @@ -31,7 +33,7 @@ public class OpenAIChatClientProvider : ChatClientProvider ]; } - public override Task CreateAsync(WorkspaceDefinition workspace) + public override async Task CreateAsync(WorkspaceDefinition workspace) { Check.NotNull(workspace, nameof(workspace)); Check.NotNullOrWhiteSpace(workspace.ApiKey, nameof(WorkspaceDefinition.ApiKey)); @@ -43,16 +45,23 @@ public class OpenAIChatClientProvider : ChatClientProvider Endpoint = new Uri(workspace.ApiBaseUrl ?? DefaultEndpoint), }); - var chatClient = openAIClient + var options = ServiceProvider.GetRequiredService>().Value; + + var chatClientBuilder = openAIClient .GetChatClient(workspace.ModelName) .AsIChatClient() - .AsBuilder() + .AsBuilder(); + + foreach (var handlerAction in options.ChatClientBuildActions) + { + await handlerAction(workspace, ServiceProvider, chatClientBuilder); + } + + return chatClientBuilder .UseLogging() .UseOpenTelemetry() .UseFunctionInvocation() .UseDistributedCache() .Build(ServiceProvider); - - return Task.FromResult(chatClient); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIKernelProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIKernelProvider.cs index 2eb16859e..56cd5245e 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIKernelProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Internal/OpenAIKernelProvider.cs @@ -1,4 +1,6 @@ using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Microsoft.SemanticKernel; using OpenAI; using System; @@ -23,6 +25,8 @@ public class OpenAIKernelProvider : KernelProvider Check.NotNull(workspace, nameof(workspace)); Check.NotNullOrWhiteSpace(workspace.ApiKey, nameof(WorkspaceDefinition.ApiKey)); + var options = ServiceProvider.GetRequiredService>().Value; + var openAIClient = new OpenAIClient( new ApiKeyCredential(workspace.ApiKey), new OpenAIClientOptions @@ -30,10 +34,14 @@ public class OpenAIKernelProvider : KernelProvider Endpoint = new Uri(workspace.ApiBaseUrl ?? DefaultEndpoint), }); - var kernel = Kernel.CreateBuilder() - .AddOpenAIChatClient(workspace.ModelName, openAIClient) - .Build(); + var kernelBuilder = Kernel.CreateBuilder() + .AddOpenAIChatClient(workspace.ModelName, openAIClient); + + foreach (var handlerAction in options.KernelBuildActions) + { + handlerAction(workspace, kernelBuilder); + } - return Task.FromResult(kernel); + return Task.FromResult(kernelBuilder.Build()); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tools/GlobalFunctions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tools/GlobalFunctions.cs deleted file mode 100644 index cc52e1fd7..000000000 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tools/GlobalFunctions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.AI; -using System; - -namespace LINGYUN.Abp.AI.Tools; -public static class GlobalFunctions -{ - public static AITool Now => AIFunctionFactory.Create( - () => DateTime.Now, - nameof(Now), - "Get now time"); -} 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 index 82bd1c224..2c1014072 100644 --- 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 @@ -5,12 +5,12 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.AI.Workspaces; public interface IWorkspaceDefinitionManager { - [ItemNotNull] + [NotNull] Task GetAsync([NotNull] string name); [ItemNotNull] Task> GetAllAsync(); - [ItemCanBeNull] + [CanBeNull] Task GetOrNullAsync([NotNull] string name); } 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 index dd6953ba7..16a9a41c6 100644 --- 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 @@ -5,6 +5,8 @@ using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace LINGYUN.Abp.AI.Workspaces; + +[Dependency(TryRegister = true)] public class NullDynamicWorkspaceDefinitionStore : IDynamicWorkspaceDefinitionStore, ISingletonDependency { private readonly static Task CachedNullableWorkspaceResult = Task.FromResult((WorkspaceDefinition?)null); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/FodyWeavers.xml b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/FodyWeavers.xsd b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/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.Ollama/LINGYUN.Abp.AI.Ollama.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN.Abp.AI.Ollama.csproj new file mode 100644 index 000000000..60d093d0a --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN.Abp.AI.Ollama.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0 + LINGYUN.Abp.AI.Ollama + LINGYUN.Abp.AI.Ollama + $(NoWarn);SKEXP0001,SKEXP0010,SKEXP0070 + false + false + false + enable + + + + + + + + + + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/AbpAIOllamaModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/AbpAIOllamaModule.cs new file mode 100644 index 000000000..7c061c42e --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/AbpAIOllamaModule.cs @@ -0,0 +1,17 @@ +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.AI.Ollama; + +[DependsOn(typeof(AbpAICoreModule))] +public class AbpAIOllamaModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ChatClientProviders.Add(); + + options.KernelProviders.Add(); + }); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/OllamaChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/OllamaChatClientProvider.cs new file mode 100644 index 000000000..7ce79dde8 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/OllamaChatClientProvider.cs @@ -0,0 +1,51 @@ +using LINGYUN.Abp.AI.Models; +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using OllamaSharp; +using System; +using System.Threading.Tasks; +using Volo.Abp; + +namespace LINGYUN.Abp.AI.Ollama; +public class OllamaChatClientProvider : ChatClientProvider +{ + public const string DefaultEndpoint = "http://localhost:11434"; + + public const string ProviderName = "Ollama"; + public override string Name => ProviderName; + + public OllamaChatClientProvider( + IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + public async override Task CreateAsync(WorkspaceDefinition workspace) + { + Check.NotNull(workspace, nameof(workspace)); + + var options = ServiceProvider.GetRequiredService>().Value; + + var ollamaApiClient = new OllamaApiClient(workspace.ApiBaseUrl ?? DefaultEndpoint); + + var chatClientBuilder = ChatClientBuilderChatClientExtensions.AsBuilder(ollamaApiClient); + + foreach (var handlerAction in options.ChatClientBuildActions) + { + await handlerAction(workspace, ServiceProvider, chatClientBuilder); + } + + return chatClientBuilder + .UseLogging() + .UseOpenTelemetry() + .UseFunctionInvocation() + .UseDistributedCache() + .Build(ServiceProvider); + } + + public override ChatModel[] GetModels() + { + return []; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/OllamaKernelProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/OllamaKernelProvider.cs new file mode 100644 index 000000000..3cef63e1f --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Ollama/LINGYUN/Abp/AI/Ollama/OllamaKernelProvider.cs @@ -0,0 +1,39 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.SemanticKernel; +using OllamaSharp; +using System; +using System.Threading.Tasks; +using Volo.Abp; + +namespace LINGYUN.Abp.AI.Ollama; +public class OllamaKernelProvider : KernelProvider +{ + public const string ProviderName = "Ollama"; + public override string Name => ProviderName; + + public OllamaKernelProvider(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + public override Task CreateAsync(WorkspaceDefinition workspace) + { + Check.NotNull(workspace, nameof(workspace)); + + var options = ServiceProvider.GetRequiredService>().Value; + + var ollamaApiClient = new OllamaApiClient(workspace.ApiBaseUrl ?? OllamaChatClientProvider.DefaultEndpoint); + + var kernelBuilder = Kernel.CreateBuilder() + .AddOllamaChatClient(ollamaApiClient); + + foreach (var handlerAction in options.KernelBuildActions) + { + handlerAction(workspace, kernelBuilder); + } + + return Task.FromResult(kernelBuilder.Build()); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/FodyWeavers.xml b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/FodyWeavers.xsd b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/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.Tools/LINGYUN.Abp.AI.Tools.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN.Abp.AI.Tools.csproj new file mode 100644 index 000000000..d82d95f49 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN.Abp.AI.Tools.csproj @@ -0,0 +1,26 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0 + LINGYUN.Abp.AI.Tools + LINGYUN.Abp.AI.Tools + false + false + false + enable + + + + + + + + + + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs new file mode 100644 index 000000000..5e4bc9a7e --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs @@ -0,0 +1,55 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using Volo.Abp.Localization; +using Volo.Abp.SimpleStateChecking; + +namespace LINGYUN.Abp.AI.Tools; + +public class AIToolDefinition : IHasSimpleStateCheckers +{ + /// + /// 名称 + /// + [NotNull] + public string Name { get; } + /// + /// 提供者 + /// + [NotNull] + public string Provider { get; } + /// + /// 描述 + /// + public ILocalizableString? Description { get; set; } + /// + /// 属性 + /// + [NotNull] + public Dictionary Properties { get; } + + public List> StateCheckers { get; } + + public AIToolDefinition( + [NotNull] string name, + [NotNull] string provider, + ILocalizableString? description = null) + { + Name = name; + Provider = provider; + Description = description; + + Properties = new Dictionary(); + StateCheckers = new List>(); + } + + public virtual AIToolDefinition WithProperty(string key, object? value) + { + Properties[key] = value; + return this; + } + + public override string ToString() + { + return $"[{nameof(AIToolDefinition)} {Name}]"; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionContext.cs new file mode 100644 index 000000000..319fa6608 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionContext.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace LINGYUN.Abp.AI.Tools; +public class AIToolDefinitionContext : IAIToolDefinitionContext +{ + protected Dictionary Tools { get; } + + public AIToolDefinitionContext(Dictionary tools) + { + Tools = tools; + } + + public virtual AIToolDefinition? GetOrNull(string name) + { + return Tools.GetOrDefault(name); + } + + public virtual IReadOnlyList GetAll() + { + return Tools.Values.ToImmutableList(); + } + + public virtual void Add(params AIToolDefinition[] definitions) + { + if (definitions.IsNullOrEmpty()) + { + return; + } + + foreach (var definition in definitions) + { + Tools[definition.Name] = definition; + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.cs new file mode 100644 index 000000000..2c33bc160 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionManager.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.Tools; +public class AIToolDefinitionManager : IAIToolDefinitionManager, ISingletonDependency +{ + protected readonly IStaticAIToolDefinitionStore StaticStore; + protected readonly IDynamicAIToolDefinitionStore DynamicStore; + + public AIToolDefinitionManager( + IStaticAIToolDefinitionStore staticStore, + IDynamicAIToolDefinitionStore dynamicStore) + { + StaticStore = staticStore; + DynamicStore = dynamicStore; + } + + public virtual async Task GetAsync(string name) + { + var workspace = await GetOrNullAsync(name); + if (workspace == null) + { + throw new AbpException("Undefined AITool: " + 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 staticAITools = await StaticStore.GetAllAsync(); + var staticAIToolNames = staticAITools + .Select(p => p.Name) + .ToImmutableHashSet(); + + var dynamicAITools = await DynamicStore.GetAllAsync(); + + return staticAITools.Concat(dynamicAITools.Where(d => !staticAIToolNames.Contains(d.Name))) + .ToImmutableList(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionProvider.cs new file mode 100644 index 000000000..ab3641d8f --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinitionProvider.cs @@ -0,0 +1,7 @@ +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Tools; +public abstract class AIToolDefinitionProvider : IAIToolDefinitionProvider, ITransientDependency +{ + public abstract void Define(IAIToolDefinitionContext context); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs new file mode 100644 index 000000000..5d9a34fc3 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.AI; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.SimpleStateChecking; + +namespace LINGYUN.Abp.AI.Tools; +public class AIToolFactory : IAIToolFactory, ITransientDependency +{ + protected ISimpleStateCheckerManager StateCheckerManager { get; } + protected IAIToolDefinitionManager AIToolDefinitionManager { get; } + protected IAIToolProviderManager AIToolProviderManager { get; } + + public AIToolFactory( + ISimpleStateCheckerManager stateCheckerManager, + IAIToolDefinitionManager aIToolDefinitionManager, + IAIToolProviderManager aIToolProviderManager) + { + StateCheckerManager = stateCheckerManager; + AIToolDefinitionManager = aIToolDefinitionManager; + AIToolProviderManager = aIToolProviderManager; + } + + public virtual AITool CreateTool(AIToolDefinition definition) + { + foreach (var provider in AIToolProviderManager.Providers) + { + if (!string.Equals(provider.Name, definition.Provider)) + { + continue; + } + + return provider.CreateTool(definition); + } + + throw new AbpException($"The AITool provider implementation named {definition.Provider} was not found!"); + } + + public async virtual Task CreateAllTools() + { + var aiTools = new List(); + var toolDefines = await AIToolDefinitionManager.GetAllAsync(); + + foreach (var toolDefine in toolDefines) + { + if (await StateCheckerManager.IsEnabledAsync(toolDefine)) + { + aiTools.Add(CreateTool(toolDefine)); + } + } + + return aiTools.ToArray(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolProviderManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolProviderManager.cs new file mode 100644 index 000000000..e58c40a14 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolProviderManager.cs @@ -0,0 +1,45 @@ +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.Tools; +public class AIToolProviderManager : IAIToolProviderManager, ISingletonDependency +{ + public List Providers => _lazyProviders.Value; + + protected AbpAIToolsOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + + private readonly Lazy> _lazyProviders; + + public AIToolProviderManager( + IServiceProvider serviceProvider, + IOptions options) + { + + Options = options.Value; + ServiceProvider = serviceProvider; + + _lazyProviders = new Lazy>(GetProviders, true); + } + + protected virtual List GetProviders() + { + var providers = Options + .AIToolProviders + .Select(type => (ServiceProvider.GetRequiredService(type) as IAIToolProvider)!) + .ToList(); + + var multipleProviders = providers.GroupBy(p => p.Name).FirstOrDefault(x => x.Count() > 1); + if (multipleProviders != null) + { + throw new AbpException($"Duplicate AITool 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.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs new file mode 100644 index 000000000..30029e5f5 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs @@ -0,0 +1,80 @@ +using LINGYUN.Abp.AI.Localization; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.AI.Tools; + +[DependsOn(typeof(AbpAICoreModule))] +public class AbpAIToolsModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + // 自定义函数工具 + options.AIToolProviders.Add(); + }); + + Configure(options => + { + options.ChatClientBuildActions.Add(async (_, sp, builder) => + { + var aiToolFactory = sp.GetRequiredService(); + var aiTools = await aiToolFactory.CreateAllTools(); + + builder.ConfigureOptions(ai => + { + ai.ToolMode = ChatToolMode.Auto; + ai.AllowMultipleToolCalls = true; + + ai.Tools ??= []; + + foreach (var aiTool in aiTools) + { + ai.Tools.Add(aiTool); + } + }); + }); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/AI/Tools/Localization/Resources"); + }); + } + + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistered(context => + { + if (typeof(IAIToolDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs new file mode 100644 index 000000000..c2d4d872c --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Collections; + +namespace LINGYUN.Abp.AI.Tools; +public class AbpAIToolsOptions +{ + public ITypeList DefinitionProviders { get; } + public ITypeList AIToolProviders { get; } + public AbpAIToolsOptions() + { + DefinitionProviders = new TypeList(); + AIToolProviders = new TypeList(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs new file mode 100644 index 000000000..56f9e9e98 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Volo.Abp; + +namespace LINGYUN.Abp.AI.Tools; +public static class FunctionAIToolDefinitionExtenssions +{ + private const string FunctionType = "Function"; + public static AIToolDefinition WithFunction(this AIToolDefinition definition) + { + return definition.WithFunction(typeof(TFunc)); + } + + public static AIToolDefinition WithFunction(this AIToolDefinition definition, Type funcType) + { + Check.NotNull(funcType, nameof(funcType)); + + definition.WithProperty(FunctionType, funcType); + + return definition; + } + + public static Type GetFunction(this AIToolDefinition definition) + { + var funcTypeSet = definition.Properties.GetOrDefault(FunctionType); + Check.NotNull(funcTypeSet, nameof(FunctionType)); + + var funcType = Type.GetType(funcTypeSet.ToString()!); + + Check.NotNull(funcType, nameof(funcType)); + + return funcType; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs new file mode 100644 index 000000000..fea88cecb --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using System; +using Volo.Abp; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Tools; +public class FunctionAIToolProvider : IAIToolProvider, ITransientDependency +{ + public const string ProviderName = "Function"; + public string Name => ProviderName; + + protected IServiceProvider ServiceProvider { get; } + public FunctionAIToolProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public virtual AITool CreateTool(AIToolDefinition definition) + { + var aiToolType = definition.GetFunction(); + // 框架约定, 自定义Tool只需要定义同步方法(Invoke)或异步方法(InvokeAsync)即可 + var aiToolMethodInfo = aiToolType.GetMethod("Invoke") ?? aiToolType.GetMethod("InvokeAsync"); + + Check.NotNull(aiToolMethodInfo, nameof(aiToolMethodInfo)); + + string? description = null; + if (definition.Description != null) + { + var localizerFactory = ServiceProvider.GetRequiredService(); + description = definition.Description.Localize(localizerFactory)?.Value; + } + + return AIFunctionFactory.Create( + method: aiToolMethodInfo, + createInstanceFunc: (AIFunctionArguments args) => + { + return ActivatorUtilities.CreateInstance(args.Services ?? ServiceProvider, aiToolType); + }, + options: new AIFunctionFactoryOptions + { + Name = definition.Name, + Description = description, + AdditionalProperties = definition.Properties, + }); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/GlobalAIToolDefinitionProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/GlobalAIToolDefinitionProvider.cs new file mode 100644 index 000000000..03cfbc3ae --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/GlobalAIToolDefinitionProvider.cs @@ -0,0 +1,41 @@ +using LINGYUN.Abp.AI.Localization; +using LINGYUN.Abp.AI.Tools.Settings; +using LINGYUN.Abp.AI.Tools.Timing; +using LINGYUN.Abp.AI.Tools.Users; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.AI.Tools; +public class GlobalAIToolDefinitionProvider : AIToolDefinitionProvider +{ + public override void Define(IAIToolDefinitionContext context) + { + context.Add( + new AIToolDefinition( + NowTimeTool.Name, + FunctionAIToolProvider.ProviderName, + L("Tools:GetNowTime")) + .WithFunction(), + + new AIToolDefinition( + CurrentUserTool.Name, + FunctionAIToolProvider.ProviderName, + L("Tools:GetUserInfo")) + .WithFunction(), + + new AIToolDefinition( + GetSettingTool.Name, + FunctionAIToolProvider.ProviderName, + L("Tools:GetSetting")) + .WithFunction(), + new AIToolDefinition( + GetSettingsTool.Name, + FunctionAIToolProvider.ProviderName, + L("Tools:GetSettings")) + .WithFunction()); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionContext.cs new file mode 100644 index 000000000..20cc54b27 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionContext.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI.Tools; +public interface IAIToolDefinitionContext +{ + AIToolDefinition? GetOrNull(string name); + + IReadOnlyList GetAll(); + + void Add(params AIToolDefinition[] definitions); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionManager.cs new file mode 100644 index 000000000..430763b06 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionManager.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Tools; +public interface IAIToolDefinitionManager +{ + [NotNull] + Task GetAsync([NotNull] string name); + + [ItemNotNull] + Task> GetAllAsync(); + + [CanBeNull] + Task GetOrNullAsync([NotNull] string name); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionProvider.cs new file mode 100644 index 000000000..29cd49430 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolDefinitionProvider.cs @@ -0,0 +1,5 @@ +namespace LINGYUN.Abp.AI.Tools; +public interface IAIToolDefinitionProvider +{ + void Define(IAIToolDefinitionContext context); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolFactory.cs new file mode 100644 index 000000000..929403ab3 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolFactory.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.AI; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Tools; +public interface IAIToolFactory +{ + AITool CreateTool(AIToolDefinition definition); + + Task CreateAllTools(); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProvider.cs new file mode 100644 index 000000000..2d65d95eb --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProvider.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.AI; + +namespace LINGYUN.Abp.AI.Tools; +public interface IAIToolProvider +{ + string Name { get; } + + AITool CreateTool(AIToolDefinition definition); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProviderManager.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProviderManager.cs new file mode 100644 index 000000000..5a565483d --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IAIToolProviderManager.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI.Tools; +public interface IAIToolProviderManager +{ + List Providers { get; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IDynamicAIToolDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IDynamicAIToolDefinitionStore.cs new file mode 100644 index 000000000..b980cc406 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IDynamicAIToolDefinitionStore.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Tools; +public interface IDynamicAIToolDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync([NotNull] string name); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IStaticAIToolDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IStaticAIToolDefinitionStore.cs new file mode 100644 index 000000000..d938ce26f --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IStaticAIToolDefinitionStore.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Tools; +public interface IStaticAIToolDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync([NotNull] string name); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/en.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/en.json new file mode 100644 index 000000000..7a1b2b9f1 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/en.json @@ -0,0 +1,10 @@ +{ + "culture": "en", + "texts": { + "Tools": "Tools", + "Tools:GetNowTime": "Get now time", + "Tools:GetUserInfo": "Get the current user information", + "Tools:GetSetting": "Get setting value", + "Tools:GetSettings": "Get all settings" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/zh-Hans.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..0b5545756 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Localization/Resources/zh-Hans.json @@ -0,0 +1,10 @@ +{ + "culture": "zh-Hans", + "texts": { + "Tools": "工具列表", + "Tools:GetNowTime": "获取当前时间", + "Tools:GetUserInfo": "获取当前用户信息", + "Tools:GetSetting": "获取设置值", + "Tools:GetSettings": "获取所有设置" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/NullDynamicAIToolDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/NullDynamicAIToolDefinitionStore.cs new file mode 100644 index 000000000..b108cf9d6 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/NullDynamicAIToolDefinitionStore.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Tools; + +[Dependency(TryRegister = true)] +public class NullDynamicAIToolDefinitionStore : IDynamicAIToolDefinitionStore, ISingletonDependency +{ + private readonly static Task CachedNullableAIToolResult = Task.FromResult((AIToolDefinition?)null); + private readonly static Task CachedAIToolResult = Task.FromResult((AIToolDefinition)null!); + + private readonly static Task> CachedAIToolsResult = Task.FromResult( + (IReadOnlyList)Array.Empty().ToImmutableList()); + + public Task GetAsync(string name) + { + return CachedAIToolResult; + } + + public Task> GetAllAsync() + { + return CachedAIToolsResult; + } + + public Task GetOrNullAsync(string name) + { + return CachedNullableAIToolResult; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Settings/GetSettingTool.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Settings/GetSettingTool.cs new file mode 100644 index 000000000..b5ee97281 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Settings/GetSettingTool.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.AI.Tools.Settings; +public class GetSettingTool : ITransientDependency +{ + public const string Name = "GetSetting"; + + private readonly ISettingProvider _settingProvider; + public GetSettingTool(ISettingProvider settingProvider) + { + _settingProvider = settingProvider; + } + + public async Task InvokeAsync(string name) + { + return await _settingProvider.GetOrNullAsync(name); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Settings/GetSettingsTool.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Settings/GetSettingsTool.cs new file mode 100644 index 000000000..113acef94 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Settings/GetSettingsTool.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.AI.Tools.Settings; +public class GetSettingsTool : ITransientDependency +{ + public const string Name = "GetSettings"; + + private readonly ISettingProvider _settingProvider; + public GetSettingsTool(ISettingProvider settingProvider) + { + _settingProvider = settingProvider; + } + + public async Task InvokeAsync() + { + return await _settingProvider.GetAllAsync(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/StaticAIToolDefinitionStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/StaticAIToolDefinitionStore.cs new file mode 100644 index 000000000..08793913e --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/StaticAIToolDefinitionStore.cs @@ -0,0 +1,80 @@ +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.Tools; +public class StaticAIToolDefinitionStore : IStaticAIToolDefinitionStore, ISingletonDependency +{ + protected IServiceProvider ServiceProvider { get; } + protected AbpAIToolsOptions Options { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } + + public StaticAIToolDefinitionStore( + 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 AITool: " + name); + } + + return workspace; + } + + public virtual async Task> GetAllAsync() + { + var defs = await GetAIToolDefinitionsAsync(); + return defs.Values.ToImmutableList(); + } + + public virtual async Task GetOrNullAsync(string name) + { + var defs = await GetAIToolDefinitionsAsync(); + return defs.GetOrDefault(name); + } + + protected virtual async Task> GetAIToolDefinitionsAsync() + { + return await DefinitionCache.GetOrCreateAsync(CreateAIToolDefinitionsAsync); + } + + protected virtual Task> CreateAIToolDefinitionsAsync() + { + var workspaces = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as IAIToolDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider?.Define(new AIToolDefinitionContext(workspaces)); + } + } + + return Task.FromResult(workspaces); + } +} + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Timing/NowTimeTool.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Timing/NowTimeTool.cs new file mode 100644 index 000000000..b6d7160fb --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Timing/NowTimeTool.cs @@ -0,0 +1,19 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.Timing; + +namespace LINGYUN.Abp.AI.Tools.Timing; +public class NowTimeTool : ITransientDependency +{ + public const string Name = "GetNowTime"; + + private readonly IClock _clock; + public NowTimeTool(IClock clock) + { + _clock = clock; + } + + public object? Invoke() + { + return _clock.Now; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Users/CurrentUserTool.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Users/CurrentUserTool.cs new file mode 100644 index 000000000..2c83729b2 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/Users/CurrentUserTool.cs @@ -0,0 +1,21 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.Users; + +namespace LINGYUN.Abp.AI.Tools.Users; + +public class CurrentUserTool : ITransientDependency +{ + public const string Name = "GetUserInfo"; + + private readonly ICurrentUser _currentUser; + + public CurrentUserTool(ICurrentUser currentUser) + { + _currentUser = currentUser; + } + + public object? Invoke() + { + return _currentUser; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md new file mode 100644 index 000000000..28f333b4d --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md @@ -0,0 +1,79 @@ +# LINGYUN.Abp.AI.Tools + +AI 工具模块. + +参考: [AI 工具调用](https://learn.microsoft.com/zh-cn/dotnet/ai/conceptual/ai-tools) + +## 功能特性 + + +## 模块引用 + +```csharp +[DependsOn(typeof(AbpAIToolsModule))] +public class YouProjectModule : AbpModule +{ + // other +} +``` + +## 依赖模块 + +* [AbpAICoreModule](../LINGYUN.Abp.AI.Core) + +## 自定义函数 + +> 注意: 自定义函数遵循一个约定: 同步工具需要定义一个名为 `Invoke` 的函数; 异步工具需要定义一个名为 `InvokeAsync` 的函数, 如需使用DI容器, 需要将工具注入到容器 + +```csharp + +public class NowTimeTool : ITransientDependency +{ + private readonly IClock _clock; + public NowTimeTool(IClock clock) + { + _clock = clock; + } + + public object? Invoke() + { + return _clock.Now; + } +} + +public class NowTimeAIToolDefinitionProvider : AIToolDefinitionProvider +{ + context.Add( + new AIToolDefinition( + "GetNowTime", + FunctionAIToolProvider.ProviderName) + .WithFunction()); +} +``` + +## 扩展 + +实现 `IAIToolProvider` 接口, 以增加其他工具调用 +实现 `IDynamicAIToolDefinitionStore` 接口, 以增加动态AI工具管理 + +```csharp +public class McpAIToolProvider : IAIToolProvider, ITransientDependency +{ + public virtual AITool CreateTool(AIToolDefinition definition) + { + // 你的具体实现 + } +} + +[DependsOn(typeof(AbpAIToolsModule))] +public class YouProjectModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AIToolProviders.Add(); + }); + } +} +``` \ No newline at end of file