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 index 5f233f4e2..106e40768 100644 --- 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 @@ -2,6 +2,8 @@ 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; @@ -12,25 +14,27 @@ using Volo.Abp.SimpleStateChecking; namespace LINGYUN.Abp.AI.Agent; public class ChatClientAgentFactory : IChatClientAgentFactory, ISingletonDependency { - private readonly static ConcurrentDictionary _chatClientAgentCache = new(); - + 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() + public async virtual Task CreateAsync() { var workspace = WorkspaceNameAttribute.GetWorkspaceName(); if (_chatClientAgentCache.TryGetValue(workspace, out var chatClientAgent)) @@ -53,17 +57,33 @@ public class ChatClientAgentFactory : IChatClientAgentFactory, ISingletonDepende description = workspaceDefine.Description.Localize(StringLocalizerFactory); } - chatClientAgent = chatClient.CreateAIAgent( - instructions: workspaceDefine?.SystemPrompt, - name: workspaceDefine?.Name, - description: description); + 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) + public async virtual Task CreateAsync(string workspace) { if (_chatClientAgentCache.TryGetValue(workspace, out var chatClientAgent)) { @@ -81,10 +101,26 @@ public class ChatClientAgentFactory : IChatClientAgentFactory, ISingletonDepende description = workspaceDefine.Description.Localize(StringLocalizerFactory); } - chatClientAgent = chatClient.CreateAIAgent( - instructions: workspaceDefine.SystemPrompt, - name: workspaceDefine.Name, - description: description); + 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); 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 index 0d15511fd..cf7f938ef 100644 --- 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 @@ -1,13 +1,12 @@ using JetBrains.Annotations; -using Microsoft.Agents.AI; using System.Threading.Tasks; namespace LINGYUN.Abp.AI.Agent; public interface IChatClientAgentFactory { [NotNull] - Task CreateAsync(); + Task CreateAsync(); [NotNull] - Task CreateAsync(string workspace); + 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 new file mode 100644 index 000000000..41539ec09 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/WorkspaceAIAgent.cs @@ -0,0 +1,59 @@ +using LINGYUN.Abp.AI.Workspaces; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +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) + { + InnerAIAgent = innerAIAgent; + Workspace = workspace; + } + + public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + { + return InnerAIAgent.DeserializeThread(serializedThread, jsonSerializerOptions); + } + + public override AgentThread GetNewThread() + { + return InnerAIAgent.GetNewThread(); + } + + protected override Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) + { + return InnerAIAgent.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); + } + + 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; + } +} 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 c9dd2e74c..04f59d3e3 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 @@ -12,7 +12,7 @@ using Volo.Abp.SimpleStateChecking; namespace LINGYUN.Abp.AI; public class ChatClientFactory : IChatClientFactory, ISingletonDependency { - private readonly static ConcurrentDictionary _chatClientCache = new(); + private readonly static ConcurrentDictionary _chatClientCache = new(); protected ISimpleStateCheckerManager StateCheckerManager { get; } protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; } protected IChatClientProviderManager ChatClientProviderManager { get; } @@ -30,7 +30,7 @@ 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)) @@ -44,7 +44,7 @@ public class ChatClientFactory : IChatClientFactory, ISingletonDependency chatClientAccessor is IChatClientAccessor accessor && accessor.ChatClient != null) { - chatClient = accessor.ChatClient; + chatClient = new WorkspaceChatClient(accessor.ChatClient); _chatClientCache.TryAdd(workspace, chatClient); } else @@ -54,7 +54,7 @@ public class ChatClientFactory : IChatClientFactory, ISingletonDependency return chatClient; } - public async virtual Task CreateAsync(string workspace) + public async virtual Task CreateAsync(string workspace) { if (_chatClientCache.TryGetValue(workspace, out var chatClient)) { @@ -72,7 +72,7 @@ public class ChatClientFactory : IChatClientFactory, ISingletonDependency return chatClient; } - 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 new file mode 100644 index 000000000..34f536698 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProvider.cs @@ -0,0 +1,18 @@ +using LINGYUN.Abp.AI.Workspaces; +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.AI; +public abstract class ChatClientProvider : IChatClientProvider, ITransientDependency +{ + public abstract string Name { get; } + + protected IServiceProvider ServiceProvider { get; } + protected ChatClientProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + 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 3ab6e99bb..437197ebb 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,13 +1,12 @@ 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 bdf5aeeb7..ebde49eec 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,5 +1,4 @@ using LINGYUN.Abp.AI.Workspaces; -using Microsoft.Extensions.AI; using System.Threading.Tasks; namespace LINGYUN.Abp.AI; @@ -7,5 +6,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 new file mode 100644 index 000000000..04592ec53 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IWorkspaceChatClient.cs @@ -0,0 +1,8 @@ +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/OpenAIChatClientProvider.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs index f8d145334..6574c47a4 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 @@ -5,17 +5,20 @@ using System; using System.ClientModel; using System.Threading.Tasks; using Volo.Abp; -using Volo.Abp.DependencyInjection; namespace LINGYUN.Abp.AI; -public class OpenAIChatClientProvider : IChatClientProvider, ITransientDependency +public class OpenAIChatClientProvider : ChatClientProvider { private const string DefaultEndpoint = "https://api.openai.com/v1"; public const string ProviderName = "OpenAI"; - public virtual string Name => ProviderName; + public override string Name => ProviderName; + public OpenAIChatClientProvider(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } - public virtual Task CreateAsync(WorkspaceDefinition workspace) + public override Task CreateAsync(WorkspaceDefinition workspace) { Check.NotNull(workspace, nameof(workspace)); Check.NotNullOrWhiteSpace(workspace.ApiKey, nameof(WorkspaceDefinition.ApiKey)); @@ -29,8 +32,17 @@ public class OpenAIChatClientProvider : IChatClientProvider, ITransientDependenc var chatClient = openAIClient .GetChatClient(workspace.ModelName) - .AsIChatClient(); + .AsIChatClient() + .AsBuilder() + .UseLogging() + .UseOpenTelemetry() + .UseFunctionInvocation() + .UseDistributedCache() + .UseChatReducer() + .Build(ServiceProvider); + + IWorkspaceChatClient workspaceChatClient = new WorkspaceChatClient(chatClient, workspace); - return Task.FromResult(chatClient); + return Task.FromResult(workspaceChatClient); } } 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 new file mode 100644 index 000000000..cc0563249 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/WorkspaceChatClient.cs @@ -0,0 +1,69 @@ +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; + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs index 56f37e127..2f3288616 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Workspaces/WorkspaceDefinition.cs @@ -41,16 +41,84 @@ public class WorkspaceDefinition : IHasSimpleStateCheckers /// /// API 身份验证密钥 /// - public string? ApiKey { get; set; } + public string? ApiKey { get; private set; } /// /// 自定义端点 URL /// - public string? ApiBaseUrl { get; set; } + public string? ApiBaseUrl { get; private set; } /// /// 系统提示词 /// public string? SystemPrompt { get; set; } /// + /// 附加系统提示词 + /// + public string? Instructions { get; set; } + /// + /// 聊天回复时所依据的温度值, 为空时由模型提供者决定默认值 + /// + /// + /// 范围在 0 到 2 之间, 数值越高(比如 0.8)会使输出更加随机,而数值越低(比如 0.2)则会使输出更加集中且更具确定性 + /// + public float? Temperature { + get => _temperature; + set { + if (value.HasValue) + { + _temperature = Check.Range(value.Value, nameof(value), 0, 2); + } + else + { + _temperature = value; + } + } + } + private float? _temperature; + /// + /// 限制一次请求中模型生成 completion 的最大 token 数 + /// + public int? MaxOutputTokens { get; set; } + /// + /// 介于 -2.0 和 2.0 之间的数字 + /// + /// + /// 如果该值为正,那么新 token 会根据其在已有文本中的出现频率受到相应的惩罚,降低模型重复相同内容的可能性 + /// + public float? FrequencyPenalty { + get => _frequencyPenalty; + set { + if (value.HasValue) + { + _frequencyPenalty = Check.Range(value.Value, nameof(value), -2, 2); + } + else + { + _frequencyPenalty = value; + } + } + } + private float? _frequencyPenalty; + /// + /// 介于 -2.0 和 2.0 之间的数字 + /// + /// + /// 如果该值为正,那么新 token 会根据其是否已在已有文本中出现受到相应的惩罚,从而增加模型谈论新主题的可能性 + /// + public float? PresencePenalty { + get => _presencePenalty; + set { + if (value.HasValue) + { + _presencePenalty = Check.Range(value.Value, nameof(value), -2, 2); + } + else + { + _presencePenalty = value; + } + } + } + private float? _presencePenalty; + /// /// 启用/禁用工作区 /// public bool IsEnabled { get; set; } @@ -65,7 +133,13 @@ public class WorkspaceDefinition : IHasSimpleStateCheckers string provider, string modelName, ILocalizableString displayName, - ILocalizableString? description = null) + ILocalizableString? description = null, + string? systemPrompt = null, + string? instructions = null, + float? temperature = null, + int? maxOutputTokens = null, + float? frequencyPenalty = null, + float? presencePenalty = null) { Name = name; Provider = provider; @@ -73,6 +147,12 @@ public class WorkspaceDefinition : IHasSimpleStateCheckers _displayName = displayName; _displayName = displayName; Description = description; + SystemPrompt = systemPrompt; + Instructions = instructions; + Temperature = temperature; + MaxOutputTokens = maxOutputTokens; + FrequencyPenalty = frequencyPenalty; + PresencePenalty = presencePenalty; IsEnabled = true; Properties = new Dictionary();