Browse Source

feat(ai): Implement dynamic AI tools store

pull/1457/head
colin 6 months ago
parent
commit
13a4578a75
  1. 5
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs
  2. 2
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs
  3. 5
      aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs
  4. 1
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj
  5. 6
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AIManagementOptions.cs
  6. 12
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs
  7. 80
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecord.cs
  8. 67
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionSerializer.cs
  9. 138
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDynamicInitializer.cs
  10. 160
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStore.cs
  11. 57
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreCacheInvalidator.cs
  12. 79
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreInMemoryCache.cs
  13. 11
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionSerializer.cs
  14. 21
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IDynamicAIToolDefinitionStoreInMemoryCache.cs
  15. 7
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IStaticAIToolSaver.cs
  16. 241
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/StaticAIToolSaver.cs
  17. 2
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IDynamicWorkspaceDefinitionStoreInMemoryCache.cs

5
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolDefinition.cs

@ -26,6 +26,10 @@ public class AIToolDefinition : IHasSimpleStateCheckers<AIToolDefinition>
/// </summary>
[NotNull]
public Dictionary<string, object?> Properties { get; }
/// <summary>
/// 启用/禁用工具
/// </summary>
public bool IsEnabled { get; set; }
public List<ISimpleStateChecker<AIToolDefinition>> StateCheckers { get; }
@ -38,6 +42,7 @@ public class AIToolDefinition : IHasSimpleStateCheckers<AIToolDefinition>
Provider = provider;
Description = description;
IsEnabled = true;
Properties = new Dictionary<string, object?>();
StateCheckers = new List<ISimpleStateChecker<AIToolDefinition>>();
}

2
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AIToolFactory.cs

