Browse Source

feat(ai): Add simple conversation service

pull/1421/head
colin 3 weeks ago
parent
commit
1d35fc6628
  1. 110
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentFactory.cs
  2. 91
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/AgentService.cs
  3. 140
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs
  4. 13
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentFactory.cs
  5. 8
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IAgentService.cs
  6. 12
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs
  7. 38
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/WorkspaceAIAgent.cs
  8. 1
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs
  9. 35
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientFactory.cs
  10. 3
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/ChatClientProvider.cs
  11. 5
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientFactory.cs
  12. 3
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IChatClientProvider.cs
  13. 8
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IWorkspaceChatClient.cs
  14. 28
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelFactory.cs
  15. 19
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/KernelProvider.cs
  16. 11
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/IUserMessageStore.cs
  17. 40
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Messages/InMemoryUserMessageStore.cs
  18. 8
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/ChatMessageInfo.cs
  19. 13
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/MediaMessage.cs
  20. 17
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/TokenUsageInfo.cs
  21. 37
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Models/UserMessage.cs
  22. 6
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs
  23. 39
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIKernelProvider.cs
  24. 9
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/ITokenUsageStore.cs
  25. 50
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tokens/InMemoryTokenUsageStore.cs
  26. 11
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/Tools/GlobalFunctions.cs
  27. 69
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/WorkspaceChatClient.cs

110
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<WorkspaceDefinition> StateCheckerManager { get; }
public AgentFactory(
IServiceProvider serviceProvider,
IChatClientFactory chatClientFactory,
IStringLocalizerFactory stringLocalizerFactory,
IWorkspaceDefinitionManager workspaceDefinitionManager,
ISimpleStateCheckerManager<WorkspaceDefinition> stateCheckerManager)
{
ServiceProvider = serviceProvider;
ChatClientFactory = chatClientFactory;
StringLocalizerFactory = stringLocalizerFactory;
WorkspaceDefinitionManager = workspaceDefinitionManager;
StateCheckerManager = stateCheckerManager;
}
public async virtual Task<AIAgent> CreateAsync<TWorkspace>()
{
var workspace = WorkspaceNameAttribute.GetWorkspaceName<TWorkspace>();
var chatClient = await ChatClientFactory.CreateAsync<TWorkspace>();
var workspaceDefine = await WorkspaceDefinitionManager.GetOrNullAsync(workspace);
if (workspaceDefine != null)
{
await CheckWorkspaceStateAsync(workspaceDefine);
}
return await CreateAgentAsync(chatClient, workspaceDefine);
}
public async virtual Task<AIAgent> 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<AIAgent> 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<AITool[]> GetAgentToolsAsync(WorkspaceDefinition? workspace)
{
return Task.FromResult<AITool[]>([]);
}
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);
}
}
}

91
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<string> 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<IEnumerable<ChatMessage>> BuildChatMessages(UserMessage message)
{
var messages = new List<ChatMessage>();
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<UsageContent>()
.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);
}
}
}

140
aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/ChatClientAgentFactory.cs

@ -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<string, WorkspaceAIAgent> _chatClientAgentCache = new();
protected IServiceProvider ServiceProvider { get; }
protected IChatClientFactory ChatClientFactory { get; }
protected IStringLocalizerFactory StringLocalizerFactory { get; }
protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; }
protected ISimpleStateCheckerManager<WorkspaceDefinition> StateCheckerManager { get; }
public ChatClientAgentFactory(
IServiceProvider serviceProvider,
IChatClientFactory chatClientFactory,
IStringLocalizerFactory stringLocalizerFactory,
IWorkspaceDefinitionManager workspaceDefinitionManager,
ISimpleStateCheckerManager<WorkspaceDefinition> stateCheckerManager)
{
ServiceProvider = serviceProvider;
ChatClientFactory = chatClientFactory;
StringLocalizerFactory = stringLocalizerFactory;
WorkspaceDefinitionManager = workspaceDefinitionManager;
StateCheckerManager = stateCheckerManager;
}
public async virtual Task<WorkspaceAIAgent> CreateAsync<TWorkspace>()
{
var workspace = WorkspaceNameAttribute.GetWorkspaceName<TWorkspace>();
if (_chatClientAgentCache.TryGetValue(workspace, out var chatClientAgent))
{
return chatClientAgent;
}
var chatClient = await ChatClientFactory.CreateAsync<TWorkspace>();
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<WorkspaceAIAgent> 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);
}
}
}

13
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<AIAgent> CreateAsync<TWorkspace>();
[NotNull]
Task<AIAgent> CreateAsync(string workspace);
}

8
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<string> SendMessageAsync(UserMessage message);
}

12
aspnet-core/modules/ai/LINGYUN.Abp.AI.Agent/LINGYUN/Abp/AI/Agent/IChatClientAgentFactory.cs

@ -1,12 +0,0 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
namespace LINGYUN.Abp.AI.Agent;
public interface IChatClientAgentFactory
{
[NotNull]
Task<WorkspaceAIAgent> CreateAsync<TWorkspace>();
[NotNull]
Task<WorkspaceAIAgent> CreateAsync(string workspace);
}

38
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<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> 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<AgentRunResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> 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<ChatMessage> GetChatMessages(IEnumerable<ChatMessage> messages)
{
if (Workspace?.SystemPrompt?.IsNullOrWhiteSpace() == false &&
!messages.Any(msg => msg.Role == ChatRole.System))
var unionMessages = new List<ChatMessage>();
if (Workspace != null)
{
// 加入系统提示词
messages = new List<ChatMessage>
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);
}
}

