From 6a72aa0361189108ab93e4266e6c426d67ec8ae9 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 27 Mar 2026 11:12:33 +0800 Subject: [PATCH 1/4] feat(ai): Adjust the IAIToolProvider to create tools as async --- .../LINGYUN/Abp/AI/Tools/AIToolFactory.cs | 8 ++++---- .../Abp/AI/Tools/FunctionAIToolDefinitionExtenssions.cs | 2 +- .../LINGYUN/Abp/AI/Tools/FunctionAIToolProvider.cs | 7 +++++-- ...vider.cs => GlobalFunctionAIToolDefinitionProvider.cs} | 2 +- .../LINGYUN/Abp/AI/Tools/IAIToolFactory.cs | 2 +- .../LINGYUN/Abp/AI/Tools/IAIToolProvider.cs | 3 ++- aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-) rename aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/{GlobalAIToolDefinitionProvider.cs => GlobalFunctionAIToolDefinitionProvider.cs} (94%) 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 index 5d9a34fc3..beb0b1199 100644 --- 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 @@ -6,7 +6,7 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.SimpleStateChecking; namespace LINGYUN.Abp.AI.Tools; -public class AIToolFactory : IAIToolFactory, ITransientDependency +public class AIToolFactory : IAIToolFactory, IScopedDependency { protected ISimpleStateCheckerManager StateCheckerManager { get; } protected IAIToolDefinitionManager AIToolDefinitionManager { get; } @@ -22,7 +22,7 @@ public class AIToolFactory : IAIToolFactory, ITransientDependency AIToolProviderManager = aIToolProviderManager; } - public virtual AITool CreateTool(AIToolDefinition definition) + public virtual Task CreateTool(AIToolDefinition definition) { foreach (var provider in AIToolProviderManager.Providers) { @@ -31,7 +31,7 @@ public class AIToolFactory : IAIToolFactory, ITransientDependency continue; } - return provider.CreateTool(definition); + return provider.CreateToolsAsync(definition); } throw new AbpException($"The AITool provider implementation named {definition.Provider} was not found!"); @@ -46,7 +46,7 @@ public class AIToolFactory : IAIToolFactory, ITransientDependency { if (await StateCheckerManager.IsEnabledAsync(toolDefine)) { - aiTools.Add(CreateTool(toolDefine)); + aiTools.AddRange(await CreateTool(toolDefine)); } } 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 index 56f9e9e98..9379d79b2 100644 --- 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 @@ -15,7 +15,7 @@ public static class FunctionAIToolDefinitionExtenssions { Check.NotNull(funcType, nameof(funcType)); - definition.WithProperty(FunctionType, funcType); + definition.WithProperty(FunctionType, funcType.AssemblyQualifiedName); return definition; } 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 index fea88cecb..482228c22 100644 --- 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 @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using System; +using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.DependencyInjection; @@ -17,7 +18,7 @@ public class FunctionAIToolProvider : IAIToolProvider, ITransientDependency ServiceProvider = serviceProvider; } - public virtual AITool CreateTool(AIToolDefinition definition) + public virtual Task CreateToolsAsync(AIToolDefinition definition) { var aiToolType = definition.GetFunction(); // 框架约定, 自定义Tool只需要定义同步方法(Invoke)或异步方法(InvokeAsync)即可 @@ -32,7 +33,7 @@ public class FunctionAIToolProvider : IAIToolProvider, ITransientDependency description = definition.Description.Localize(localizerFactory)?.Value; } - return AIFunctionFactory.Create( + var functionAITool = AIFunctionFactory.Create( method: aiToolMethodInfo, createInstanceFunc: (AIFunctionArguments args) => { @@ -44,5 +45,7 @@ public class FunctionAIToolProvider : IAIToolProvider, ITransientDependency Description = description, AdditionalProperties = definition.Properties, }); + + return Task.FromResult([functionAITool]); } } 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/GlobalFunctionAIToolDefinitionProvider.cs similarity index 94% rename from aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/GlobalAIToolDefinitionProvider.cs rename to aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/GlobalFunctionAIToolDefinitionProvider.cs index 03cfbc3ae..2a5a3ab99 100644 --- 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/GlobalFunctionAIToolDefinitionProvider.cs @@ -5,7 +5,7 @@ using LINGYUN.Abp.AI.Tools.Users; using Volo.Abp.Localization; namespace LINGYUN.Abp.AI.Tools; -public class GlobalAIToolDefinitionProvider : AIToolDefinitionProvider +public class GlobalFunctionAIToolDefinitionProvider : AIToolDefinitionProvider { public override 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 index 929403ab3..4e5dbc646 100644 --- 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 @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.AI.Tools; public interface IAIToolFactory { - AITool CreateTool(AIToolDefinition definition); + Task 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 index 2d65d95eb..b8027f85f 100644 --- 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 @@ -1,9 +1,10 @@ using Microsoft.Extensions.AI; +using System.Threading.Tasks; namespace LINGYUN.Abp.AI.Tools; public interface IAIToolProvider { string Name { get; } - AITool CreateTool(AIToolDefinition definition); + Task CreateToolsAsync(AIToolDefinition definition); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md index 28f333b4d..323172535 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/README.md @@ -59,7 +59,7 @@ public class NowTimeAIToolDefinitionProvider : AIToolDefinitionProvider ```csharp public class McpAIToolProvider : IAIToolProvider, ITransientDependency { - public virtual AITool CreateTool(AIToolDefinition definition) + public virtual Task CreateToolsAsync(AIToolDefinition definition) { // 你的具体实现 } From 45e52110ce946dd5d8babe61029b7ff8616cfbdd Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 27 Mar 2026 11:17:08 +0800 Subject: [PATCH 2/4] feat(ai): Add the HttpAITool module --- aspnet-core/LINGYUN.MicroService.Aspire.slnx | 2 + .../AIServiceModule.cs | 5 +- .../LINGYUN.Abp.MicroService.AIService.csproj | 2 + .../appsettings.Development.json | 8 + .../LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xml | 3 + .../LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xsd | 30 ++++ .../LINGYUN.Abp.AI.Tools.Http.csproj | 31 ++++ .../Abp/AI/Tools/Http/AbpAIToolsHttpModule.cs | 42 +++++ .../AI/Tools/Http/AbpAIToolsHttpOptiions.cs | 31 ++++ ...pAIToolsApplicationConfigurationOptions.cs | 11 ++ .../GlobalHttpAIToolDefinitionProvider.cs | 36 +++++ .../LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs | 136 ++++++++++++++++ .../Http/HttpAIToolDefinitionExtenssions.cs | 150 ++++++++++++++++++ .../AI/Tools/Http/HttpAIToolInvokeContext.cs | 15 ++ .../Abp/AI/Tools/Http/HttpAIToolProvider.cs | 47 ++++++ .../Tools/Http/Localization/Resources/en.json | 6 + .../Http/Localization/Resources/zh-Hans.json | 6 + .../HttpClientHttpAIToolExtenssions.cs | 48 ++++++ 18 files changed, 608 insertions(+), 1 deletion(-) create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xml create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xsd create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN.Abp.AI.Tools.Http.csproj create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpModule.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpOptiions.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/ApplicationConfigurations/AbpAIToolsApplicationConfigurationOptions.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/GlobalHttpAIToolDefinitionProvider.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolInvokeContext.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs diff --git a/aspnet-core/LINGYUN.MicroService.Aspire.slnx b/aspnet-core/LINGYUN.MicroService.Aspire.slnx index 5a0522e3b..cdfeaa94b 100644 --- a/aspnet-core/LINGYUN.MicroService.Aspire.slnx +++ b/aspnet-core/LINGYUN.MicroService.Aspire.slnx @@ -285,6 +285,8 @@ + + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs index 6d3dc38f6..b190eadcb 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.AI.Ollama; using LINGYUN.Abp.AI.Tools; +using LINGYUN.Abp.AI.Tools.Http; using LINGYUN.Abp.AIManagement; using LINGYUN.Abp.AspNetCore.HttpOverrides; using LINGYUN.Abp.AspNetCore.Mvc.Localization; @@ -23,6 +24,7 @@ using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Http.Client; +using Volo.Abp.Http.Client.IdentityModel.Web; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.Identity; using Volo.Abp.PermissionManagement.OpenIddict; @@ -50,13 +52,14 @@ namespace LINGYUN.Abp.MicroService.AIService; typeof(AbpAIOllamaModule), typeof(AbpAIToolsModule), + typeof(AbpAIToolsHttpModule), typeof(AbpAIManagementApplicationModule), typeof(AbpAIManagementHttpApiModule), typeof(AIServiceMigrationsEntityFrameworkCoreModule), typeof(AbpDataDbMigratorModule), typeof(AbpAspNetCoreAuthenticationJwtBearerModule), typeof(AbpEmailingExceptionHandlingModule), - typeof(AbpHttpClientModule), + typeof(AbpHttpClientIdentityModelWebModule), typeof(AbpSmsPlatformModule), typeof(AbpEmailingPlatformModule), typeof(AbpCachingStackExchangeRedisModule), 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 994165395..5457d9300 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 @@ -31,6 +31,7 @@ + @@ -58,6 +59,7 @@ + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.Development.json b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.Development.json index c9a9d8327..3d9679901 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.Development.json +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/appsettings.Development.json @@ -7,6 +7,14 @@ "Auditing": { "AllEntitiesSelector": true }, + "AITools": { + "Http": { + "ApplicationConfiguration": { + "IsEnabled": true, + "EndPoint": "http://localhost:30010/api/abp/application-configuration" + } + } + }, "DistributedCache": { "HideErrors": true, "KeyPrefix": "LINGYUN.Abp.Application", diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xml b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xsd b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/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.Http/LINGYUN.Abp.AI.Tools.Http.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN.Abp.AI.Tools.Http.csproj new file mode 100644 index 000000000..42e278639 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN.Abp.AI.Tools.Http.csproj @@ -0,0 +1,31 @@ + + + + + + + net8.0;net9.0;net10.0 + LINGYUN.Abp.AI.Tools.Http + LINGYUN.Abp.AI.Tools.Http + false + false + false + enable + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpModule.cs new file mode 100644 index 000000000..7d7f713c9 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpModule.cs @@ -0,0 +1,42 @@ +using LINGYUN.Abp.AI.Localization; +using LINGYUN.Abp.AI.Tools.Http.ApplicationConfigurations; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Http.Client; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.AI.Tools.Http; + +[DependsOn( + typeof(AbpAIToolsModule), + typeof(AbpHttpClientModule), + typeof(AbpAspNetCoreMvcContractsModule))] +public class AbpAIToolsHttpModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(context.Configuration.GetSection("AITools:Http:ApplicationConfiguration")); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + // Http工具 + options.AIToolProviders.Add(); + }); + + Configure(options => + { + options.Resources + .Get() + .AddVirtualJson("/LINGYUN/Abp/AI/Tools/Http/Localization/Resources"); + }); + + context.Services.AddHttpAIToolClient(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpOptiions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpOptiions.cs new file mode 100644 index 000000000..55b447b8c --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/AbpAIToolsHttpOptiions.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Tools.Http; +public class AbpAIToolsHttpOptiions +{ + public List> ClientBuildActions { get; } + + public List> ClientActions { get; } + + public List> ClientHandlerActions { get; } + /// + /// 自定义Http请求响应处理 + /// + /// + /// 参数1: HttpAITool工具名称 + /// 参数2: IServiceProvider + /// 参数3: 请求响应 + /// + public IDictionary>> HttpResponseActions { get; } + public AbpAIToolsHttpOptiions() + { + ClientBuildActions = new List>(); + ClientActions = new List>(); + ClientHandlerActions = new List>(); + HttpResponseActions = new Dictionary>>(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/ApplicationConfigurations/AbpAIToolsApplicationConfigurationOptions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/ApplicationConfigurations/AbpAIToolsApplicationConfigurationOptions.cs new file mode 100644 index 000000000..3a3bda315 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/ApplicationConfigurations/AbpAIToolsApplicationConfigurationOptions.cs @@ -0,0 +1,11 @@ +namespace LINGYUN.Abp.AI.Tools.Http.ApplicationConfigurations; +public class AbpAIToolsApplicationConfigurationOptions +{ + public bool IsEnabled { get; set; } + public string EndPoint { get; set; } + public AbpAIToolsApplicationConfigurationOptions() + { + IsEnabled = false; + EndPoint = "http://localhost:8080/api/abp/application-configuration"; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/GlobalHttpAIToolDefinitionProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/GlobalHttpAIToolDefinitionProvider.cs new file mode 100644 index 000000000..baab188cd --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/GlobalHttpAIToolDefinitionProvider.cs @@ -0,0 +1,36 @@ +using LINGYUN.Abp.AI.Localization; +using LINGYUN.Abp.AI.Tools.Http.ApplicationConfigurations; +using Microsoft.Extensions.Options; +using System.Net.Http; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.AI.Tools.Http; +public class GlobalHttpAIToolDefinitionProvider : AIToolDefinitionProvider +{ + protected AbpAIToolsApplicationConfigurationOptions Options { get; } + public GlobalHttpAIToolDefinitionProvider(IOptions options) + { + Options = options.Value; + } + + public override void Define(IAIToolDefinitionContext context) + { + if (Options.IsEnabled) + { + context.Add( + new AIToolDefinition( + "GetApplicationConfiguration", + HttpAIToolProvider.ProviderName, + L("Tools:GetApplicationConfiguration")) + .WithHttpEndpoint(Options.EndPoint) + .WithHttpMethod(HttpMethod.Get) + .WithHttpHeaders("_AbpDontWrapResult", "true")// 无需包装结果 + .UseHttpCurrentAccessToken()); + } + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs new file mode 100644 index 000000000..c265ef9a9 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs @@ -0,0 +1,136 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Globalization; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Volo.Abp.Http.Client.Authentication; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; +using Volo.Abp.Tracing; + +namespace LINGYUN.Abp.AI.Tools.Http; +public class HttpAITool +{ + protected HttpAIToolInvokeContext Context { get; } + + public HttpAITool(HttpAIToolInvokeContext context) + { + Context = context; + } + + public async virtual Task InvokeAsync() + { + // Abp远程服务适配 + //var remoteService = Context.ToolDefinition.GetRemoteServiceOrNull(); + //var remoteMethod = Context.ToolDefinition.GetRemoteMethodOrNull(); + //if (!remoteService.IsNullOrWhiteSpace() && !remoteMethod.IsNullOrWhiteSpace()) + //{ + // var abpHttpClientOptions = Context.ServiceProvider.GetRequiredService>().Value; + // HttpClientProxyConfig? clientProxyConfig = null; + + // foreach (var httpClientProxyConfig in abpHttpClientOptions.HttpClientProxies.Values) + // { + // if (httpClientProxyConfig.RemoteServiceName.Equals(remoteService)) + // { + // clientProxyConfig = httpClientProxyConfig; + // continue; + // } + // } + + // if (clientProxyConfig != null) + // { + // // var serviceType = clientProxyConfig.Type.GetProperty(nameof(IHttpClientProxy.Service)); + // var serviceMethod = clientProxyConfig.Type.GetMethod(remoteMethod); + // if (serviceMethod != null) + // { + // var httpClientProxyType = typeof(IHttpClientProxy<>).MakeGenericType(clientProxyConfig.Type); + // var httpClientProxy = Context.ServiceProvider.GetRequiredService(httpClientProxyType); + // var service = httpClientProxyType.GetProperty(nameof(IHttpClientProxy.Service))!.GetValue(httpClientProxy); + // // TODO: 暂不支持有参远程服务调用 + // var task = (Task)serviceMethod.Invoke(service, null)!; + // await task; + // // TODO: 必须为Task返回结构 + // return typeof(Task<>).MakeGenericType(serviceMethod.ReturnType.GenericTypeArguments[0]) + // .GetProperty(nameof(Task.Result), BindingFlags.Public | BindingFlags.Instance)! + // .GetValue(task); + // } + // } + //} + + var options = Context.ServiceProvider.GetRequiredService>().Value; + var httpClientFactory = Context.ServiceProvider.GetRequiredService(); + var httpClient = httpClientFactory.GetHttpAIToolClient(); + + var httpRequestMessage = new HttpRequestMessage( + Context.ToolDefinition.GetHttpMethod(), + Context.ToolDefinition.GetHttpEndpoint()); + + var headers = Context.ToolDefinition.GetHttpHeaders(); + foreach (var header in headers) + { + httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + if (Context.ToolDefinition.IsUseHttpCurrentAccessToken()) + { + var accessTokenProvider = Context.ServiceProvider.GetRequiredService(); + + var token = await accessTokenProvider.GetTokenAsync(); + if (!token.IsNullOrWhiteSpace()) + { + httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + } + + AddDefaultHeaders(httpRequestMessage); + + var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage); + + if (options.HttpResponseActions.TryGetValue(Context.ToolDefinition.Name, out var customHandler)) + { + return await customHandler(Context.ServiceProvider, httpResponseMessage); + } + + return await httpResponseMessage.Content.ReadAsStringAsync(); + } + + protected virtual void AddDefaultHeaders(HttpRequestMessage requestMessage) + { + //CorrelationId + var correlationIdProvider = Context.ServiceProvider.GetRequiredService(); + var correlationId = correlationIdProvider.Get(); + if (correlationId != null) + { + var correlationIdOptions = Context.ServiceProvider.GetRequiredService>(); + requestMessage.Headers.Add(correlationIdOptions.Value.HttpHeaderName, correlationId); + } + + //TenantId + var currentTenant = Context.ServiceProvider.GetRequiredService(); + if (currentTenant.Id.HasValue) + { + //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key + requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, currentTenant.Id.Value.ToString()); + } + + //Culture + //TODO: Is that the way we want? Couldn't send the culture (not ui culture) + var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; + if (!currentCulture.IsNullOrEmpty()) + { + requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); + } + + //X-Requested-With + requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); + + //Timezone + var currentTimezoneProvider = Context.ServiceProvider.GetRequiredService(); + if (!currentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + requestMessage.Headers.Add(TimeZoneConsts.DefaultTimeZoneKey, currentTimezoneProvider.TimeZone); + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs new file mode 100644 index 000000000..7e6af037b --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolDefinitionExtenssions.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using System.Net.Http; +using Volo.Abp; + +namespace LINGYUN.Abp.AI.Tools.Http; +public static class HttpAIToolDefinitionExtenssions +{ + private const string RemoteService = "RemoteService"; + private const string RemoteController = "RemoteController"; + private const string RemoteMethod = "RemoteMethod"; + + private const string Endpoint = "HttpEndpoint"; + private const string Method = "HttpMethod"; + private const string Headers = "HttpHeaders"; + + private const string CurrentAccessToken = "UseHttpCurrentAccessToken"; + + public static AIToolDefinition UseHttpCurrentAccessToken(this AIToolDefinition definition, bool useCurrentAccessToken = true) + { + definition.WithProperty(CurrentAccessToken, useCurrentAccessToken); + + return definition; + } + + public static AIToolDefinition WithRemoteService(this AIToolDefinition definition, string service, string controllerName, string uniqueMethodName) + { + Check.NotNullOrWhiteSpace(service, nameof(service)); + Check.NotNullOrWhiteSpace(controllerName, nameof(controllerName)); + Check.NotNullOrWhiteSpace(uniqueMethodName, nameof(uniqueMethodName)); + + definition.WithProperty(RemoteService, service); + definition.WithProperty(RemoteController, controllerName); + definition.WithProperty(RemoteMethod, uniqueMethodName); + + return definition; + } + + public static AIToolDefinition WithHttpEndpoint(this AIToolDefinition definition, string endPoint) + { + Check.NotNullOrWhiteSpace(endPoint, nameof(endPoint)); + + definition.WithProperty(Endpoint, endPoint); + + return definition; + } + + public static AIToolDefinition WithHttpMethod(this AIToolDefinition definition, HttpMethod method) + { + Check.NotNull(method, nameof(method)); + + definition.WithProperty(Method, method.Method); + + return definition; + } + + public static AIToolDefinition WithHttpHeaders(this AIToolDefinition definition, IDictionary headers) + { + Check.NotNullOrEmpty(headers, nameof(headers)); + + var currentHeaders = definition.GetHttpHeaders(); + + currentHeaders.AddIfNotContains(headers); + + definition.WithProperty(Headers, currentHeaders); + + return definition; + } + + public static AIToolDefinition WithHttpHeaders(this AIToolDefinition definition, string key, string value) + { + Check.NotNullOrEmpty(key, nameof(key)); + Check.NotNullOrEmpty(value, nameof(value)); + + var currentHeaders = definition.GetHttpHeaders(); + + currentHeaders.TryAdd(key, value); + + definition.WithProperty(Headers, currentHeaders); + + return definition; + } + + public static bool IsUseHttpCurrentAccessToken(this AIToolDefinition definition) + { + if (definition.Properties.TryGetValue(CurrentAccessToken, out var useCurrentAccessTokenObj) && useCurrentAccessTokenObj != null + && bool.TryParse(useCurrentAccessTokenObj.ToString(), out var useCurrentAccessToken)) + { + return useCurrentAccessToken; + } + + return false; + } + + public static string? GetRemoteServiceOrNull(this AIToolDefinition definition) + { + definition.Properties.TryGetValue(RemoteService, out var remoteServiceObj); + + return remoteServiceObj?.ToString(); + } + + public static string? GetRemoteControllerOrNull(this AIToolDefinition definition) + { + definition.Properties.TryGetValue(RemoteController, out var remoteControllerObj); + + return remoteControllerObj?.ToString(); + } + + public static string? GetRemoteMethodOrNull(this AIToolDefinition definition) + { + definition.Properties.TryGetValue(RemoteMethod, out var remoteMethodObj); + + return remoteMethodObj?.ToString(); + } + + public static string GetHttpEndpoint(this AIToolDefinition definition) + { + definition.Properties.TryGetValue(Endpoint, out var endpointObj); + + Check.NotNull(endpointObj, nameof(Endpoint)); + + return endpointObj.ToString()!; + } + + public static HttpMethod GetHttpMethod(this AIToolDefinition definition) + { + if (definition.Properties.TryGetValue(Method, out var methodObj) && methodObj != null) + { + try + { + return HttpMethod.Parse(methodObj.ToString()); + } + catch { } + } + + return HttpMethod.Get; + } + + public static IDictionary GetHttpHeaders(this AIToolDefinition definition) + { + if (definition.Properties.TryGetValue(Headers, out var headersObj) && headersObj != null) + { + if (headersObj is IDictionary h) + { + return h; + } + } + + return new Dictionary(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolInvokeContext.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolInvokeContext.cs new file mode 100644 index 000000000..41e2bdbf7 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolInvokeContext.cs @@ -0,0 +1,15 @@ +using System; + +namespace LINGYUN.Abp.AI.Tools.Http; +public class HttpAIToolInvokeContext +{ + public IServiceProvider ServiceProvider { get; } + public AIToolDefinition ToolDefinition { get; } + public HttpAIToolInvokeContext( + IServiceProvider serviceProvider, + AIToolDefinition toolDefinition) + { + ServiceProvider = serviceProvider; + ToolDefinition = toolDefinition; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs new file mode 100644 index 000000000..32022ffa2 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAIToolProvider.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Tools.Http; +public class HttpAIToolProvider : IAIToolProvider, ITransientDependency +{ + public const string ProviderName = "Http"; + public string Name => ProviderName; + protected IServiceProvider ServiceProvider { get; } + public HttpAIToolProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public virtual Task CreateToolsAsync(AIToolDefinition definition) + { + string? description = null; + if (definition.Description != null) + { + var localizerFactory = ServiceProvider.GetRequiredService(); + description = definition.Description.Localize(localizerFactory)?.Value; + } + + var httpAITool = AIFunctionFactory.Create( + method: typeof(HttpAITool).GetMethod(nameof(HttpAITool.InvokeAsync))!, + createInstanceFunc: (AIFunctionArguments args) => + { + var context = new HttpAIToolInvokeContext( + args.Services ?? ServiceProvider, + definition); + + return new HttpAITool(context); + }, + options: new AIFunctionFactoryOptions + { + Name = definition.Name, + Description = description, + AdditionalProperties = definition.Properties, + }); + + return Task.FromResult([httpAITool]); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json new file mode 100644 index 000000000..088f61324 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "Tools:GetApplicationConfiguration": "Get Application Configuration" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..d8439fa2d --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/Localization/Resources/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Tools:GetApplicationConfiguration": "获取应用程序配置" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs new file mode 100644 index 000000000..49445cb11 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs @@ -0,0 +1,48 @@ +using LINGYUN.Abp.AI.Tools.Http; +using System.Net.Http; +using System.Runtime.InteropServices; + +namespace Microsoft.Extensions.DependencyInjection; +internal static class HttpClientHttpAIToolExtenssions +{ + private const string HttpAIToolClient = "__AbpAIHttpToolClient"; + public static IServiceCollection AddHttpAIToolClient(this IServiceCollection services) + { + var preOptions = services.ExecutePreConfiguredActions(); + + var clientBuilder = services.AddHttpClient(HttpAIToolClient, (provider, client) => + { + foreach (var clientBuildAction in preOptions.ClientActions) + { + clientBuildAction(provider, client); + } + }).ConfigurePrimaryHttpMessageHandler(provider => + { + var handler = new HttpClientHandler(); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))) + { + handler.UseCookies = false; + } + + foreach (var handlerAction in preOptions.ClientHandlerActions) + { + handlerAction(provider, handler); + } + + return handler; + }); + + foreach (var clientBuildAction in preOptions.ClientBuildActions) + { + clientBuildAction(clientBuilder); + } + + return services; + } + + public static HttpClient GetHttpAIToolClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(HttpAIToolClient); + } +} From 469ce8c79e493e8cc9d818d0135016932403762a Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 27 Mar 2026 11:20:07 +0800 Subject: [PATCH 3/4] feat(ai): Add the McpAITool module --- Directory.Packages.props | 1 + aspnet-core/LINGYUN.MicroService.Aspire.slnx | 1 + .../AIServiceModule.cs | 3 +- .../LINGYUN.Abp.MicroService.AIService.csproj | 1 + .../LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs | 2 +- .../HttpClientHttpAIToolExtenssions.cs | 2 +- .../LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xml | 3 + .../LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xsd | 30 ++++ .../LINGYUN.Abp.AI.Tools.Mcp.csproj | 26 +++ .../Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs | 22 +++ .../Mcp/McpAIToolDefinitionExtenssions.cs | 150 ++++++++++++++++++ .../Abp/AI/Tools/Mcp/McpAIToolProvider.cs | 46 ++++++ .../HttpClientMcpAIToolExtenssions.cs | 18 +++ 13 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xml create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xsd create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index df0b5e2c1..5ea55035b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -349,6 +349,7 @@ + diff --git a/aspnet-core/LINGYUN.MicroService.Aspire.slnx b/aspnet-core/LINGYUN.MicroService.Aspire.slnx index cdfeaa94b..2f1617f3d 100644 --- a/aspnet-core/LINGYUN.MicroService.Aspire.slnx +++ b/aspnet-core/LINGYUN.MicroService.Aspire.slnx @@ -286,6 +286,7 @@ + diff --git a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs index b190eadcb..148ceb0c3 100644 --- a/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs +++ b/aspnet-core/aspire/LINGYUN.Abp.MicroService.AIService/AIServiceModule.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.AI.Ollama; using LINGYUN.Abp.AI.Tools; using LINGYUN.Abp.AI.Tools.Http; +using LINGYUN.Abp.AI.Tools.Mcp; using LINGYUN.Abp.AIManagement; using LINGYUN.Abp.AspNetCore.HttpOverrides; using LINGYUN.Abp.AspNetCore.Mvc.Localization; @@ -23,7 +24,6 @@ using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Caching.StackExchangeRedis; -using Volo.Abp.Http.Client; using Volo.Abp.Http.Client.IdentityModel.Web; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.Identity; @@ -53,6 +53,7 @@ namespace LINGYUN.Abp.MicroService.AIService; typeof(AbpAIOllamaModule), typeof(AbpAIToolsModule), typeof(AbpAIToolsHttpModule), + typeof(AbpAIToolsMcpModule), 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 5457d9300..1de96c0a0 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 @@ -60,6 +60,7 @@ + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs index c265ef9a9..3ce577228 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/LINGYUN/Abp/AI/Tools/Http/HttpAITool.cs @@ -61,7 +61,7 @@ public class HttpAITool var options = Context.ServiceProvider.GetRequiredService>().Value; var httpClientFactory = Context.ServiceProvider.GetRequiredService(); - var httpClient = httpClientFactory.GetHttpAIToolClient(); + var httpClient = httpClientFactory.CreateHttpAIToolClient(); var httpRequestMessage = new HttpRequestMessage( Context.ToolDefinition.GetHttpMethod(), diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs index 49445cb11..b2c050740 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Http/Microsoft/Extensions/DependencyInjection/HttpClientHttpAIToolExtenssions.cs @@ -41,7 +41,7 @@ internal static class HttpClientHttpAIToolExtenssions return services; } - public static HttpClient GetHttpAIToolClient(this IHttpClientFactory httpClientFactory) + public static HttpClient CreateHttpAIToolClient(this IHttpClientFactory httpClientFactory) { return httpClientFactory.CreateClient(HttpAIToolClient); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xml b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xsd b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/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.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj new file mode 100644 index 000000000..838a8c793 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN.Abp.AI.Tools.Mcp.csproj @@ -0,0 +1,26 @@ + + + + + + + net8.0;net9.0;net10.0 + LINGYUN.Abp.AI.Tools.Mcp + LINGYUN.Abp.AI.Tools.Mcp + false + false + false + enable + + + + + + + + + + + + + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs new file mode 100644 index 000000000..ceb89e623 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/AbpAIToolsMcpModule.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.AI.Tools.Mcp; + +[DependsOn( + typeof(AbpAIToolsModule), + typeof(AbpHttpClientModule))] +public class AbpAIToolsMcpModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMcpAIToolClient(); + + Configure(options => + { + // Mcp工具 + options.AIToolProviders.Add(); + }); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs new file mode 100644 index 000000000..5854903d4 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolDefinitionExtenssions.cs @@ -0,0 +1,150 @@ +using ModelContextProtocol.Client; +using System; +using System.Collections.Generic; +using Volo.Abp; + +namespace LINGYUN.Abp.AI.Tools.Mcp; +public static class McpAIToolDefinitionExtenssions +{ + private const string Endpoint = "McpEndpoint"; + private const string Headers = "McpHeaders"; + private const string TransportMode = "McpTransportMode"; + private const string ConnectionTimeout = "McpConnectionTimeout"; + private const string MaxReconnectionAttempts = "McpMaxReconnectionAttempts"; + private const string CurrentAccessToken = "UseMcpCurrentAccessToken"; + + public static AIToolDefinition UseMcpCurrentAccessToken(this AIToolDefinition definition, bool useCurrentAccessToken = true) + { + definition.WithProperty(CurrentAccessToken, useCurrentAccessToken); + + return definition; + } + + public static AIToolDefinition WithMcpEndpoint(this AIToolDefinition definition, string endPoint) + { + Check.NotNullOrWhiteSpace(endPoint, nameof(endPoint)); + + definition.WithProperty(Endpoint, endPoint); + + return definition; + } + + public static AIToolDefinition WithMcpTransportMode(this AIToolDefinition definition, HttpTransportMode transportMode = HttpTransportMode.AutoDetect) + { + definition.WithProperty(TransportMode, ((int)transportMode).ToString()); + + return definition; + } + + public static AIToolDefinition WithMcpConnectionTimeout(this AIToolDefinition definition, TimeSpan connectionTimeout) + { + definition.WithProperty(ConnectionTimeout, connectionTimeout.ToString()); + + return definition; + } + + public static AIToolDefinition WithMcpMaxReconnectionAttempts(this AIToolDefinition definition, int maxReconnectionAttempts = 5) + { + definition.WithProperty(MaxReconnectionAttempts, maxReconnectionAttempts); + + return definition; + } + + public static AIToolDefinition WithMcpHeaders(this AIToolDefinition definition, IDictionary headers) + { + Check.NotNullOrEmpty(headers, nameof(headers)); + + var currentHeaders = definition.GetMcpHeaders(); + + currentHeaders.AddIfNotContains(headers); + + definition.WithProperty(Headers, currentHeaders); + + return definition; + } + + public static AIToolDefinition WithMcpHeaders(this AIToolDefinition definition, string key, string value) + { + Check.NotNullOrEmpty(key, nameof(key)); + Check.NotNullOrEmpty(value, nameof(value)); + + var currentHeaders = definition.GetMcpHeaders(); + + currentHeaders.TryAdd(key, value); + + definition.WithProperty(Headers, currentHeaders); + + return definition; + } + + public static bool IsUseMcpCurrentAccessToken(this AIToolDefinition definition) + { + if (definition.Properties.TryGetValue(CurrentAccessToken, out var useCurrentAccessTokenObj) && useCurrentAccessTokenObj != null + && bool.TryParse(useCurrentAccessTokenObj.ToString(), out var useCurrentAccessToken)) + { + return useCurrentAccessToken; + } + + return false; + } + + public static string GetMcpEndpoint(this AIToolDefinition definition) + { + definition.Properties.TryGetValue(Endpoint, out var endpointObj); + + Check.NotNull(endpointObj, nameof(Endpoint)); + + return endpointObj.ToString()!; + } + + public static TimeSpan GetMcpConnectionTimeout(this AIToolDefinition definition, double defaultConnectionTimeoutSeconds = 30) + { + definition.Properties.TryGetValue(ConnectionTimeout, out var connectionTimeoutObj); + + if (connectionTimeoutObj != null && + TimeSpan.TryParse(connectionTimeoutObj.ToString(), out var connectionTimeout)) + { + return connectionTimeout; + } + + return TimeSpan.FromSeconds(defaultConnectionTimeoutSeconds); + } + + public static HttpTransportMode GetMcpTransportMode(this AIToolDefinition definition, HttpTransportMode defaultTransportMode = HttpTransportMode.AutoDetect) + { + definition.Properties.TryGetValue(TransportMode, out var defaultTransportModeObj); + + if (defaultTransportModeObj != null && + Enum.TryParse(defaultTransportModeObj.ToString(), out var transportMode)) + { + return transportMode; + } + + return defaultTransportMode; + } + public static int GetMcpMaxReconnectionAttempts(this AIToolDefinition definition, int defaultMaxReconnectionAttempts = 5) + { + definition.Properties.TryGetValue(MaxReconnectionAttempts, out var maxReconnectionAttemptsObj); + + if (maxReconnectionAttemptsObj != null && + int.TryParse(maxReconnectionAttemptsObj.ToString(), out var maxReconnectionAttempts)) + { + return maxReconnectionAttempts; + } + + return defaultMaxReconnectionAttempts; + } + + public static IDictionary GetMcpHeaders(this AIToolDefinition definition) + { + if (definition.Properties.TryGetValue(Headers, out var headersObj) && headersObj != null) + { + if (headersObj is IDictionary h) + { + return h; + } + } + + return new Dictionary(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs new file mode 100644 index 000000000..80942da22 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using ModelContextProtocol.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Tools.Mcp; +public class McpAIToolProvider : IAIToolProvider, ITransientDependency +{ + public const string ProviderName = "Mcp"; + public string Name => ProviderName; + + protected IHttpClientFactory HttpClientFactory { get; } + public McpAIToolProvider(IHttpClientFactory httpClientFactory) + { + HttpClientFactory = httpClientFactory; + } + + public async virtual Task CreateToolsAsync(AIToolDefinition definition) + { + var httpClient = HttpClientFactory.CreateMcpAIToolClient(); + var httpClientTransportOptions = new HttpClientTransportOptions + { + Endpoint = new Uri(definition.GetMcpEndpoint()), + AdditionalHeaders = new Dictionary(), + TransportMode = definition.GetMcpTransportMode(), + ConnectionTimeout = definition.GetMcpConnectionTimeout(), + MaxReconnectionAttempts = definition.GetMcpMaxReconnectionAttempts(), + }; + + var headers = definition.GetMcpHeaders(); + foreach (var header in headers) + { + httpClientTransportOptions.AdditionalHeaders.TryAdd(header.Key, header.Value); + } + + var mcpClient = await McpClient.CreateAsync( + new HttpClientTransport(httpClientTransportOptions, httpClient)); + + return (await mcpClient.ListToolsAsync()).ToArray(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs new file mode 100644 index 000000000..98d5bc1c8 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/Microsoft/Extensions/DependencyInjection/HttpClientMcpAIToolExtenssions.cs @@ -0,0 +1,18 @@ +using System.Net.Http; + +namespace Microsoft.Extensions.DependencyInjection; +internal static class HttpClientMcpAIToolExtenssions +{ + private const string McpAIToolClient = "__AbpAIMcpToolClient"; + public static IServiceCollection AddMcpAIToolClient(this IServiceCollection services) + { + services.AddHttpClient(McpAIToolClient); + + return services; + } + + public static HttpClient CreateMcpAIToolClient(this IHttpClientFactory httpClientFactory) + { + return httpClientFactory.CreateClient(McpAIToolClient); + } +} From eb9ed8ee2434f9e38411201315d81ccc9f69ea24 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 27 Mar 2026 11:25:01 +0800 Subject: [PATCH 4/4] feat(ai-mcp): Increase the current user token --- .../Abp/AI/Tools/Mcp/McpAIToolProvider.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs index 80942da22..1b32dc900 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools.Mcp/LINGYUN/Abp/AI/Tools/Mcp/McpAIToolProvider.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.Authentication; namespace LINGYUN.Abp.AI.Tools.Mcp; public class McpAIToolProvider : IAIToolProvider, ITransientDependency @@ -14,9 +15,13 @@ public class McpAIToolProvider : IAIToolProvider, ITransientDependency public const string ProviderName = "Mcp"; public string Name => ProviderName; + protected IServiceProvider ServiceProvider { get; } protected IHttpClientFactory HttpClientFactory { get; } - public McpAIToolProvider(IHttpClientFactory httpClientFactory) + public McpAIToolProvider( + IServiceProvider serviceProvider, + IHttpClientFactory httpClientFactory) { + ServiceProvider = serviceProvider; HttpClientFactory = httpClientFactory; } @@ -38,6 +43,18 @@ public class McpAIToolProvider : IAIToolProvider, ITransientDependency httpClientTransportOptions.AdditionalHeaders.TryAdd(header.Key, header.Value); } + if (definition.IsUseMcpCurrentAccessToken()) + { + var accessTokenProvider = ServiceProvider.GetRequiredService(); + + var token = await accessTokenProvider.GetTokenAsync(); + if (!token.IsNullOrWhiteSpace()) + { + // TODO: 使用OAuth配置? + httpClientTransportOptions.AdditionalHeaders.TryAdd("Authorization", $"Bearer {token}"); + } + } + var mcpClient = await McpClient.CreateAsync( new HttpClientTransport(httpClientTransportOptions, httpClient));