27 changed files with 517 additions and 307 deletions
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
|
||||
} |
|
||||
@ -1,12 +1,13 @@ |
|||||
using JetBrains.Annotations; |
using JetBrains.Annotations; |
||||
|
using Microsoft.Extensions.AI; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
|
||||
namespace LINGYUN.Abp.AI; |
namespace LINGYUN.Abp.AI; |
||||
public interface IChatClientFactory |
public interface IChatClientFactory |
||||
{ |
{ |
||||
[NotNull] |
[NotNull] |
||||
Task<IWorkspaceChatClient> CreateAsync<TWorkspace>(); |
Task<IChatClient> CreateAsync<TWorkspace>(); |
||||
|
|
||||
[NotNull] |
[NotNull] |
||||
Task<IWorkspaceChatClient> CreateAsync(string workspace); |
Task<IChatClient> CreateAsync(string workspace); |
||||
} |
} |
||||
|
|||||
@ -1,8 +0,0 @@ |
|||||
using LINGYUN.Abp.AI.Workspaces; |
|
||||
using Microsoft.Extensions.AI; |
|
||||
|
|
||||
namespace LINGYUN.Abp.AI; |
|
||||
public interface IWorkspaceChatClient : IChatClient |
|
||||
{ |
|
||||
WorkspaceDefinition? Workspace { get; } |
|
||||
} |
|
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AI.Models; |
||||
|
internal class ChatMessageInfo |
||||
|
{ |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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"); |
||||
|
} |
||||
@ -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…
Reference in new issue