From 4bcdc417b33458974a1a01cea0d4dd9d478e99c5 Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 1 Apr 2026 10:42:03 +0800 Subject: [PATCH] feat(ai): Optimize the configuration of AI tools --- .../Abp/AI/Tools/AIToolDisabledState.cs | 10 +++ .../LINGYUN/Abp/AI/Tools/AbpAIToolsModule.cs | 60 ++++------------ .../Abp/AI/Tools/IWorkspaceAIToolFinder.cs | 14 ++++ .../Abp/AI/Tools/WorkspaceAIToolFinder.cs | 70 +++++++++++++++++++ .../Chats/ConversationChangeNameHandler.cs | 69 +++++++++--------- 5 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDisabledState.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IWorkspaceAIToolFinder.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/WorkspaceAIToolFinder.cs diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDisabledState.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDisabledState.cs new file mode 100644 index 000000000..b129e82a5 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDisabledState.cs @@ -0,0 +1,10 @@ +namespace LINGYUN.Abp.AI.Tools; +public class AIToolDisabledState +{ + public bool IsDisabled { get; private set; } + + public AIToolDisabledState(bool isDisabled) + { + IsDisabled = isDisabled; + } +} 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 index 211537b3e..2cbdd7ef0 100644 --- 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 @@ -1,10 +1,9 @@ using LINGYUN.Abp.AI.Localization; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -34,55 +33,26 @@ public class AbpAIToolsModule : AbpModule Configure(options => { - options.ChatClientBuildActions.Add((_, __, builder) => - { - // 启用以支持函数式工具 - builder.UseFunctionInvocation(); - - return Task.FromResult(builder); - }); options.ChatClientBuildActions.Add(async (workspace, sp, builder) => { - var useAITools = new List(); - var useAIToolDefinitions = new List(); - var aiToolFactory = sp.GetRequiredService(); - var aiToolDefinitionManager = sp.GetRequiredService(); - var aiToolDefinitions = await aiToolDefinitionManager.GetAllAsync(); - - if (workspace.Tools.Count > 0) + IList? workspaceAITools = default!; + var workspaceAIToolFinder = sp.GetService(); + if (workspaceAIToolFinder != null && workspaceAIToolFinder.IsAIToolEnabled()) { - useAIToolDefinitions.AddRange(aiToolDefinitions.Where(aiTool => workspace.Tools.Contains(aiTool.Name))); - } - - foreach (var globalAIToolDefinition in aiToolDefinitions.Where(aiTool => aiTool.IsGlobal)) - { - if (!useAIToolDefinitions.Any(tool => tool.Name == globalAIToolDefinition.Name)) - { - useAIToolDefinitions.Add(globalAIToolDefinition); - } + workspaceAITools = await workspaceAIToolFinder.GetToolsAsync(workspace); } - foreach (var aiToolDefinition in useAIToolDefinitions) - { - var aiTools = await aiToolFactory.CreateTool(aiToolDefinition); - if (aiTools.Length > 0) - { - useAITools.AddRange(aiTools); - } - } - - return builder.ConfigureOptions(ai => - { - ai.ToolMode = ChatToolMode.Auto; - ai.AllowMultipleToolCalls = true; - - ai.Tools ??= []; - - foreach (var aiTool in useAITools) + return builder + .ConfigureOptions(config => { - ai.Tools.Add(aiTool); - } - }); + config.ToolMode = ChatToolMode.Auto; + config.AllowMultipleToolCalls = true; + + // 添加发现的工具 + config.Tools = workspaceAITools; + }) + // 启用以支持函数式工具 + .UseFunctionInvocation(); }); }); diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IWorkspaceAIToolFinder.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IWorkspaceAIToolFinder.cs new file mode 100644 index 000000000..2e0437c5e --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/IWorkspaceAIToolFinder.cs @@ -0,0 +1,14 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Tools; +public interface IWorkspaceAIToolFinder +{ + IDisposable DisableAITool(); + + Task GetToolsAsync(WorkspaceDefinition workspace); + + bool IsAIToolEnabled(); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/WorkspaceAIToolFinder.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/WorkspaceAIToolFinder.cs new file mode 100644 index 000000000..6fd57ef7c --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/WorkspaceAIToolFinder.cs @@ -0,0 +1,70 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace LINGYUN.Abp.AI.Tools; +public class WorkspaceAIToolFinder : IWorkspaceAIToolFinder, ITransientDependency +{ + private const string AIToolDisabledScopeKey = "Abp.AI.Tools.DisabledAITool"; + + private readonly IAIToolFactory _aiToolFactory; + private readonly IAIToolDefinitionManager _aiToolDefinitionManager; + private readonly IAmbientScopeProvider _aiToolDisabledState; + + public WorkspaceAIToolFinder( + IAIToolFactory aiToolFactory, + IAIToolDefinitionManager aiToolDefinitionManager, + IAmbientScopeProvider aiToolDisabledState) + { + _aiToolFactory = aiToolFactory; + _aiToolDefinitionManager = aiToolDefinitionManager; + _aiToolDisabledState = aiToolDisabledState; + } + + public virtual IDisposable DisableAITool() + { + return _aiToolDisabledState.BeginScope(AIToolDisabledScopeKey, new AIToolDisabledState(true)); + } + + public async virtual Task GetToolsAsync(WorkspaceDefinition workspace) + { + var useAITools = new List(); + var useAIToolDefinitions = new List(); + var aiToolDefinitions = await _aiToolDefinitionManager.GetAllAsync(); + + if (workspace.Tools.Count > 0) + { + useAIToolDefinitions.AddRange(aiToolDefinitions.Where(aiTool => workspace.Tools.Contains(aiTool.Name))); + } + + foreach (var globalAIToolDefinition in aiToolDefinitions.Where(aiTool => aiTool.IsGlobal)) + { + if (!useAIToolDefinitions.Any(tool => tool.Name == globalAIToolDefinition.Name)) + { + useAIToolDefinitions.Add(globalAIToolDefinition); + } + } + + foreach (var aiToolDefinition in useAIToolDefinitions) + { + var aiTools = await _aiToolFactory.CreateTool(aiToolDefinition); + if (aiTools.Length > 0) + { + useAITools.AddRange(aiTools); + } + } + + return useAITools.ToArray(); + } + + public virtual bool IsAIToolEnabled() + { + var state = _aiToolDisabledState.GetValue(AIToolDisabledScopeKey); + return state == null || !state.IsDisabled; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Chats/ConversationChangeNameHandler.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Chats/ConversationChangeNameHandler.cs index 2588abf3e..4c254c319 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Chats/ConversationChangeNameHandler.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Chats/ConversationChangeNameHandler.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.AI; +using LINGYUN.Abp.AI.Tools; using LINGYUN.Abp.AIManagement.Localization; using LINGYUN.Abp.AIManagement.Tokens; using Microsoft.Extensions.AI; @@ -25,6 +26,7 @@ public class ConversationChangeNameHandler : private readonly IGuidGenerator _guidGenerator; private readonly IAbpDistributedLock _distributedLock; private readonly IChatClientFactory _chatClientFactory; + private readonly IWorkspaceAIToolFinder _workspaceAIToolFinder; private readonly IStringLocalizer _stringLocalizer; private readonly ITokenUsageRecordRepository _tokenUsageRecordRepository; private readonly IConversationRecordRepository _conversationRecordRepository; @@ -34,7 +36,8 @@ public class ConversationChangeNameHandler : IServiceProvider serviceProvider, IGuidGenerator guidGenerator, IAbpDistributedLock distributedLock, - IChatClientFactory chatClientFactory, + IChatClientFactory chatClientFactory, + IWorkspaceAIToolFinder workspaceAIToolFinder, IStringLocalizer stringLocalizer, ITokenUsageRecordRepository tokenUsageRecordRepository, IConversationRecordRepository conversationRecordRepository, @@ -44,6 +47,7 @@ public class ConversationChangeNameHandler : _guidGenerator = guidGenerator; _distributedLock = distributedLock; _chatClientFactory = chatClientFactory; + _workspaceAIToolFinder = workspaceAIToolFinder; _stringLocalizer = stringLocalizer; _tokenUsageRecordRepository = tokenUsageRecordRepository; _conversationRecordRepository = conversationRecordRepository; @@ -96,42 +100,41 @@ public class ConversationChangeNameHandler : { var chatClient = await _chatClientFactory.CreateAsync(chatMessage.Workspace); var instructions = _stringLocalizer["DesignConversationNamePrompt", ConversationRecordConsts.MaxNameLength].Value; - var aiAgent = chatClient - .AsBuilder() - .ConfigureOptions(options => - { - // 不受工具影响 - options.Tools = []; - }) - .BuildAIAgent( - instructions: instructions, - services: _serviceProvider); - var agentRunRes = await aiAgent.RunAsync([ - new ChatMessage(ChatRole.System, instructions), - new ChatMessage(ChatRole.User, chatMessage.Content)]); + // 禁用AI工具 + using (_workspaceAIToolFinder.DisableAITool()) + { + var aiAgent = chatClient + .CreateAIAgent( + instructions: instructions, + services: _serviceProvider); - conversation.SetName( - agentRunRes.Text.Length > ConversationRecordConsts.MaxNameLength - ? agentRunRes.Text[..ConversationRecordConsts.MaxNameLength] - : agentRunRes.Text); + var agentRunRes = await aiAgent.RunAsync([ + new ChatMessage(ChatRole.System, instructions), + new ChatMessage(ChatRole.User, chatMessage.Content)]); - await _conversationRecordRepository.UpdateAsync(conversation); + conversation.SetName( + agentRunRes.Text.Length > ConversationRecordConsts.MaxNameLength + ? chatMessage.Content[..ConversationRecordConsts.MaxNameLength] + : agentRunRes.Text); - if (agentRunRes.Usage != null) - { - var tokenUsageRecord = new TokenUsageRecord( - _guidGenerator.Create(), - chatMessage.Id, - conversation.Id, - agentRunRes.Usage.InputTokenCount, - agentRunRes.Usage.OutputTokenCount, - agentRunRes.Usage.TotalTokenCount, - agentRunRes.Usage.CachedInputTokenCount, - agentRunRes.Usage.ReasoningTokenCount, - chatMessage.TenantId); - - await _tokenUsageRecordRepository.InsertAsync(tokenUsageRecord); + await _conversationRecordRepository.UpdateAsync(conversation); + + if (agentRunRes.Usage != null) + { + var tokenUsageRecord = new TokenUsageRecord( + _guidGenerator.Create(), + chatMessage.Id, + conversation.Id, + agentRunRes.Usage.InputTokenCount, + agentRunRes.Usage.OutputTokenCount, + agentRunRes.Usage.TotalTokenCount, + agentRunRes.Usage.CachedInputTokenCount, + agentRunRes.Usage.ReasoningTokenCount, + chatMessage.TenantId); + + await _tokenUsageRecordRepository.InsertAsync(tokenUsageRecord); + } } } }