Add configuration object management for providers and chat templates

This commit is contained in:
Thorsten Sommer 2025-08-18 20:34:04 +02:00
parent f88fdabfbe
commit 18828fbe85
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 131 additions and 72 deletions

View File

@ -14,7 +14,14 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginConfiguration).Namespace, nameof(PluginConfiguration));
private static readonly ILogger<PluginConfiguration> LOGGER = Program.LOGGER_FACTORY.CreateLogger<PluginConfiguration>();
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
private readonly List<PluginConfigurationObject> configObjects = [];
/// <summary>
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
/// </summary>
public IEnumerable<PluginConfigurationObject> ConfigObjects => this.configObjects;
public async Task InitializeAsync(bool dryRun)
{
if(!this.TryProcessConfiguration(dryRun, out var issue))
@ -30,11 +37,13 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
/// <summary>
/// Tries to initialize the UI text content of the plugin.
/// </summary>
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <param name="message">The error message, when the UI text content could not be read.</param>
/// <returns>True, when the UI text content could be read successfully.</returns>
private bool TryProcessConfiguration(bool dryRun, out string message)
{
this.configObjects.Clear();
// Ensure that the main CONFIG table exists and is a valid Lua table:
if (!this.state.Environment["CONFIG"].TryRead<LuaTable>(out var mainTable))
{
@ -90,29 +99,38 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Apply the configured providers to the system settings:
//
#pragma warning disable MWAIS0001
if (!dryRun)
foreach (var configuredProvider in configuredProviders)
{
foreach (var configuredProvider in configuredProviders)
{
// The iterating variable is immutable, so we need to create a local copy:
var provider = configuredProvider;
// The iterating variable is immutable, so we need to create a local copy:
var provider = configuredProvider;
var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id);
if (providerIndex > -1)
{
// Case: The provider already exists, we update it:
var existingProvider = SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex];
provider = provider with { Num = existingProvider.Num }; // Keep the original number
SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex] = provider;
}
else
{
// Case: The provider does not exist, we add it:
provider = provider with { Num = SETTINGS_MANAGER.ConfigurationData.NextProviderNum++ };
SETTINGS_MANAGER.ConfigurationData.Providers.Add(provider);
}
// Store this provider in the config object list:
this.configObjects.Add(new()
{
ConfigPluginId = this.Id,
Id = Guid.Parse(provider.Id),
Type = PluginConfigurationObjectType.LLM_PROVIDER,
});
if (dryRun)
continue;
var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id);
if (providerIndex > -1)
{
// Case: The provider already exists, we update it:
var existingProvider = SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex];
provider = provider with { Num = existingProvider.Num }; // Keep the original number
SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex] = provider;
}
else
{
// Case: The provider does not exist, we add it:
provider = provider with { Num = SETTINGS_MANAGER.ConfigurationData.NextProviderNum++ };
SETTINGS_MANAGER.ConfigurationData.Providers.Add(provider);
}
}
#pragma warning restore MWAIS0001
//
@ -138,26 +156,35 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
}
// Apply configured chat templates to the system settings:
if (!dryRun)
foreach (var configuredTemplate in configuredTemplates)
{
foreach (var configuredTemplate in configuredTemplates)
// The iterating variable is immutable, so we need to create a local copy:
var template = configuredTemplate;
// Store this provider in the config object list:
this.configObjects.Add(new()
{
// The iterating variable is immutable, so we need to create a local copy:
var template = configuredTemplate;
var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id);
if (tplIndex > -1)
{
// Case: The template already exists, we update it:
var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex];
template = template with { Num = existingTemplate.Num };
SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template;
}
else
{
// Case: The template does not exist, we add it:
template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ };
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template);
}
ConfigPluginId = this.Id,
Id = Guid.Parse(template.Id),
Type = PluginConfigurationObjectType.CHAT_TEMPLATE,
});
if (dryRun)
continue;
var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id);
if (tplIndex > -1)
{
// Case: The template already exists, we update it:
var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex];
template = template with { Num = existingTemplate.Num };
SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template;
}
else
{
// Case: The template does not exist, we add it:
template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ };
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template);
}
}
}

View File

@ -0,0 +1,23 @@
namespace AIStudio.Tools.PluginSystem;
/// <summary>
/// Represents metadata for a configuration object from a configuration plugin. These are
/// complex objects such as configured LLM providers, chat templates, etc.
/// </summary>
public sealed record PluginConfigurationObject
{
/// <summary>
/// The id of the configuration plugin to which this configuration object belongs.
/// </summary>
public required Guid ConfigPluginId { get; init; } = Guid.NewGuid();
/// <summary>
/// The id of the configuration object, e.g., the id of a chat template.
/// </summary>
public required Guid Id { get; init; } = Guid.NewGuid();
/// <summary>
/// The type of the configuration object.
/// </summary>
public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE;
}

View File

