From 1d35fc6628f0b346592c7e14a2934d5f5ddc3874 Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 15 Jan 2026 20:25:58 +0800 Subject: [PATCH] feat(ai): Add simple conversation service --- .../LINGYUN/Abp/AI/Agent/AgentFactory.cs | 110 ++++++++++++++ .../LINGYUN/Abp/AI/Agent/AgentService.cs | 91 ++++++++++++ .../Abp/AI/Agent/ChatClientAgentFactory.cs | 140 ------------------ .../LINGYUN/Abp/AI/Agent/IAgentFactory.cs | 13 ++ .../LINGYUN/Abp/AI/Agent/IAgentService.cs | 8 + .../Abp/AI/Agent/IChatClientAgentFactory.cs | 12 -- .../LINGYUN/Abp/AI/Agent/WorkspaceAIAgent.cs | 38 ++--- .../LINGYUN/Abp/AI/AbpAICoreModule.cs | 1 + .../LINGYUN/Abp/AI/ChatClientFactory.cs | 35 +---- .../LINGYUN/Abp/AI/ChatClientProvider.cs | 3 +- .../LINGYUN/Abp/AI/IChatClientFactory.cs | 5 +- .../LINGYUN/Abp/AI/IChatClientProvider.cs | 3 +- .../LINGYUN/Abp/AI/IWorkspaceChatClient.cs | 8 - .../LINGYUN/Abp/AI/KernelFactory.cs | 28 +--- .../LINGYUN/Abp/AI/KernelProvider.cs | 19 +++ .../Abp/AI/Messages/IUserMessageStore.cs | 11 ++ .../AI/Messages/InMemoryUserMessageStore.cs | 40 +++++ .../LINGYUN/Abp/AI/Models/ChatMessageInfo.cs | 8 + .../LINGYUN/Abp/AI/Models/MediaMessage.cs | 13 ++ .../LINGYUN/Abp/AI/Models/TokenUsageInfo.cs | 17 +++ .../LINGYUN/Abp/AI/Models/UserMessage.cs | 37 +++++ .../Abp/AI/OpenAIChatClientProvider.cs | 6 +- .../LINGYUN/Abp/AI/OpenAIKernelProvider.cs | 39 +++++ .../LINGYUN/Abp/AI/Tokens/ITokenUsageStore.cs | 9 ++ .../Abp/AI/Tokens/InMemoryTokenUsageStore.cs | 50 +++++++ .../LINGYUN/Abp/AI/Tools/GlobalFunctions.cs | 11 ++ .../LINGYUN/Abp/AI/WorkspaceChatClient.cs | 69 --------- 27 files changed, 517 insertions(+), 307 deletions(-) create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentFactory.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentService.cs delete mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentFactory.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentService.cs delete mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs delete mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IWorkspaceChatClient.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProvider.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/IUserMessageStore.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/InMemoryUserMessageStore.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/ChatMessageInfo.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/MediaMessage.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/TokenUsageInfo.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/UserMessage.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIKernelProvider.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/ITokenUsageStore.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/InMemoryTokenUsageStore.cs create mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tools/GlobalFunctions.cs delete mode 100644 aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/WorkspaceChatClient.cs 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 new file mode 100644 index 000000000..3739fdfd3 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentFactory.cs @@ -0,0 +1,110 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Localization; +using System; +using System.Threading.Tasks; +using Volo.Abp.AI; +using Volo.Abp.Authorization; +using Volo.Abp.DependencyInjection; +using Volo.Abp.SimpleStateChecking; + +namespace LINGYUN.Abp.AI.Agent; +public class AgentFactory : IAgentFactory, IScopedDependency +{ + protected IServiceProvider ServiceProvider { get; } + protected IChatClientFactory ChatClientFactory { get; } + protected IStringLocalizerFactory StringLocalizerFactory { get; } + protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } + protected ISimpleStateCheckerManager StateCheckerManager { get; } + public AgentFactory( + IServiceProvider serviceProvider, + IChatClientFactory chatClientFactory, + IStringLocalizerFactory stringLocalizerFactory, + IWorkspaceDefinitionManager workspaceDefinitionManager, + ISimpleStateCheckerManager stateCheckerManager) + { + ServiceProvider = serviceProvider; + ChatClientFactory = chatClientFactory; + StringLocalizerFactory = stringLocalizerFactory; + WorkspaceDefinitionManager = workspaceDefinitionManager; + StateCheckerManager = stateCheckerManager; + } + + public async virtual Task CreateAsync() + { + var workspace = WorkspaceNameAttribute.GetWorkspaceName(); + + var chatClient = await ChatClientFactory.CreateAsync(); + + var workspaceDefine = await WorkspaceDefinitionManager.GetOrNullAsync(workspace); + + if (workspaceDefine != null) + { + await CheckWorkspaceStateAsync(workspaceDefine); + } + + return await CreateAgentAsync(chatClient, workspaceDefine); + } + + public async virtual Task CreateAsync(string workspace) + { + var workspaceDefine = await WorkspaceDefinitionManager.GetAsync(workspace); + + await CheckWorkspaceStateAsync(workspaceDefine); + + var chatClient = await ChatClientFactory.CreateAsync(workspace); + + return await CreateAgentAsync(chatClient, workspaceDefine); + } + + protected async virtual Task CreateAgentAsync(IChatClient chatClient, WorkspaceDefinition? workspace) + { + string? description = null; + if (workspace?.Description != null) + { + description = workspace.Description.Localize(StringLocalizerFactory); + } + + var tools = await GetAgentToolsAsync(workspace); + + var clientAgentOptions = new ChatClientAgentOptions + { + ChatOptions = new ChatOptions + { + Instructions = workspace?.Instructions, + Temperature = workspace?.Temperature, + MaxOutputTokens = workspace?.MaxOutputTokens, + PresencePenalty = workspace?.PresencePenalty, + FrequencyPenalty = workspace?.FrequencyPenalty, + Tools = tools, + }, + Name = workspace?.Name, + Description = description + }; + + var aiAgent = chatClient.CreateAIAgent(clientAgentOptions) + .AsBuilder() + .UseLogging() + .UseOpenTelemetry() + .Build(ServiceProvider); + + return new WorkspaceAIAgent(aiAgent, workspace); + } + + protected virtual Task GetAgentToolsAsync(WorkspaceDefinition? workspace) + { + return Task.FromResult([]); + } + + protected async virtual Task CheckWorkspaceStateAsync(WorkspaceDefinition workspace) + { + if (!await StateCheckerManager.IsEnabledAsync(workspace)) + { + throw new AbpAuthorizationException( + $"Workspace is not enabled: {workspace.Name}!", + AbpAIErrorCodes.WorkspaceIsNotEnabled) + .WithData("Workspace", workspace.Name); + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentService.cs new file mode 100644 index 000000000..ed34c4200 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentService.cs @@ -0,0 +1,91 @@ +using LINGYUN.Abp.AI.Messages; +using LINGYUN.Abp.AI.Models; +using LINGYUN.Abp.AI.Tokens; +using Microsoft.Extensions.AI; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Agent; +public class AgentService : IAgentService, IScopedDependency +{ + private readonly IAgentFactory _agentFactory; + private readonly ITokenUsageStore _tokenUsageStore; + private readonly IUserMessageStore _userMessageStore; + public AgentService( + IAgentFactory agentFactory, + ITokenUsageStore tokenUsageStore, + IUserMessageStore userMessageStore) + { + _agentFactory = agentFactory; + _tokenUsageStore = tokenUsageStore; + _userMessageStore = userMessageStore; + } + + public async virtual IAsyncEnumerable SendMessageAsync(UserMessage message) + { + var messages = await BuildChatMessages(message); + + var agent = await _agentFactory.CreateAsync(message.Workspace); + + var agentRunRes = agent.RunStreamingAsync(messages); + + var agentMessageBuilder = new StringBuilder(); + + await foreach (var item in agentRunRes) + { + agentMessageBuilder.Append(item); + yield return item.Text; + + await StoreTokenUsageInfo(message, item.RawRepresentation); + } + + await StoreChatMessage(message, agentMessageBuilder.ToString()); + } + + protected virtual async Task> BuildChatMessages(UserMessage message) + { + var messages = new List(); + + var historyMessages = await _userMessageStore.GetHistoryMessagesAsync(message.ChatId); + + foreach (var chatMessage in historyMessages) + { + messages.Add(new ChatMessage(ChatRole.System, chatMessage.Content)); + } + + messages.Add(new ChatMessage(ChatRole.User, message.Content)); + + return messages; + } + + protected async virtual Task StoreChatMessage(UserMessage message, string agentMessage) + { + message.WithReply(agentMessage); + + await _userMessageStore.SaveMessageAsync(message); + } + + protected async virtual Task StoreTokenUsageInfo(UserMessage message, object? rawRepresentation) + { + if (rawRepresentation is ChatResponseUpdate update) + { + var tokenUsageInfos = update.Contents + .OfType() + .Where(usage => usage.Details != null) + .Select(usage => new TokenUsageInfo(message.Workspace) + { + TotalTokenCount = usage.Details.TotalTokenCount, + CachedInputTokenCount = usage.Details.CachedInputTokenCount, + InputTokenCount = usage.Details.InputTokenCount, + AdditionalCounts = usage.Details.AdditionalCounts, + OutputTokenCount = usage.Details.OutputTokenCount, + ReasoningTokenCount = usage.Details.ReasoningTokenCount, + }); + + await _tokenUsageStore.SaveTokenUsagesAsync(tokenUsageInfos); + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs deleted file mode 100644 index 106e40768..000000000 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs +++ /dev/null @@ -1,140 +0,0 @@ -using LINGYUN.Abp.AI.Workspaces; -using Microsoft.Agents.AI; -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using Volo.Abp.AI; -using Volo.Abp.Authorization; -using Volo.Abp.DependencyInjection; -using Volo.Abp.SimpleStateChecking; - -namespace LINGYUN.Abp.AI.Agent; -public class ChatClientAgentFactory : IChatClientAgentFactory, ISingletonDependency -{ - private readonly static ConcurrentDictionary _chatClientAgentCache = new(); - protected IServiceProvider ServiceProvider { get; } - protected IChatClientFactory ChatClientFactory { get; } - protected IStringLocalizerFactory StringLocalizerFactory { get; } - protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } - protected ISimpleStateCheckerManager StateCheckerManager { get; } - public ChatClientAgentFactory( - IServiceProvider serviceProvider, - IChatClientFactory chatClientFactory, - IStringLocalizerFactory stringLocalizerFactory, - IWorkspaceDefinitionManager workspaceDefinitionManager, - ISimpleStateCheckerManager stateCheckerManager) - { - ServiceProvider = serviceProvider; - ChatClientFactory = chatClientFactory; - StringLocalizerFactory = stringLocalizerFactory; - WorkspaceDefinitionManager = workspaceDefinitionManager; - StateCheckerManager = stateCheckerManager; - } - - public async virtual Task CreateAsync() - { - var workspace = WorkspaceNameAttribute.GetWorkspaceName(); - if (_chatClientAgentCache.TryGetValue(workspace, out var chatClientAgent)) - { - return chatClientAgent; - } - - var chatClient = await ChatClientFactory.CreateAsync(); - - var workspaceDefine = await WorkspaceDefinitionManager.GetOrNullAsync(workspace); - - if (workspaceDefine != null) - { - await CheckWorkspaceStateAsync(workspaceDefine); - } - - string? description = null; - if (workspaceDefine?.Description != null) - { - description = workspaceDefine.Description.Localize(StringLocalizerFactory); - } - - var clientAgentOptions = new ChatClientAgentOptions - { - ChatOptions = new ChatOptions - { - Instructions = workspaceDefine?.Instructions, - Temperature = workspaceDefine?.Temperature, - MaxOutputTokens = workspaceDefine?.MaxOutputTokens, - PresencePenalty = workspaceDefine?.PresencePenalty, - FrequencyPenalty = workspaceDefine?.FrequencyPenalty, - }, - Name = workspaceDefine?.Name, - Description = description - }; - - chatClientAgent = new WorkspaceAIAgent( - new AIAgentBuilder(chatClient.CreateAIAgent(clientAgentOptions)) - .UseLogging() - .UseOpenTelemetry() - .Build(ServiceProvider), - workspaceDefine); - - _chatClientAgentCache.TryAdd(workspace, chatClientAgent); - - return chatClientAgent; - } - - public async virtual Task CreateAsync(string workspace) - { - if (_chatClientAgentCache.TryGetValue(workspace, out var chatClientAgent)) - { - return chatClientAgent; - } - var workspaceDefine = await WorkspaceDefinitionManager.GetAsync(workspace); - - await CheckWorkspaceStateAsync(workspaceDefine); - - var chatClient = await ChatClientFactory.CreateAsync(workspace); - - string? description = null; - if (workspaceDefine.Description != null) - { - description = workspaceDefine.Description.Localize(StringLocalizerFactory); - } - - var clientAgentOptions = new ChatClientAgentOptions - { - ChatOptions = new ChatOptions - { - Instructions = workspaceDefine.Instructions, - Temperature = workspaceDefine.Temperature, - MaxOutputTokens = workspaceDefine.MaxOutputTokens, - PresencePenalty = workspaceDefine.PresencePenalty, - FrequencyPenalty = workspaceDefine.FrequencyPenalty, - }, - Name = workspaceDefine.Name, - Description = description - }; - - chatClientAgent = new WorkspaceAIAgent( - new AIAgentBuilder(chatClient.CreateAIAgent(clientAgentOptions)) - .UseLogging() - .UseOpenTelemetry() - .Build(ServiceProvider), - workspaceDefine); - - _chatClientAgentCache.TryAdd(workspace, chatClientAgent); - - return chatClientAgent; - } - - protected async virtual Task CheckWorkspaceStateAsync(WorkspaceDefinition workspace) - { - if (!await StateCheckerManager.IsEnabledAsync(workspace)) - { - throw new AbpAuthorizationException( - $"Workspace is not enabled: {workspace.Name}!", - AbpAIErrorCodes.WorkspaceIsNotEnabled) - .WithData("Workspace", workspace.Name); - } - } -} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentFactory.cs new file mode 100644 index 000000000..b969e8f67 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentFactory.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; +using Microsoft.Agents.AI; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Agent; +public interface IAgentFactory +{ + [NotNull] + Task CreateAsync(); + + [NotNull] + Task CreateAsync(string workspace); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentService.cs new file mode 100644 index 000000000..24ad9e19f --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentService.cs @@ -0,0 +1,8 @@ +using LINGYUN.Abp.AI.Models; +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI.Agent; +public interface IAgentService +{ + IAsyncEnumerable SendMessageAsync(UserMessage message); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs deleted file mode 100644 index cf7f938ef..000000000 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JetBrains.Annotations; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.AI.Agent; -public interface IChatClientAgentFactory -{ - [NotNull] - Task CreateAsync(); - - [NotNull] - Task CreateAsync(string workspace); -} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/WorkspaceAIAgent.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/WorkspaceAIAgent.cs index 41539ec09..03998df61 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/WorkspaceAIAgent.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/WorkspaceAIAgent.cs @@ -11,49 +11,49 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.AI.Agent; public class WorkspaceAIAgent : AIAgent { - protected AIAgent InnerAIAgent { get; } - public WorkspaceDefinition? Workspace { get; } - - public WorkspaceAIAgent( - AIAgent innerAIAgent, - WorkspaceDefinition? workspace = null) + protected AIAgent InnerAgent { get; } + protected WorkspaceDefinition? Workspace { get; } + public WorkspaceAIAgent(AIAgent innerAgent, WorkspaceDefinition? workspace) { - InnerAIAgent = innerAIAgent; + InnerAgent = innerAgent; Workspace = workspace; } public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) { - return InnerAIAgent.DeserializeThread(serializedThread, jsonSerializerOptions); + return InnerAgent.DeserializeThread(serializedThread, jsonSerializerOptions); } public override AgentThread GetNewThread() { - return InnerAIAgent.GetNewThread(); + return InnerAgent.GetNewThread(); } protected override Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { - return InnerAIAgent.RunAsync(GetChatMessages(messages), thread, options, cancellationToken); + return InnerAgent.RunAsync(GetChatMessages(messages), thread, options, cancellationToken); } protected override IAsyncEnumerable RunCoreStreamingAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { - return InnerAIAgent.RunStreamingAsync(GetChatMessages(messages), thread, options, cancellationToken); + return InnerAgent.RunStreamingAsync(GetChatMessages(messages), thread, options, cancellationToken); } protected virtual IEnumerable GetChatMessages(IEnumerable messages) { - if (Workspace?.SystemPrompt?.IsNullOrWhiteSpace() == false && - !messages.Any(msg => msg.Role == ChatRole.System)) + var unionMessages = new List(); + + if (Workspace != null) { - // 加入系统提示词 - messages = new List + if (!Workspace.SystemPrompt.IsNullOrWhiteSpace()) { - new ChatMessage(ChatRole.System, Workspace.SystemPrompt) - }.Union(messages); + unionMessages.Add(new ChatMessage(ChatRole.System, Workspace.SystemPrompt)); + } + if (!Workspace.Instructions.IsNullOrWhiteSpace()) + { + unionMessages.Add(new ChatMessage(ChatRole.System, Workspace.Instructions)); + } } - - return messages; + return unionMessages.Union(messages); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs index 8bdcf3754..3f81bd409 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs @@ -38,6 +38,7 @@ public class AbpAICoreModule : AbpModule Configure(options => { options.ChatClientProviders.Add(); + options.KernelProviders.Add(); }); Configure(options => diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs index 04f59d3e3..3f5280dee 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs @@ -1,7 +1,6 @@ using LINGYUN.Abp.AI.Workspaces; using Microsoft.Extensions.AI; using System; -using System.Collections.Concurrent; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.AI; @@ -10,9 +9,8 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.SimpleStateChecking; namespace LINGYUN.Abp.AI; -public class ChatClientFactory : IChatClientFactory, ISingletonDependency +public class ChatClientFactory : IChatClientFactory, IScopedDependency { - private readonly static ConcurrentDictionary _chatClientCache = new(); protected ISimpleStateCheckerManager StateCheckerManager { get; } protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } protected IChatClientProviderManager ChatClientProviderManager { get; } @@ -30,13 +28,9 @@ public class ChatClientFactory : IChatClientFactory, ISingletonDependency ServiceProvider = serviceProvider; } - public async virtual Task CreateAsync() + public async virtual Task CreateAsync() { var workspace = WorkspaceNameAttribute.GetWorkspaceName(); - if (_chatClientCache.TryGetValue(workspace, out var chatClient)) - { - return chatClient; - } var chatClientAccessorType = typeof(IChatClientAccessor<>).MakeGenericType(typeof(TWorkspace)); var chatClientAccessor = ServiceProvider.GetService(chatClientAccessorType); @@ -44,35 +38,22 @@ public class ChatClientFactory : IChatClientFactory, ISingletonDependency chatClientAccessor is IChatClientAccessor accessor && accessor.ChatClient != null) { - chatClient = new WorkspaceChatClient(accessor.ChatClient); - _chatClientCache.TryAdd(workspace, chatClient); + return accessor.ChatClient; } - else - { - chatClient = await CreateAsync(workspace); - } - return chatClient; + + return await CreateAsync(workspace); } - public async virtual Task CreateAsync(string workspace) + public async virtual Task CreateAsync(string workspace) { - if (_chatClientCache.TryGetValue(workspace, out var chatClient)) - { - return chatClient; - } - var workspaceDefine = await WorkspaceDefinitionManager.GetAsync(workspace); await CheckWorkspaceStateAsync(workspaceDefine); - chatClient = await CreateChatClientAsync(workspaceDefine); - - _chatClientCache.TryAdd(workspace, chatClient); - - return chatClient; + return await CreateChatClientAsync(workspaceDefine); } - protected async virtual Task CreateChatClientAsync(WorkspaceDefinition workspace) + protected async virtual Task CreateChatClientAsync(WorkspaceDefinition workspace) { foreach (var provider in ChatClientProviderManager.Providers) { diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProvider.cs index 34f536698..c15b7875f 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProvider.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; using System; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -14,5 +15,5 @@ public abstract class ChatClientProvider : IChatClientProvider, ITransientDepend ServiceProvider = serviceProvider; } - public abstract Task CreateAsync(WorkspaceDefinition workspace); + public abstract Task CreateAsync(WorkspaceDefinition workspace); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs index 437197ebb..3ab6e99bb 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; +using Microsoft.Extensions.AI; using System.Threading.Tasks; namespace LINGYUN.Abp.AI; public interface IChatClientFactory { [NotNull] - Task CreateAsync(); + Task CreateAsync(); [NotNull] - Task CreateAsync(string workspace); + Task CreateAsync(string workspace); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs index ebde49eec..bdf5aeeb7 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Extensions.AI; using System.Threading.Tasks; namespace LINGYUN.Abp.AI; @@ -6,5 +7,5 @@ public interface IChatClientProvider { string Name { get; } - Task CreateAsync(WorkspaceDefinition workspace); + Task CreateAsync(WorkspaceDefinition workspace); } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IWorkspaceChatClient.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IWorkspaceChatClient.cs deleted file mode 100644 index 04592ec53..000000000 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IWorkspaceChatClient.cs +++ /dev/null @@ -1,8 +0,0 @@ -using LINGYUN.Abp.AI.Workspaces; -using Microsoft.Extensions.AI; - -namespace LINGYUN.Abp.AI; -public interface IWorkspaceChatClient : IChatClient -{ - WorkspaceDefinition? Workspace { get; } -} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs index a7067651a..5778e7921 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs @@ -1,7 +1,6 @@ using LINGYUN.Abp.AI.Workspaces; using Microsoft.SemanticKernel; using System; -using System.Collections.Concurrent; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.AI; @@ -10,9 +9,8 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.SimpleStateChecking; namespace LINGYUN.Abp.AI; -public class KernelFactory : IKernelFactory, ISingletonDependency +public class KernelFactory : IKernelFactory, IScopedDependency { - private readonly static ConcurrentDictionary _kernelCache = new(); protected ISimpleStateCheckerManager StateCheckerManager { get; } protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } protected IKernelProviderManager KernelProviderManager { get; } @@ -33,10 +31,6 @@ public class KernelFactory : IKernelFactory, ISingletonDependency public async virtual Task CreateAsync() { var workspace = WorkspaceNameAttribute.GetWorkspaceName(); - if (_kernelCache.TryGetValue(workspace, out var kernel)) - { - return kernel; - } var kernelAccessorType = typeof(IKernelAccessor<>).MakeGenericType(typeof(TWorkspace)); var kernelAccessor = ServiceProvider.GetService(kernelAccessorType); @@ -44,32 +38,18 @@ public class KernelFactory : IKernelFactory, ISingletonDependency kernelAccessor is IKernelAccessor accessor && accessor.Kernel != null) { - kernel = accessor.Kernel; - _kernelCache.TryAdd(workspace, kernel); + return accessor.Kernel; } - else - { - kernel = await CreateAsync(workspace); - } - return kernel; + return await CreateAsync(workspace); } public async virtual Task CreateAsync(string workspace) { - if (_kernelCache.TryGetValue(workspace, out var kernel)) - { - return kernel; - } - var workspaceDefine = await WorkspaceDefinitionManager.GetAsync(workspace); await CheckWorkspaceStateAsync(workspaceDefine); - kernel = await CreateKernelAsync(workspaceDefine); - - _kernelCache.TryAdd(workspace, kernel); - - return kernel; + return await CreateKernelAsync(workspaceDefine); } protected async virtual Task CreateKernelAsync(WorkspaceDefinition workspace) diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProvider.cs new file mode 100644 index 000000000..957f2a43c --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProvider.cs @@ -0,0 +1,19 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.SemanticKernel; +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI; +public abstract class KernelProvider : IKernelProvider, ITransientDependency +{ + public abstract string Name { get; } + + protected IServiceProvider ServiceProvider { get; } + protected KernelProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public abstract Task CreateAsync(WorkspaceDefinition workspace); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/IUserMessageStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/IUserMessageStore.cs new file mode 100644 index 000000000..f9d8baf54 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/IUserMessageStore.cs @@ -0,0 +1,11 @@ +using LINGYUN.Abp.AI.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Messages; +public interface IUserMessageStore +{ + Task SaveMessageAsync(UserMessage message); + + Task> GetHistoryMessagesAsync(string chatId); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/InMemoryUserMessageStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/InMemoryUserMessageStore.cs new file mode 100644 index 000000000..2492a51ae --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/InMemoryUserMessageStore.cs @@ -0,0 +1,40 @@ +using LINGYUN.Abp.AI.Models; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Messages; + +[Dependency(ServiceLifetime.Singleton, TryRegister = true)] +public class InMemoryUserMessageStore : IUserMessageStore +{ + private static readonly ConcurrentDictionary> _userMessageCache = new ConcurrentDictionary>(); + + public Task> GetHistoryMessagesAsync(string chatId) + { + if (_userMessageCache.TryGetValue(chatId, out var messages)) + { + return Task.FromResult(messages.Take(5)); + } + + return Task.FromResult>(Array.Empty()); + } + + public Task SaveMessageAsync(UserMessage message) + { + if (_userMessageCache.ContainsKey(message.ChatId)) + { + _userMessageCache[message.ChatId].Add(message); + } + else + { + _userMessageCache[message.ChatId] = new List() { message }; + } + + return Task.FromResult(message.Id); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/ChatMessageInfo.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/ChatMessageInfo.cs new file mode 100644 index 000000000..279c9ba9b --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/ChatMessageInfo.cs @@ -0,0 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LINGYUN.Abp.AI.Models; +internal class ChatMessageInfo +{ +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/MediaMessage.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/MediaMessage.cs new file mode 100644 index 000000000..f851d303f --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/MediaMessage.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Content; + +namespace LINGYUN.Abp.AI.Models; +public class MediaMessage +{ + public string Id { get; } + public IRemoteStreamContent Content { get; } + public MediaMessage(string id, IRemoteStreamContent content) + { + Id = id; + Content = content; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/TokenUsageInfo.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/TokenUsageInfo.cs new file mode 100644 index 000000000..cdac08043 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/TokenUsageInfo.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.AI.Models; +public class TokenUsageInfo +{ + public string Workspace { get; } + public long? InputTokenCount { get; set; } + public long? OutputTokenCount { get; set; } + public long? TotalTokenCount { get; set; } + public long? CachedInputTokenCount { get; set; } + public long? ReasoningTokenCount { get; set; } + public IDictionary? AdditionalCounts { get; set; } + public TokenUsageInfo(string workspace) + { + Workspace = workspace; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/UserMessage.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/UserMessage.cs new file mode 100644 index 000000000..f902b0c67 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/UserMessage.cs @@ -0,0 +1,37 @@ +using System.Linq; + +namespace LINGYUN.Abp.AI.Models; +public class UserMessage +{ + public string Id { get; } + public string ChatId { get; } + public string Content { get; } + public string Workspace { get; } + public string ReplyMessage { get; private set; } + public MediaMessage[]? Medias { get; private set; } + public UserMessage( + string workspace, + string id, + string chatId, + string content) + { + Workspace = workspace; + Id = id; + ChatId = chatId; + Content = content; + } + + public UserMessage WithMedia(MediaMessage media) + { + Medias ??= []; + Medias = Medias.Union([media]).ToArray(); + + return this; + } + + public UserMessage WithReply(string replyMessage) + { + ReplyMessage = replyMessage; + return this; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs index 6574c47a4..d13ac08a1 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs @@ -18,7 +18,7 @@ public class OpenAIChatClientProvider : ChatClientProvider { } - public override Task CreateAsync(WorkspaceDefinition workspace) + public override Task CreateAsync(WorkspaceDefinition workspace) { Check.NotNull(workspace, nameof(workspace)); Check.NotNullOrWhiteSpace(workspace.ApiKey, nameof(WorkspaceDefinition.ApiKey)); @@ -41,8 +41,6 @@ public class OpenAIChatClientProvider : ChatClientProvider .UseChatReducer() .Build(ServiceProvider); - IWorkspaceChatClient workspaceChatClient = new WorkspaceChatClient(chatClient, workspace); - - return Task.FromResult(workspaceChatClient); + return Task.FromResult(chatClient); } } diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIKernelProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIKernelProvider.cs new file mode 100644 index 000000000..2f1bb8f61 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIKernelProvider.cs @@ -0,0 +1,39 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.SemanticKernel; +using OpenAI; +using System; +using System.ClientModel; +using System.Threading.Tasks; +using Volo.Abp; + +namespace LINGYUN.Abp.AI; +public class OpenAIKernelProvider : KernelProvider +{ + private const string DefaultEndpoint = "https://api.openai.com/v1"; + + public const string ProviderName = "OpenAI"; + public override string Name => ProviderName; + public OpenAIKernelProvider(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + public override Task CreateAsync(WorkspaceDefinition workspace) + { + Check.NotNull(workspace, nameof(workspace)); + Check.NotNullOrWhiteSpace(workspace.ApiKey, nameof(WorkspaceDefinition.ApiKey)); + + var openAIClient = new OpenAIClient( + new ApiKeyCredential(workspace.ApiKey), + new OpenAIClientOptions + { + Endpoint = new Uri(workspace.ApiBaseUrl ?? DefaultEndpoint), + }); + + var kernel = Kernel.CreateBuilder() + .AddOpenAIChatClient(workspace.ModelName, openAIClient) + .Build(); + + return Task.FromResult(kernel); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/ITokenUsageStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/ITokenUsageStore.cs new file mode 100644 index 000000000..32b4d0718 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/ITokenUsageStore.cs @@ -0,0 +1,9 @@ +using LINGYUN.Abp.AI.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.AI.Tokens; +public interface ITokenUsageStore +{ + Task SaveTokenUsagesAsync(IEnumerable usageInfos); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/InMemoryTokenUsageStore.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/InMemoryTokenUsageStore.cs new file mode 100644 index 000000000..621f39c06 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/InMemoryTokenUsageStore.cs @@ -0,0 +1,50 @@ +using LINGYUN.Abp.AI.Models; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI.Tokens; + +[Dependency(ServiceLifetime.Singleton, TryRegister = true)] +public class InMemoryTokenUsageStore : ITokenUsageStore +{ + private static readonly ConcurrentDictionary _tokenUsageCache = new ConcurrentDictionary(); + + public Task SaveTokenUsagesAsync(IEnumerable usageInfos) + { + foreach (var usageInfo in usageInfos.GroupBy(x => x.Workspace)) + { + var tokenUsageInfo = new TokenUsageInfo(usageInfo.Key) + { + InputTokenCount = usageInfo.Sum(x => x.InputTokenCount), + TotalTokenCount = usageInfo.Sum(x => x.TotalTokenCount), + OutputTokenCount = usageInfo.Sum(x => x.OutputTokenCount), + ReasoningTokenCount = usageInfo.Sum(x => x.ReasoningTokenCount), + CachedInputTokenCount = usageInfo.Sum(x => x.CachedInputTokenCount), + }; + tokenUsageInfo.AdditionalCounts ??= new Dictionary(); + foreach (var item in usageInfo) + { + if (item.AdditionalCounts == null) + { + continue; + } + tokenUsageInfo.AdditionalCounts.AddIfNotContains(item.AdditionalCounts); + } + + if (!_tokenUsageCache.ContainsKey(usageInfo.Key)) + { + _tokenUsageCache.TryAdd(usageInfo.Key, tokenUsageInfo); + } + else + { + _tokenUsageCache[usageInfo.Key] = tokenUsageInfo; + } + } + + return Task.CompletedTask; + } +} 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 new file mode 100644 index 000000000..cc52e1fd7 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tools/GlobalFunctions.cs @@ -0,0 +1,11 @@ +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/WorkspaceChatClient.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/WorkspaceChatClient.cs deleted file mode 100644 index cc0563249..000000000 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/WorkspaceChatClient.cs +++ /dev/null @@ -1,69 +0,0 @@ -using LINGYUN.Abp.AI.Tools; -using LINGYUN.Abp.AI.Workspaces; -using Microsoft.Extensions.AI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.AI; -public class WorkspaceChatClient : IWorkspaceChatClient -{ - public IChatClient Client { get; } - public WorkspaceDefinition? Workspace { get; } - public WorkspaceChatClient( - IChatClient chatClient, - WorkspaceDefinition? workspace = null) - { - Workspace = workspace; - Client = chatClient; - } - - public virtual Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) - { - return Client.GetResponseAsync(GetChatMessages(messages), GetChatOptions(options), cancellationToken); - } - - public virtual IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) - { - return Client.GetStreamingResponseAsync(GetChatMessages(messages), GetChatOptions(options), cancellationToken); - } - - public virtual object? GetService(Type serviceType, object? serviceKey = null) - { - return Client.GetService(serviceType, serviceKey); - } - - public virtual void Dispose() - { - Client.Dispose(); - } - - protected virtual ChatOptions GetChatOptions(ChatOptions? options) - { - options ??= new ChatOptions(); - options.Instructions = Workspace?.Instructions; - options.Temperature ??= Workspace?.Temperature; - options.MaxOutputTokens ??= Workspace?.MaxOutputTokens; - options.FrequencyPenalty ??= Workspace?.FrequencyPenalty; - options.PresencePenalty ??= Workspace?.PresencePenalty; - - return options; - } - - protected virtual IEnumerable GetChatMessages(IEnumerable messages) - { - if (Workspace?.SystemPrompt?.IsNullOrWhiteSpace() == false && - !messages.Any(msg => msg.Role == ChatRole.System)) - { - // 加入系统提示词 - messages = new List - { - new ChatMessage(ChatRole.System, Workspace.SystemPrompt) - }.Union(messages); - } - - return messages; - } -}