1
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/AbpAICoreModule.cs

@ -38,6 +38,7 @@ public class AbpAICoreModule : AbpModule
Configure<AbpAICoreOptions>(options =>
{
options.ChatClientProviders.Add<OpenAIChatClientProvider>();
options.KernelProviders.Add<OpenAIKernelProvider>();
});
Configure<AbpExceptionLocalizationOptions>(options =>

35
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<string, IWorkspaceChatClient> _chatClientCache = new();
protected ISimpleStateCheckerManager<WorkspaceDefinition> 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<IWorkspaceChatClient> CreateAsync<TWorkspace>()
public async virtual Task<IChatClient> CreateAsync<TWorkspace>()
{
var workspace = WorkspaceNameAttribute.GetWorkspaceName<TWorkspace>();
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<IWorkspaceChatClient> CreateAsync(string workspace)
public async virtual Task<IChatClient> 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<IWorkspaceChatClient> CreateChatClientAsync(WorkspaceDefinition workspace)
protected async virtual Task<IChatClient> CreateChatClientAsync(WorkspaceDefinition workspace)
{
foreach (var provider in ChatClientProviderManager.Providers)
{

3
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<IWorkspaceChatClient> CreateAsync(WorkspaceDefinition workspace);
public abstract Task<IChatClient> CreateAsync(WorkspaceDefinition workspace);
}

5
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<IWorkspaceChatClient> CreateAsync<TWorkspace>();
Task<IChatClient> CreateAsync<TWorkspace>();
[NotNull]
Task<IWorkspaceChatClient> CreateAsync(string workspace);
Task<IChatClient> CreateAsync(string workspace);
}

3
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<IWorkspaceChatClient> CreateAsync(WorkspaceDefinition workspace);
Task<IChatClient> CreateAsync(WorkspaceDefinition workspace);
}

8
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/IWorkspaceChatClient.cs

@ -1,8 +0,0 @@
using LINGYUN.Abp.AI.Workspaces;
using Microsoft.Extensions.AI;
namespace LINGYUN.Abp.AI;
public interface IWorkspaceChatClient : IChatClient
{
WorkspaceDefinition? Workspace { get; }
}

28
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<string, Kernel> _kernelCache = new();
protected ISimpleStateCheckerManager<WorkspaceDefinition> StateCheckerManager { get; }
protected IWorkspaceDefinitionManager WorkspaceDefinitionManager { get; }
protected IKernelProviderManager KernelProviderManager { get; }
@ -33,10 +31,6 @@ public class KernelFactory : IKernelFactory, ISingletonDependency
public async virtual Task<Kernel> CreateAsync<TWorkspace>()
{
var workspace = WorkspaceNameAttribute.GetWorkspaceName<TWorkspace>();
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<Kernel> 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<Kernel> CreateKernelAsync(WorkspaceDefinition workspace)

19
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<Kernel> CreateAsync(WorkspaceDefinition workspace);
}

11
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<string> SaveMessageAsync(UserMessage message);
Task<IEnumerable<UserMessage>> GetHistoryMessagesAsync(string chatId);
}

40
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<string, List<UserMessage>> _userMessageCache = new ConcurrentDictionary<string, List<UserMessage>>();
public Task<IEnumerable<UserMessage>> GetHistoryMessagesAsync(string chatId)
{
if (_userMessageCache.TryGetValue(chatId, out var messages))
{
return Task.FromResult(messages.Take(5));
}
return Task.FromResult<IEnumerable<UserMessage>>(Array.Empty<UserMessage>());
}
public Task<string> SaveMessageAsync(UserMessage message)
{
if (_userMessageCache.ContainsKey(message.ChatId))
{
_userMessageCache[message.ChatId].Add(message);
}
else
{
_userMessageCache[message.ChatId] = new List<UserMessage>() { message };
}
return Task.FromResult(message.Id);
}
}

8
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
{
}

13
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;
}
}

17
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<string, long>? AdditionalCounts { get; set; }
public TokenUsageInfo(string workspace)
{
Workspace = workspace;
}
}

37
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;
}
}

6
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/OpenAIChatClientProvider.cs

@ -18,7 +18,7 @@ public class OpenAIChatClientProvider : ChatClientProvider
{
}
public override Task<IWorkspaceChatClient> CreateAsync(WorkspaceDefinition workspace)
public override Task<IChatClient> 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);
}
}

39
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<Kernel> 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);
}
}

9
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<TokenUsageInfo> usageInfos);
}

50
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<string, TokenUsageInfo> _tokenUsageCache = new ConcurrentDictionary<string, TokenUsageInfo>();
public Task SaveTokenUsagesAsync(IEnumerable<TokenUsageInfo> 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<string, long>();
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;
}
}

11
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");
}

69
aspnet-core/modules/ai/LINGYUN.Abp.AI.Core/LINGYUN/Abp/AI/WorkspaceChatClient.cs

@ -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<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
{
return Client.GetResponseAsync(GetChatMessages(messages), GetChatOptions(options), cancellationToken);
}
public virtual IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> 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<ChatMessage> GetChatMessages(IEnumerable<ChatMessage> messages)
{
if (Workspace?.SystemPrompt?.IsNullOrWhiteSpace() == false &&
!messages.Any(msg => msg.Role == ChatRole.System))
{
// 加入系统提示词
messages = new List<ChatMessage>
{
new ChatMessage(ChatRole.System, Workspace.SystemPrompt)
}.Union(messages);
}
return messages;
}
}
Loading…
Cancel
Save