11 changed files with 1068 additions and 0 deletions
@ -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"; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Workspaces; |
|||
public interface IStaticWorkspaceSaver |
|||
{ |
|||
Task SaveAsync(); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue