Browse Source

feat: Implement dynamic workspace

pull/1421/head
colin 9 months ago
parent
commit
c1bf459685
  1. 42
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs
  2. 161
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStore.cs
  3. 95
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStoreInMemoryCache.cs
  4. 22
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IDynamicWorkspaceDefinitionStoreInMemoryCache.cs
  5. 7
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IStaticWorkspaceSaver.cs
  6. 11
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IWorkspaceDefinitionSerializer.cs
  7. 12
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IWorkspaceRepository.cs
  8. 242
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/StaticWorkspaceSaver.cs
  9. 255
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/Workspace.cs
  10. 72
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionSerializer.cs
  11. 149
      aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDynamicInitializer.cs

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

@ -1,8 +1,16 @@
using LINGYUN.Abp.AI;
using LINGYUN.Abp.AIManagement.Workspaces;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain;
using Volo.Abp.Mapperly;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.AIManagement;
@ -14,4 +22,38 @@ namespace LINGYUN.Abp.AIManagement;
typeof(AbpDddDomainModule))]
public class AbpAIManagementDomainModule : AbpModule
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMapperlyObjectMapper<AbpAIManagementDomainModule>();
if (context.Services.IsDataMigrationEnvironment())
{
Configure<AIManagementOptions>(options =>
{
options.SaveStaticWorkspacesToDatabase = false;
options.IsDynamicWorkspaceStoreEnabled = false;
});
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context));
}
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var initializer = rootServiceProvider.GetRequiredService<WorkspaceDynamicInitializer>();
await initializer.InitializeAsync(true, _cancellationTokenSource.Token);
}
public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
{
_cancellationTokenSource.Cancel();
return Task.CompletedTask;
}
}

161
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStore.cs

@ -0,0 +1,161 @@
using JetBrains.Annotations;
using LINGYUN.Abp.AI.Workspaces;
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.Workspaces;
[Dependency(ReplaceServices = true)]
public class DynamicWorkspaceDefinitionStore : IDynamicWorkspaceDefinitionStore, ITransientDependency
{
protected IWorkspaceRepository WorkspaceRepository { get; }
protected IWorkspaceDefinitionSerializer WorkspaceDefinitionSerializer { get; }
protected IDynamicWorkspaceDefinitionStoreInMemoryCache StoreCache { get; }
protected IDistributedCache DistributedCache { get; }
protected IAbpDistributedLock DistributedLock { get; }
public AIManagementOptions AIManagementOptions { get; }
protected AbpDistributedCacheOptions CacheOptions { get; }
public DynamicWorkspaceDefinitionStore(
IWorkspaceRepository workspaceRepository,
IWorkspaceDefinitionSerializer workspaceDefinitionSerializer,
IDynamicWorkspaceDefinitionStoreInMemoryCache storeCache,
IDistributedCache distributedCache,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IOptions<AIManagementOptions> aiManagementOptions,
IAbpDistributedLock distributedLock)
{
WorkspaceRepository = workspaceRepository;
WorkspaceDefinitionSerializer = workspaceDefinitionSerializer;
StoreCache = storeCache;
DistributedCache = distributedCache;
DistributedLock = distributedLock;
AIManagementOptions = aiManagementOptions.Value;
CacheOptions = cacheOptions.Value;
}
public async virtual Task<IReadOnlyList<WorkspaceDefinition>> GetAllAsync()
{
if (!AIManagementOptions.IsDynamicWorkspaceStoreEnabled)
{
return Array.Empty<WorkspaceDefinition>();
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetWorkspaces();
}
}
public async virtual Task<WorkspaceDefinition> GetAsync([NotNull] string name)
{
Check.NotNull(name, nameof(name));
return await GetOrNullAsync(name) ?? throw new AbpException("Undefined workspace: " + name);
}
public async virtual Task<WorkspaceDefinition?> GetOrNullAsync([NotNull] string name)
{
Check.NotNull(name, nameof(name));
if (!AIManagementOptions.IsDynamicWorkspaceStoreEnabled)
{
return null;
}
using (await StoreCache.SyncSemaphore.LockAsync())
{
await EnsureCacheIsUptoDateAsync();
return StoreCache.GetWorkspaceOrNull(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 WorkspaceRepository.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 workspace 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}_AbpInMemoryWorkspaceCacheStamp";
}
protected virtual string GetCommonDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_Common_AbpWorkspaceUpdateLock";
}
}