@ -44,7 +44,7 @@ public class AIToolFactory : IAIToolFactory, IScopedDependency
foreach (var toolDefine in toolDefines)
{
if (await StateCheckerManager.IsEnabledAsync(toolDefine))
if (toolDefine.IsEnabled && await StateCheckerManager.IsEnabledAsync(toolDefine))
{
aiTools.AddRange(await CreateTool(toolDefine));
}

5
aspnet-core/modules/ai/LINGYUN.Abp.AI.Tools/LINGYUN/Abp/AI/Tools/AbpAIToolsOptions.cs

@ -1,13 +1,16 @@
using Volo.Abp.Collections;
using System.Collections.Generic;
using Volo.Abp.Collections;
namespace LINGYUN.Abp.AI.Tools;
public class AbpAIToolsOptions
{
public ITypeList<IAIToolDefinitionProvider> DefinitionProviders { get; }
public ITypeList<IAIToolProvider> AIToolProviders { get; }
public HashSet<string> DeletedAITools { get; }
public AbpAIToolsOptions()
{
DefinitionProviders = new TypeList<IAIToolDefinitionProvider>();
AIToolProviders = new TypeList<IAIToolProvider>();
DeletedAITools = new HashSet<string>();
}
}

1
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj

@ -24,6 +24,7 @@
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.AI.Agent\LINGYUN.Abp.AI.Agent.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.AI.Tools\LINGYUN.Abp.AI.Tools.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.AIManagement.Domain.Shared\LINGYUN.Abp.AIManagement.Domain.Shared.csproj" />
</ItemGroup>

6
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AIManagementOptions.cs

@ -1,10 +1,10 @@
using System;
namespace LINGYUN.Abp.AIManagement;
namespace LINGYUN.Abp.AIManagement;
public class AIManagementOptions
{
public bool IsDynamicWorkspaceStoreEnabled { get; set; }
public bool SaveStaticWorkspacesToDatabase { get; set; }
public bool IsDynamicAIToolStoreEnabled { get; set; }
public bool SaveStaticAIToolsToDatabase { get; set; }
public AIManagementOptions()
{

12
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs

@ -1,6 +1,8 @@
using LINGYUN.Abp.AI.Agent;
using LINGYUN.Abp.AI.Localization;
using LINGYUN.Abp.AI.Tools;
using LINGYUN.Abp.AIManagement.Localization;
using LINGYUN.Abp.AIManagement.Tools;
using LINGYUN.Abp.AIManagement.Workspaces;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
@ -20,6 +22,7 @@ namespace LINGYUN.Abp.AIManagement;
[DependsOn(
typeof(AbpAIManagementDomainSharedModule),
typeof(AbpAIAgentModule),
typeof(AbpAIToolsModule),
typeof(AbpCachingModule),
typeof(AbpMapperlyModule),
typeof(AbpDddDomainModule))]
@ -57,8 +60,13 @@ public class AbpAIManagementDomainModule : AbpModule
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var initializer = rootServiceProvider.GetRequiredService<WorkspaceDynamicInitializer>();
await initializer.InitializeAsync(true, _cancellationTokenSource.Token);
var workspaceDynamicInitializer = rootServiceProvider.GetRequiredService<WorkspaceDynamicInitializer>();
var aIToolDynamicInitializer = rootServiceProvider.GetRequiredService<AIToolDynamicInitializer>();
await Task.WhenAll([
workspaceDynamicInitializer.InitializeAsync(true, _cancellationTokenSource.Token),
aIToolDynamicInitializer.InitializeAsync(true, _cancellationTokenSource.Token)
]);
}
public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)

80
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionRecord.cs

@ -1,6 +1,7 @@
using JetBrains.Annotations;
using System;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Auditing;
namespace LINGYUN.Abp.AIManagement.Tools;
@ -23,12 +24,89 @@ public class AIToolDefinitionRecord : AuditedAggregateRoot<Guid>
}
public AIToolDefinitionRecord(
[NotNull] Guid id,
[NotNull] string name,
[NotNull] string provider,
[CanBeNull] string? description = null)
[CanBeNull] string? description = null,
[CanBeNull] string? stateCheckers = null)
: base(id)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), AIToolDefinitionRecordConsts.MaxNameLength);
Provider = Check.NotNullOrWhiteSpace(provider, nameof(provider), AIToolDefinitionRecordConsts.MaxProviderLength);
Description = Check.Length(description, nameof(description), AIToolDefinitionRecordConsts.MaxDescriptionLength);
StateCheckers = Check.Length(stateCheckers, nameof(stateCheckers), AIToolDefinitionRecordConsts.MaxStateCheckersLength);
}
public bool HasSameData(AIToolDefinitionRecord otherAITool)
{
if (Name != otherAITool.Name)
{
return false;
}
if (Provider != otherAITool.Provider)
{
return false;
}
if (Description != otherAITool.Description)
{
return false;
}
if (IsEnabled != otherAITool.IsEnabled)
{
return false;
}
if (StateCheckers != otherAITool.StateCheckers)
{
return false;
}
if (!this.HasSameExtraProperties(otherAITool))
{
return false;
}
return true;
}
public void Patch(AIToolDefinitionRecord otherAITool)
{
if (Name != otherAITool.Name)
{
Name = otherAITool.Name;
}
if (Provider != otherAITool.Provider)
{
Provider = otherAITool.Provider;
}
if (Description != otherAITool.Description)
{
Description = otherAITool.Description;
}
if (IsEnabled != otherAITool.IsEnabled)
{
IsEnabled = otherAITool.IsEnabled;
}
if (StateCheckers != otherAITool.StateCheckers)
{
StateCheckers = otherAITool.StateCheckers;
}
if (!this.HasSameExtraProperties(otherAITool))
{
ExtraProperties.Clear();
foreach (var property in otherAITool.ExtraProperties)
{
ExtraProperties.Add(property.Key, property.Value);
}
}
}
}

67
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDefinitionSerializer.cs

@ -0,0 +1,67 @@
using LINGYUN.Abp.AI.Tools;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Localization;
using Volo.Abp.SimpleStateChecking;
namespace LINGYUN.Abp.AIManagement.Tools;
public class AIToolDefinitionSerializer : IAIToolDefinitionSerializer, ITransientDependency
{
protected IGuidGenerator GuidGenerator { get; }
protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; }
protected ILocalizableStringSerializer LocalizableStringSerializer { get; }
public AIToolDefinitionSerializer(
IGuidGenerator guidGenerator,
ISimpleStateCheckerSerializer stateCheckerSerializer,
ILocalizableStringSerializer localizableStringSerializer)
{
GuidGenerator = guidGenerator;
StateCheckerSerializer = stateCheckerSerializer;
LocalizableStringSerializer = localizableStringSerializer;
}
public async virtual Task<AIToolDefinitionRecord[]> SerializeAsync(IEnumerable<AIToolDefinition> definitions)
{
var records = new List<AIToolDefinitionRecord>();
foreach (var aiToolDef in definitions)
{
records.Add(await SerializeAsync(aiToolDef));
}
return records.ToArray();
}
public virtual Task<AIToolDefinitionRecord> SerializeAsync(AIToolDefinition definition)
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
var aiToolRecord = new AIToolDefinitionRecord(
GuidGenerator.Create(),
definition.Name,
definition.Provider,
definition.Description != null ? LocalizableStringSerializer.Serialize(definition.Description) : null,
SerializeStateCheckers(definition.StateCheckers));
foreach (var property in definition.Properties)
{
aiToolRecord.SetProperty(property.Key, property.Value);
}
aiToolRecord.IsEnabled = definition.IsEnabled;
aiToolRecord.IsSystem = true;
return Task.FromResult(aiToolRecord);
}
}
protected virtual string? SerializeStateCheckers(List<ISimpleStateChecker<AIToolDefinition>> stateCheckers)
{
return StateCheckerSerializer.Serialize(stateCheckers);
}
}

138
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/AIToolDynamicInitializer.cs

@ -0,0 +1,138 @@
using LINGYUN.Abp.AI.Tools;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Polly;
using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.AIManagement.Tools;
public class AIToolDynamicInitializer : ITransientDependency
{
public ILogger<AIToolDynamicInitializer> Logger { get; set; }
protected IServiceProvider ServiceProvider { get; }
public AIToolDynamicInitializer(IServiceProvider serviceProvider)
{
Logger = NullLogger<AIToolDynamicInitializer>.Instance;
ServiceProvider = serviceProvider;
}
public virtual Task InitializeAsync(bool runInBackground, CancellationToken cancellationToken = default)
{
var options = ServiceProvider.GetRequiredService<IOptions<AIManagementOptions>>().Value;
if (!options.SaveStaticAIToolsToDatabase && !options.IsDynamicAIToolStoreEnabled)
{
return Task.CompletedTask;
}
if (runInBackground)
{
var applicationLifetime = ServiceProvider.GetService<IHostApplicationLifetime>();
Task.Run(async () =>
{
if (cancellationToken == default && applicationLifetime?.ApplicationStopping != null)
{
cancellationToken = applicationLifetime.ApplicationStopping;
}
await ExecuteInitializationAsync(options, cancellationToken);
}, cancellationToken);
return Task.CompletedTask;
}
return ExecuteInitializationAsync(options, cancellationToken);
}
protected virtual async Task ExecuteInitializationAsync(AIManagementOptions options, CancellationToken cancellationToken)
{
try
{
var cancellationTokenProvider = ServiceProvider.GetRequiredService<ICancellationTokenProvider>();
using (cancellationTokenProvider.Use(cancellationToken))
{
if (cancellationTokenProvider.Token.IsCancellationRequested)
{
return;
}
await SaveStaticAIToolsToDatabaseAsync(options, cancellationToken);
if (cancellationTokenProvider.Token.IsCancellationRequested)
{
return;
}
await PreCacheDynamicAIToolsAsync(options);
}
}
catch
{
// No need to log here since inner calls log
}
}
protected virtual async Task SaveStaticAIToolsToDatabaseAsync(
AIManagementOptions options,
CancellationToken cancellationToken)
{
if (!options.SaveStaticAIToolsToDatabase)
{
return;
}
var staticAIToolSaver = ServiceProvider.GetRequiredService<IStaticAIToolSaver>();
await Policy
.Handle<Exception>(ex => ex is not OperationCanceledException)
.WaitAndRetryAsync(
8,
retryAttempt => TimeSpan.FromSeconds(
Volo.Abp.RandomHelper.GetRandom(
(int)Math.Pow(2, retryAttempt) * 8,
(int)Math.Pow(2, retryAttempt) * 12)
)
)
.ExecuteAsync(async _ =>
{
try
{
await staticAIToolSaver.SaveAsync();
}
catch (Exception ex)
{
Logger.LogException(ex);
throw; // Polly will catch it
}
}, cancellationToken);
}
protected virtual async Task PreCacheDynamicAIToolsAsync(AIManagementOptions options)
{
if (!options.IsDynamicAIToolStoreEnabled)
{
return;
}
var dynamicAIToolDefinitionStore = ServiceProvider.GetRequiredService<IDynamicAIToolDefinitionStore>();
try
{
// Pre-cache AITools, so first request doesn't wait
await dynamicAIToolDefinitionStore.GetAllAsync();
}
catch (Exception ex)
{
Logger.LogException(ex);
throw; // It will be cached in Initialize()
}
}
}