@ -0,0 +1,13 @@
namespace AIStudio.Tools.PluginSystem;
public enum PluginConfigurationObjectType
{
NONE,
UNKNOWN,
PROFILE,
DATA_SOURCE,
LLM_PROVIDER,
CHAT_TEMPLATE,
EMBEDDING_PROVIDER,
}

View File

@ -40,6 +40,8 @@ public static partial class PluginFactory
if (!await PLUGIN_LOAD_SEMAPHORE.WaitAsync(0, cancellationToken))
return;
var configObjectList = new List<PluginConfigurationObject>();
try
{
LOG.LogInformation("Start loading plugins.");
@ -112,7 +114,8 @@ public static partial class PluginFactory
}
// Start or restart all plugins:
await RestartAllPlugins(cancellationToken);
var configObjects = await RestartAllPlugins(cancellationToken);
configObjectList.AddRange(configObjects);
}
finally
{
@ -149,6 +152,16 @@ public static partial class PluginFactory
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider);
wasConfigurationChanged = true;
}
if(!configObjectList.Any(configObject =>
configObject.Type is PluginConfigurationObjectType.LLM_PROVIDER &&
configObject.ConfigPluginId == providerSourcePluginId &&
configObject.Id.ToString() == configuredProvider.Id))
{
LOG.LogWarning($"The configured LLM provider '{configuredProvider.InstanceName}' (id={configuredProvider.Id}) is not present in the configuration plugin anymore. Removing the provider from the settings.");
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider);
wasConfigurationChanged = true;
}
}
#pragma warning restore MWAIS0001
@ -172,41 +185,17 @@ public static partial class PluginFactory
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate);
wasConfigurationChanged = true;
}
}
//
// Before checking simple settings, validate that still-present configuration plugins haven't removed individual
// providers or chat templates they previously managed. If so, remove those items from our settings as well:
//
#pragma warning disable MWAIS0001
foreach (var runningPlugin in RUNNING_PLUGINS.OfType<PluginConfiguration>())
{
var (providerIds, templateIds) = runningPlugin.GetManagedObjectIds();
var cfgPluginId = runningPlugin.Id;
// Providers managed by this plugin but no longer present in plugin config
var providersToRemove = SETTINGS_MANAGER.ConfigurationData.Providers
.Where(p => p.IsEnterpriseConfiguration && p.EnterpriseConfigurationPluginId == cfgPluginId && !providerIds.Contains(p.Id))
.ToList();
foreach (var p in providersToRemove)
if(!configObjectList.Any(configObject =>
configObject.Type is PluginConfigurationObjectType.CHAT_TEMPLATE &&
configObject.ConfigPluginId == templateSourcePluginId &&
configObject.Id.ToString() == configuredTemplate.Id))
{
LOG.LogWarning($"The configured LLM provider '{p.InstanceName}' (id={p.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the provider from the settings.");
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(p);
wasConfigurationChanged = true;
}
// Chat templates managed by this plugin but no longer present in plugin config
var templatesToRemove = SETTINGS_MANAGER.ConfigurationData.ChatTemplates
.Where(t => t.IsEnterpriseConfiguration && t.EnterpriseConfigurationPluginId == cfgPluginId && !templateIds.Contains(t.Id))
.ToList();
foreach (var t in templatesToRemove)
{
LOG.LogWarning($"The configured chat template '{t.Name}' (id={t.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the chat template from the settings.");
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(t);
LOG.LogWarning($"The configured chat template '{configuredTemplate.Name}' (id={configuredTemplate.Id}) is not present in the configuration plugin anymore. Removing the chat template from the settings.");
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate);
wasConfigurationChanged = true;
}
}
#pragma warning restore MWAIS0001
//
// ==========================================================

View File

@ -11,9 +11,10 @@ public static partial class PluginFactory
/// </summary>
public static IReadOnlyCollection<PluginBase> RunningPlugins => RUNNING_PLUGINS;
private static async Task RestartAllPlugins(CancellationToken cancellationToken = default)
private static async Task<List<PluginConfigurationObject>> RestartAllPlugins(CancellationToken cancellationToken = default)
{
LOG.LogInformation("Try to start or restart all plugins.");
var configObjects = new List<PluginConfigurationObject>();
RUNNING_PLUGINS.Clear();
//
@ -65,7 +66,12 @@ public static partial class PluginFactory
{
if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin) || availablePlugin.Type == PluginType.CONFIGURATION)
if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin)
{
if (plugin is PluginConfiguration configPlugin)
configObjects.AddRange(configPlugin.ConfigObjects);
RUNNING_PLUGINS.Add(plugin);
}
}
catch (Exception e)
{
@ -75,6 +81,7 @@ public static partial class PluginFactory
// Inform all components that the plugins have been reloaded or started:
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.PLUGINS_RELOADED);
return configObjects;
}
private static async Task<PluginBase> Start(IAvailablePlugin meta, CancellationToken cancellationToken = default)