95
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/DynamicWorkspaceDefinitionStoreInMemoryCache.cs

@ -0,0 +1,95 @@
using LINGYUN.Abp.AI.Workspaces;
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.Workspaces;
public class DynamicWorkspaceDefinitionStoreInMemoryCache : IDynamicWorkspaceDefinitionStoreInMemoryCache, ISingletonDependency
{
public string CacheStamp { get; set; }
protected IDictionary<string, WorkspaceDefinition> WorkspaceDefinitions { get; }
protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; }
protected ILocalizableStringSerializer LocalizableStringSerializer { get; }
public SemaphoreSlim SyncSemaphore { get; } = new(1, 1);
public DateTime? LastCheckTime { get; set; }
public DynamicWorkspaceDefinitionStoreInMemoryCache(
ISimpleStateCheckerSerializer stateCheckerSerializer,
ILocalizableStringSerializer localizableStringSerializer)
{
StateCheckerSerializer = stateCheckerSerializer;
LocalizableStringSerializer = localizableStringSerializer;
WorkspaceDefinitions = new Dictionary<string, WorkspaceDefinition>();
}
public Task FillAsync(List<Workspace> workspaces)
{
WorkspaceDefinitions.Clear();
foreach (var workspace in workspaces)
{
var workspaceDef = new WorkspaceDefinition(
workspace.Name,
workspace.Provider,
workspace.ModelName,
LocalizableStringSerializer.Deserialize(workspace.DisplayName),
!workspace.Description.IsNullOrWhiteSpace() ? LocalizableStringSerializer.Deserialize(workspace.Description) : null,
workspace.SystemPrompt,
workspace.Instructions,
workspace.Temperature,
workspace.MaxOutputTokens,
workspace.FrequencyPenalty,
workspace.PresencePenalty);
if (!workspace.ApiKey.IsNullOrWhiteSpace())
{
workspaceDef.WithApiKey(workspace.ApiKey);
}
if (!workspace.ApiBaseUrl.IsNullOrWhiteSpace())
{
workspaceDef.WithApiBaseUrl(workspace.ApiBaseUrl);
}
workspaceDef.IsEnabled = workspace.IsEnabled;
if (!workspace.StateCheckers.IsNullOrWhiteSpace())
{
var checkers = StateCheckerSerializer
.DeserializeArray(
workspace.StateCheckers,
workspaceDef
);
workspaceDef.StateCheckers.AddRange(checkers);
}
foreach (var property in workspace.ExtraProperties)
{
if (property.Value != null)
{
workspaceDef.WithProperty(property.Key, property.Value);
}
}
WorkspaceDefinitions[workspace.Name] = workspaceDef;
}
return Task.CompletedTask;
}
public WorkspaceDefinition? GetWorkspaceOrNull(string name)
{
return WorkspaceDefinitions.GetOrDefault(name);
}
public IReadOnlyList<WorkspaceDefinition> GetWorkspaces()
{
return WorkspaceDefinitions.Values.ToList();
}
}

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

@ -0,0 +1,22 @@
using LINGYUN.Abp.AI.Workspaces;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.AIManagement.Workspaces;
public interface IDynamicWorkspaceDefinitionStoreInMemoryCache
{
string CacheStamp { get; set; }
SemaphoreSlim SyncSemaphore { get; }
DateTime? LastCheckTime { get; set; }
Task FillAsync(List<Workspace> permissions);
WorkspaceDefinition? GetWorkspaceOrNull(string name);
IReadOnlyList<WorkspaceDefinition> GetWorkspaces();
}

7
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IStaticWorkspaceSaver.cs

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

11
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/IWorkspaceDefinitionSerializer.cs

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

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

@ -0,0 +1,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace LINGYUN.Abp.AIManagement.Workspaces;
public interface IWorkspaceRepository : IBasicRepository<Workspace, Guid>
{
Task<Workspace> FindByNameAsync(
string name,
CancellationToken cancellationToken = default);
}

242
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/StaticWorkspaceSaver.cs

@ -0,0 +1,242 @@
using LINGYUN.Abp.AI;
using LINGYUN.Abp.AI.Workspaces;
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.Workspaces;
public class StaticWorkspaceSaver : IStaticWorkspaceSaver, ITransientDependency
{
protected IStaticWorkspaceDefinitionStore StaticStore { get; }
protected IWorkspaceRepository WorkspaceRepository { get; }
protected IWorkspaceDefinitionSerializer WorkspaceSerializer { get; }
protected IDistributedCache Cache { get; }
protected IApplicationInfoAccessor ApplicationInfoAccessor { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected AbpAICoreOptions AIOptions { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected AbpDistributedCacheOptions CacheOptions { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IGuidGenerator GuidGenerator { get; }
public StaticWorkspaceSaver(
IStaticWorkspaceDefinitionStore staticStore,
IWorkspaceRepository workspaceRepository,
IWorkspaceDefinitionSerializer workspaceSerializer,
IDistributedCache cache,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IApplicationInfoAccessor applicationInfoAccessor,
IAbpDistributedLock distributedLock,
IOptions<AbpAICoreOptions> settingOptions,
ICancellationTokenProvider cancellationTokenProvider,
IUnitOfWorkManager unitOfWorkManager,
IGuidGenerator guidGenerator)
{
StaticStore = staticStore;
WorkspaceRepository = workspaceRepository;
WorkspaceSerializer = workspaceSerializer;
Cache = cache;
ApplicationInfoAccessor = applicationInfoAccessor;
DistributedLock = distributedLock;
CancellationTokenProvider = cancellationTokenProvider;
AIOptions = settingOptions.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 workspaces = await WorkspaceSerializer.SerializeAsync(await StaticStore.GetAllAsync());
var currentHash = CalculateHash(workspaces, AIOptions.DeletedWorkspaces);
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 Workspaces!");
}
using (var unitOfWork = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
{
try
{
var hasChangesInWorkspaces = await UpdateChangedWorkspacesAsync(workspaces);
if (hasChangesInWorkspaces)
{
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> UpdateChangedWorkspacesAsync(Workspace[] workspaces)
{
var newRecords = new List<Workspace>();
var changedRecords = new List<Workspace>();
var workspaceRecordsInDatabase = (await WorkspaceRepository.GetListAsync()).ToDictionary(x => x.Name);
foreach (var record in workspaces)
{
var workspaceRecordInDatabase = workspaceRecordsInDatabase.GetOrDefault(record.Name);
if (workspaceRecordInDatabase == null)
{
/* New group */
newRecords.Add(record);
continue;
}
if (record.HasSameData(workspaceRecordInDatabase))
{
/* Not changed */
continue;
}
/* Changed */
workspaceRecordInDatabase.Patch(record);
changedRecords.Add(workspaceRecordInDatabase);
}
/* Deleted */
var deletedRecords = new List<Workspace>();
if (AIOptions.DeletedWorkspaces.Any())
{
deletedRecords.AddRange(workspaceRecordsInDatabase.Values.Where(x => AIOptions.DeletedWorkspaces.Contains(x.Name)));
}
if (newRecords.Any())
{
await WorkspaceRepository.InsertManyAsync(newRecords);
}
if (changedRecords.Any())
{
await WorkspaceRepository.UpdateManyAsync(changedRecords);
}
if (deletedRecords.Any())
{
await WorkspaceRepository.DeleteManyAsync(deletedRecords);
}
return newRecords.Any() || changedRecords.Any() || deletedRecords.Any();
}
private string GetApplicationDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpWorkspaceUpdateLock";
}
private string GetCommonDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_Common_AbpWorkspaceUpdateLock";
}
private string GetApplicationHashCacheKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpWorkspacesHash";
}
private string GetCommonStampCacheKey()
{
return $"{CacheOptions.KeyPrefix}_AbpInMemoryWorkspaceCacheStamp";
}
private string CalculateHash(Workspace[] workspaces, IEnumerable<string> deletedWorkspaces)
{
var jsonSerializerOptions = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers =
{
new AbpIgnorePropertiesModifiers<Workspace, Guid>().CreateModifyAction(x => x.Id),
}
}
};
var stringBuilder = new StringBuilder();
stringBuilder.Append("Workspaces:");
stringBuilder.AppendLine(JsonSerializer.Serialize(workspaces, jsonSerializerOptions));
stringBuilder.Append("DeletedWorkspace:");
stringBuilder.Append(deletedWorkspaces.JoinAsString(","));
return stringBuilder
.ToString()
.ToMd5();
}
}

255
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/Workspace.cs

@ -0,0 +1,255 @@
using System;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Auditing;
namespace LINGYUN.Abp.AIManagement.Workspaces;
public class Workspace : AuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public string Provider { get; private set; }
public string ModelName { get; private set; }
public string DisplayName { get; private set; }
public string? Description { get; private set; }
public string? ApiKey { get; private set; }
public string? ApiBaseUrl { get; private set; }
public string? SystemPrompt { get; set; }
public string? Instructions { get; set; }
public float? Temperature { get; set; }
public int? MaxOutputTokens { get; set; }
public float? FrequencyPenalty { get; set; }
public float? PresencePenalty { get; set; }
public bool IsEnabled { get; set; }
public string? StateCheckers { get; set; }
protected Workspace()
{
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
public Workspace(
Guid id,
string name,
string provider,
string modelName,
string displayName,
string? description = null,
string? apiKey = null,
string? apiBaseUrl = null,
string? systemPrompt = null,
string? instructions = null,
float? temperature = null,
int? maxOutputTokens = null,
float? frequencyPenalty = null,
float? presencePenalty = null,
string? stateCheckers = null)
: base(id)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), WorkspaceConsts.MaxNameLength);
Provider = Check.NotNullOrWhiteSpace(provider, nameof(provider), WorkspaceConsts.MaxProviderLength);
ModelName = Check.NotNullOrWhiteSpace(modelName, nameof(modelName), WorkspaceConsts.MaxModelNameLength);
DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), WorkspaceConsts.MaxDisplayNameLength);
Description = Check.Length(description, nameof(description), WorkspaceConsts.MaxDescriptionLength);
ApiKey = Check.Length(apiKey, nameof(apiKey), WorkspaceConsts.MaxApiKeyLength);
ApiBaseUrl = Check.Length(apiBaseUrl, nameof(apiBaseUrl), WorkspaceConsts.MaxApiBaseUrlLength);
SystemPrompt = Check.Length(systemPrompt, nameof(systemPrompt), WorkspaceConsts.MaxSystemPromptLength);
Instructions = Check.Length(instructions, nameof(instructions), WorkspaceConsts.MaxInstructionsLength);
StateCheckers = Check.Length(stateCheckers, nameof(stateCheckers), WorkspaceConsts.MaxStateCheckersLength);
Temperature = temperature;
MaxOutputTokens = maxOutputTokens;
FrequencyPenalty = frequencyPenalty;
PresencePenalty = presencePenalty;
IsEnabled = true;
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
public bool HasSameData(Workspace otherWorkspace)
{
if (Name != otherWorkspace.Name)
{
return false;
}
if (Provider != otherWorkspace.Provider)
{
return false;
}
if (ModelName != otherWorkspace.ModelName)
{
return false;
}
if (DisplayName != otherWorkspace.DisplayName)
{
return false;
}
if (Description != otherWorkspace.Description)
{
return false;
}
if (ApiKey != otherWorkspace.ApiKey)
{
return false;
}
if (ApiBaseUrl != otherWorkspace.ApiBaseUrl)
{
return false;
}
if (SystemPrompt != otherWorkspace.SystemPrompt)
{
return false;
}
if (Instructions != otherWorkspace.Instructions)
{
return false;
}
if (IsEnabled != otherWorkspace.IsEnabled)
{
return false;
}
if (Temperature != otherWorkspace.Temperature)
{
return false;
}
if (MaxOutputTokens != otherWorkspace.MaxOutputTokens)
{
return false;
}
if (FrequencyPenalty != otherWorkspace.FrequencyPenalty)
{
return false;
}
if (PresencePenalty != otherWorkspace.PresencePenalty)
{
return false;
}
if (StateCheckers != otherWorkspace.StateCheckers)
{
return false;
}
if (!this.HasSameExtraProperties(otherWorkspace))
{
return false;
}
return true;
}
public void Patch(Workspace otherWorkspace)
{
if (Name != otherWorkspace.Name)
{
Name = otherWorkspace.Name;
}
if (Provider != otherWorkspace.Provider)
{
Provider = otherWorkspace.Provider;
}
if (ModelName != otherWorkspace.ModelName)
{
ModelName = otherWorkspace.ModelName;
}
if (DisplayName != otherWorkspace.DisplayName)
{
DisplayName = otherWorkspace.DisplayName;
}
if (Description != otherWorkspace.Description)
{
Description = otherWorkspace.Description;
}
if (ApiKey != otherWorkspace.ApiKey)
{
ApiKey = otherWorkspace.ApiKey;
}
if (ApiBaseUrl != otherWorkspace.ApiBaseUrl)
{
ApiBaseUrl = otherWorkspace.ApiBaseUrl;
}
if (SystemPrompt != otherWorkspace.SystemPrompt)
{
SystemPrompt = otherWorkspace.SystemPrompt;
}
if (Instructions != otherWorkspace.Instructions)
{
Instructions = otherWorkspace.Instructions;
}
if (IsEnabled != otherWorkspace.IsEnabled)
{
IsEnabled = otherWorkspace.IsEnabled;
}
if (Temperature != otherWorkspace.Temperature)
{
Temperature = otherWorkspace.Temperature;
}
if (MaxOutputTokens != otherWorkspace.MaxOutputTokens)
{
MaxOutputTokens = otherWorkspace.MaxOutputTokens;
}
if (FrequencyPenalty != otherWorkspace.FrequencyPenalty)
{
FrequencyPenalty = otherWorkspace.FrequencyPenalty;
}
if (PresencePenalty != otherWorkspace.PresencePenalty)
{
PresencePenalty = otherWorkspace.PresencePenalty;
}
if (StateCheckers != otherWorkspace.StateCheckers)
{
StateCheckers = otherWorkspace.StateCheckers;
}
if (!this.HasSameExtraProperties(otherWorkspace))
{
ExtraProperties.Clear();
foreach (var property in otherWorkspace.ExtraProperties)
{
ExtraProperties.Add(property.Key, property.Value);
}
}
}
}

72
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDefinitionSerializer.cs

@ -0,0 +1,72 @@
using LINGYUN.Abp.AI.Workspaces;
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.Workspaces;
public class WorkspaceDefinitionSerializer : IWorkspaceDefinitionSerializer, ITransientDependency
{
protected IGuidGenerator GuidGenerator { get; }
protected ISimpleStateCheckerSerializer StateCheckerSerializer { get; }
protected ILocalizableStringSerializer LocalizableStringSerializer { get; }
public WorkspaceDefinitionSerializer(
IGuidGenerator guidGenerator,
ISimpleStateCheckerSerializer stateCheckerSerializer,
ILocalizableStringSerializer localizableStringSerializer)
{
GuidGenerator = guidGenerator;
StateCheckerSerializer = stateCheckerSerializer;
LocalizableStringSerializer = localizableStringSerializer;
}
public async virtual Task<Workspace[]> SerializeAsync(IEnumerable<WorkspaceDefinition> definitions)
{
var records = new List<Workspace>();
foreach (var workspaceDef in definitions)
{
records.Add(await SerializeAsync(workspaceDef));
}
return records.ToArray();
}
public virtual Task<Workspace> SerializeAsync(WorkspaceDefinition definition)
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
var workspace = new Workspace(
GuidGenerator.Create(),
definition.Name,
definition.Provider,
definition.ModelName,
LocalizableStringSerializer.Serialize(definition.DisplayName)!,
definition.Description != null ? LocalizableStringSerializer.Serialize(definition.Description) : null,
definition.ApiKey,
definition.ApiBaseUrl,
definition.SystemPrompt,
definition.Instructions,
definition.Temperature,
definition.MaxOutputTokens,
definition.FrequencyPenalty,
definition.PresencePenalty,
SerializeStateCheckers(definition.StateCheckers));
foreach (var property in definition.Properties)
{
workspace.SetProperty(property.Key, property.Value);
}
return Task.FromResult(workspace);
}
}
protected virtual string? SerializeStateCheckers(List<ISimpleStateChecker<WorkspaceDefinition>> stateCheckers)
{
return StateCheckerSerializer.Serialize(stateCheckers);
}
}

149
aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/Workspaces/WorkspaceDynamicInitializer.cs

@ -0,0 +1,149 @@
using JetBrains.Annotations;
using LINGYUN.Abp.AI.Workspaces;
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.Workspaces;
public class WorkspaceDynamicInitializer : ITransientDependency
{
public ILogger<WorkspaceDynamicInitializer> Logger { get; set; }
protected IServiceProvider ServiceProvider { get; }
protected IOptions<AIManagementOptions> Options { get; }
[CanBeNull]
protected IHostApplicationLifetime ApplicationLifetime { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected IDynamicWorkspaceDefinitionStore DynamicWorkspaceDefinitionStore { get; }
protected IStaticWorkspaceSaver StaticWorkspaceSaver { get; }
public WorkspaceDynamicInitializer(
IServiceProvider serviceProvider,
IOptions<AIManagementOptions> options,
ICancellationTokenProvider cancellationTokenProvider,
IDynamicWorkspaceDefinitionStore dynamicWorkspaceDefinitionStore,
IStaticWorkspaceSaver staticWorkspaceSaver)
{
Logger = NullLogger<WorkspaceDynamicInitializer>.Instance;
ServiceProvider = serviceProvider;
Options = options;
CancellationTokenProvider = cancellationTokenProvider;
DynamicWorkspaceDefinitionStore = dynamicWorkspaceDefinitionStore;
StaticWorkspaceSaver = staticWorkspaceSaver;
ApplicationLifetime = ServiceProvider.GetRequiredService<IHostApplicationLifetime>();
}
public virtual Task InitializeAsync(bool runInBackground, CancellationToken cancellationToken = default)
{
var options = Options.Value;
if (!options.SaveStaticWorkspacesToDatabase && !options.IsDynamicWorkspaceStoreEnabled)
{
return Task.CompletedTask;
}
if (runInBackground)
{
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
{
using (CancellationTokenProvider.Use(cancellationToken))
{
if (CancellationTokenProvider.Token.IsCancellationRequested)
{
return;
}
await SaveStaticWorkspacesToDatabaseAsync(options, cancellationToken);
if (CancellationTokenProvider.Token.IsCancellationRequested)
{
return;
}
await PreCacheDynamicWorkspacesAsync(options);
}
}
catch
{
// No need to log here since inner calls log
}
}
protected virtual async Task SaveStaticWorkspacesToDatabaseAsync(
AIManagementOptions options,
CancellationToken cancellationToken)
{
if (!options.SaveStaticWorkspacesToDatabase)
{
return;
}
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 StaticWorkspaceSaver.SaveAsync();
}
catch (Exception ex)
{
Logger.LogException(ex);
throw; // Polly will catch it
}
}, cancellationToken);
}
protected virtual async Task PreCacheDynamicWorkspacesAsync(AIManagementOptions options)
{
if (!options.IsDynamicWorkspaceStoreEnabled)
{
return;
}
try
{
// Pre-cache Workspaces, so first request doesn't wait
await DynamicWorkspaceDefinitionStore.GetAllAsync();
}
catch (Exception ex)
{
Logger.LogException(ex);
throw; // It will be cached in Initialize()
}
}
}
Loading…
Cancel
Save