160
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStore.cs

@ -0,0 +1,160 @@
using JetBrains.Annotations;
using LINGYUN.Abp.AI.Tools;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.AIManagement.Tools;
[Dependency(ReplaceServices = true)]
public class DynamicAIToolDefinitionStore : IDynamicAIToolDefinitionStore, ITransientDependency
{
protected IAIToolDefinitionRecordRepository AIToolDefinitionRecordRepository { get; }
protected IAIToolDefinitionSerializer AIToolDefinitionSerializer { get; }
protected IDynamicAIToolDefinitionStoreInMemoryCache StoreCache { get; }
protected IDistributedCache DistributedCache { get; }
protected IAbpDistributedLock DistributedLock { get; }
public AIManagementOptions AIManagementOptions { get; }
protected AbpDistributedCacheOptions CacheOptions { get; }
public DynamicAIToolDefinitionStore(
IAIToolDefinitionRecordRepository aiToolDefinitionRecordRepository,
IAIToolDefinitionSerializer aiToolDefinitionSerializer,
IDynamicAIToolDefinitionStoreInMemoryCache storeCache,
IDistributedCache distributedCache,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IOptions<AIManagementOptions> aiManagementOptions,
IAbpDistributedLock distributedLock)
{
AIToolDefinitionRecordRepository = aiToolDefinitionRecordRepository;
AIToolDefinitionSerializer = aiToolDefinitionSerializer;
StoreCache = storeCache;
DistributedCache = distributedCache;
DistributedLock = distributedLock;
AIManagementOptions = aiManagementOptions.Value;
CacheOptions = cacheOptions.Value;
}
public async virtual Task<IReadOnlyList<AIToolDefinition>> GetAllAsync()
{
if (!AIManagementOptions.IsDynamicAIToolStoreEnabled)
{
return Array.Empty<AIToolDefinition>();
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetAITools();
}
}
public async virtual Task<AIToolDefinition> GetAsync([NotNull] string name)
{
Check.NotNull(name, nameof(name));
return await GetOrNullAsync(name) ?? throw new AbpException("Undefined AITool: " + name);
}
public async virtual Task<AIToolDefinition?> GetOrNullAsync([NotNull] string name)
{
Check.NotNull(name, nameof(name));
if (!AIManagementOptions.IsDynamicAIToolStoreEnabled)
{
return null;
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetAIToolOrNull(name);
}
}
protected virtual async Task EnsureCacheIsUptoDateAsync()
{
if (StoreCache.LastCheckTime.HasValue &&
DateTime.Now.Subtract(StoreCache.LastCheckTime.Value).TotalSeconds < 30)
{
return;
}
var stampInDistributedCache = await GetOrSetStampInDistributedCache();
if (stampInDistributedCache == StoreCache.CacheStamp)
{
StoreCache.LastCheckTime = DateTime.Now;
return;
}
await UpdateInMemoryStoreCache();
StoreCache.CacheStamp = stampInDistributedCache;
StoreCache.LastCheckTime = DateTime.Now;
}
protected virtual async Task UpdateInMemoryStoreCache()
{
var workspaces = await AIToolDefinitionRecordRepository.GetListAsync();
await StoreCache.FillAsync(workspaces);
}
protected virtual async Task<string> GetOrSetStampInDistributedCache()
{
var cacheKey = GetCommonStampCacheKey();
var stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey);
if (stampInDistributedCache != null)
{
return stampInDistributedCache;
}
await using (var commonLockHandle = await DistributedLock
.TryAcquireAsync(GetCommonDistributedLockKey(), TimeSpan.FromMinutes(2)))
{
if (commonLockHandle == null)
{
throw new AbpException(
"Could not acquire distributed lock for AITool definition common stamp check!"
);
}
stampInDistributedCache = await DistributedCache.GetStringAsync(cacheKey);
if (stampInDistributedCache != null)
{
return stampInDistributedCache;
}
stampInDistributedCache = Guid.NewGuid().ToString();
await DistributedCache.SetStringAsync(
cacheKey,
stampInDistributedCache,
new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromDays(30)
}
);
}
return stampInDistributedCache;
}
protected virtual string GetCommonStampCacheKey()
{
return $"{CacheOptions.KeyPrefix}_AbpInMemoryAIToolCacheStamp";
}
protected virtual string GetCommonDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_Common_AbpAIToolUpdateLock";
}
}

57
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreCacheInvalidator.cs

@ -0,0 +1,57 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.AIManagement.Tools;
public class DynamicAIToolDefinitionStoreCacheInvalidator :
ILocalEventHandler<EntityChangedEventData<AIToolDefinitionRecord>>,
ITransientDependency
{
private readonly IDynamicAIToolDefinitionStoreInMemoryCache _storeCache;
private readonly IClock _clock;
private readonly IDistributedCache _distributedCache;
private readonly AbpDistributedCacheOptions _cacheOptions;
public DynamicAIToolDefinitionStoreCacheInvalidator(
IClock clock,
IDistributedCache distributedCache,
IDynamicAIToolDefinitionStoreInMemoryCache storeCache,
IOptions<AbpDistributedCacheOptions> cacheOptions)
{
_storeCache = storeCache;
_clock = clock;
_distributedCache = distributedCache;
_cacheOptions = cacheOptions.Value;
}
public async virtual Task HandleEventAsync(EntityChangedEventData<AIToolDefinitionRecord> eventData)
{
await RemoveStampInDistributedCacheAsync();
}
protected async virtual Task RemoveStampInDistributedCacheAsync()
{
using (await _storeCache.SyncSemaphore.LockAsync())
{
var cacheKey = GetCommonStampCacheKey();
await _distributedCache.RemoveAsync(cacheKey);
_storeCache.CacheStamp = Guid.NewGuid().ToString();
_storeCache.LastCheckTime = _clock.Now.AddMinutes(-5);
}
}
protected virtual string GetCommonStampCacheKey()
{
return $"{_cacheOptions.KeyPrefix}_AbpInMemoryAIToolCacheStamp";
}
}

79
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/DynamicAIToolDefinitionStoreInMemoryCache.cs

@ -0,0 +1,79 @@
using LINGYUN.Abp.AI.Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization;
using Volo.Abp.SimpleStateChecking;
namespace LINGYUN.Abp.AIManagement.Tools;
public class DynamicAIToolDefinitionStoreInMemoryCache : IDynamicAIToolDefinitionStoreInMemoryCache, ISingletonDependency
{
public string CacheStamp { get; set; }
protected IDictionary<string, AIToolDefinition> AIToolDefinitions { get; }
protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; }
protected ILocalizableStringSerializer LocalizableStringSerializer { get; }
public SemaphoreSlim SyncSemaphore { get; } = new(1, 1);
public DateTime? LastCheckTime { get; set; }
public DynamicAIToolDefinitionStoreInMemoryCache(
ISimpleStateCheckerSerializer stateCheckerSerializer,
ILocalizableStringSerializer localizableStringSerializer)
{
StateCheckerSerializer = stateCheckerSerializer;
LocalizableStringSerializer = localizableStringSerializer;
AIToolDefinitions = new Dictionary<string, AIToolDefinition>();
}
public Task FillAsync(List<AIToolDefinitionRecord> tools)
{
AIToolDefinitions.Clear();
foreach (var tool in tools)
{
var toolDef = new AIToolDefinition(
tool.Name,
tool.Provider,
!tool.Description.IsNullOrWhiteSpace() ? LocalizableStringSerializer.Deserialize(tool.Description) : null);
toolDef.IsEnabled = tool.IsEnabled;
if (!tool.StateCheckers.IsNullOrWhiteSpace())
{
var checkers = StateCheckerSerializer
.DeserializeArray(
tool.StateCheckers,
toolDef
);
toolDef.StateCheckers.AddRange(checkers);
}
foreach (var property in tool.ExtraProperties)
{
if (property.Value != null)
{
toolDef.WithProperty(property.Key, property.Value);
}
}
AIToolDefinitions[tool.Name] = toolDef;
}
return Task.CompletedTask;
}
public AIToolDefinition? GetAIToolOrNull(string name)
{
return AIToolDefinitions.GetOrDefault(name);
}
public IReadOnlyList<AIToolDefinition> GetAITools()
{
return AIToolDefinitions.Values.ToList();
}
}

11
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IAIToolDefinitionSerializer.cs

@ -0,0 +1,11 @@
using LINGYUN.Abp.AI.Tools;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LINGYUN.Abp.AIManagement.Tools;
public interface IAIToolDefinitionSerializer
{
Task<AIToolDefinitionRecord[]> SerializeAsync(IEnumerable<AIToolDefinition> definitions);
Task<AIToolDefinitionRecord> SerializeAsync(AIToolDefinition definition);
}

21
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IDynamicAIToolDefinitionStoreInMemoryCache.cs

@ -0,0 +1,21 @@
using LINGYUN.Abp.AI.Tools;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.AIManagement.Tools;
public interface IDynamicAIToolDefinitionStoreInMemoryCache
{
string CacheStamp { get; set; }
SemaphoreSlim SyncSemaphore { get; }
DateTime? LastCheckTime { get; set; }
Task FillAsync(List<AIToolDefinitionRecord> tools);
AIToolDefinition? GetAIToolOrNull(string name);
IReadOnlyList<AIToolDefinition> GetAITools();
}

7
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/IStaticAIToolSaver.cs

@ -0,0 +1,7 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.AIManagement.Tools;
public interface IStaticAIToolSaver
{
Task SaveAsync();
}

241
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Tools/StaticAIToolSaver.cs

@ -0,0 +1,241 @@
using LINGYUN.Abp.AI.Tools;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Guids;
using Volo.Abp.Json.SystemTextJson.Modifiers;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.AIManagement.Tools;
public class StaticAIToolSaver : IStaticAIToolSaver, ITransientDependency
{
protected IStaticAIToolDefinitionStore StaticStore { get; }
protected IAIToolDefinitionRecordRepository AIToolDefinitionRecordRepository { get; }
protected IAIToolDefinitionSerializer AIToolDefinitionSerializer { get; }
protected IDistributedCache Cache { get; }
protected IApplicationInfoAccessor ApplicationInfoAccessor { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected AbpAIToolsOptions AIToolOptions { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected AbpDistributedCacheOptions CacheOptions { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IGuidGenerator GuidGenerator { get; }
public StaticAIToolSaver(
IStaticAIToolDefinitionStore staticStore,
IAIToolDefinitionRecordRepository aiToolDefinitionRecordRepository,
IAIToolDefinitionSerializer aiToolDefinitionSerializer,
IDistributedCache cache,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IApplicationInfoAccessor applicationInfoAccessor,
IAbpDistributedLock distributedLock,
IOptions<AbpAIToolsOptions> aiToolOptions,
ICancellationTokenProvider cancellationTokenProvider,
IUnitOfWorkManager unitOfWorkManager,
IGuidGenerator guidGenerator)
{
StaticStore = staticStore;
AIToolDefinitionRecordRepository = aiToolDefinitionRecordRepository;
AIToolDefinitionSerializer = aiToolDefinitionSerializer;
Cache = cache;
ApplicationInfoAccessor = applicationInfoAccessor;
DistributedLock = distributedLock;
CancellationTokenProvider = cancellationTokenProvider;
AIToolOptions = aiToolOptions.Value;
CacheOptions = cacheOptions.Value;
UnitOfWorkManager = unitOfWorkManager;
GuidGenerator = guidGenerator;
}
[UnitOfWork]
public async Task SaveAsync()
{
await using var applicationLockHandle = await DistributedLock.TryAcquireAsync(
GetApplicationDistributedLockKey()
);
if (applicationLockHandle == null)
{
return;
}
var cacheKey = GetApplicationHashCacheKey();
var cachedHash = await Cache.GetStringAsync(cacheKey, CancellationTokenProvider.Token);
var aiTools = await AIToolDefinitionSerializer.SerializeAsync(await StaticStore.GetAllAsync());
var currentHash = CalculateHash(aiTools, AIToolOptions.DeletedAITools);
if (cachedHash == currentHash)
{
return;
}
await using (var commonLockHandle = await DistributedLock.TryAcquireAsync(
GetCommonDistributedLockKey(),
TimeSpan.FromMinutes(5)))
{
if (commonLockHandle == null)
{
/* It will re-try */
throw new AbpException("Could not acquire distributed lock for saving static AITool!");
}
using (var unitOfWork = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
{
try
{
var hasChangesInAITools = await UpdateChangedAIToolsAsync(aiTools);
if (hasChangesInAITools)
{
await Cache.SetStringAsync(
GetCommonStampCacheKey(),
Guid.NewGuid().ToString(),
new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromDays(30)
},
CancellationTokenProvider.Token
);
}
}
catch
{
try
{
await unitOfWork.RollbackAsync();
}
catch
{
/* ignored */
}
throw;
}
await unitOfWork.CompleteAsync();
}
}
await Cache.SetStringAsync(
cacheKey,
currentHash,
new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromDays(30)
},
CancellationTokenProvider.Token
);
}
private async Task<bool> UpdateChangedAIToolsAsync(AIToolDefinitionRecord[] aiToolRecords)
{
var newRecords = new List<AIToolDefinitionRecord>();
var changedRecords = new List<AIToolDefinitionRecord>();
var aiToolRecordsInDatabase = (await AIToolDefinitionRecordRepository.GetListAsync()).ToDictionary(x => x.Name);
foreach (var record in aiToolRecords)
{
var aiToolRecordInDatabase = aiToolRecordsInDatabase.GetOrDefault(record.Name);
if (aiToolRecordInDatabase == null)
{
/* New group */
newRecords.Add(record);
continue;
}
if (record.HasSameData(aiToolRecordInDatabase))
{
/* Not changed */
continue;
}
/* Changed */
aiToolRecordInDatabase.Patch(record);
changedRecords.Add(aiToolRecordInDatabase);
}
/* Deleted */
var deletedRecords = new List<AIToolDefinitionRecord>();
if (AIToolOptions.DeletedAITools.Any())
{
deletedRecords.AddRange(aiToolRecordsInDatabase.Values.Where(x => AIToolOptions.DeletedAITools.Contains(x.Name)));
}
if (newRecords.Any())
{
await AIToolDefinitionRecordRepository.InsertManyAsync(newRecords);
}
if (changedRecords.Any())
{
await AIToolDefinitionRecordRepository.UpdateManyAsync(changedRecords);
}
if (deletedRecords.Any())
{
await AIToolDefinitionRecordRepository.DeleteManyAsync(deletedRecords);
}
return newRecords.Any() || changedRecords.Any() || deletedRecords.Any();
}
private string GetApplicationDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpAIToolUpdateLock";
}
private string GetCommonDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_Common_AbpAIToolUpdateLock";
}
private string GetApplicationHashCacheKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpAIToolsHash";
}
private string GetCommonStampCacheKey()
{
return $"{CacheOptions.KeyPrefix}_AbpInMemoryAIToolCacheStamp";
}
private string CalculateHash(AIToolDefinitionRecord[] aiToolRecords, IEnumerable<string> deletedAITool)
{
var jsonSerializerOptions = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers =
{
new AbpIgnorePropertiesModifiers<AIToolDefinitionRecord, Guid>().CreateModifyAction(x => x.Id),
}
}
};
var stringBuilder = new StringBuilder();
stringBuilder.Append("AITools:");
stringBuilder.AppendLine(JsonSerializer.Serialize(aiToolRecords, jsonSerializerOptions));
stringBuilder.Append("DeletedAITool:");
stringBuilder.Append(deletedAITool.JoinAsString(","));
return stringBuilder
.ToString()
.ToMd5();
}
}

2
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IDynamicWorkspaceDefinitionStoreInMemoryCache.cs

@ -14,7 +14,7 @@ public interface IDynamicWorkspaceDefinitionStoreInMemoryCache
DateTime? LastCheckTime { get; set; }
Task FillAsync(List<WorkspaceDefinitionRecord> permissions);
Task FillAsync(List<WorkspaceDefinitionRecord> workspaces);
WorkspaceDefinition? GetWorkspaceOrNull(string name);

Loading…
Cancel
Save