mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-10-08 22:00:21 +00:00
Refactor plugin configuration to streamline handling and parsing of objects
This commit is contained in:
parent
adbe9d981a
commit
c9415b01b4
@ -1,6 +1,8 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Lua;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public record ChatTemplate(
|
||||
@ -12,7 +14,7 @@ public record ChatTemplate(
|
||||
List<ContentBlock> ExampleConversation,
|
||||
bool AllowProfileUsage,
|
||||
bool IsEnterpriseConfiguration = false,
|
||||
Guid EnterpriseConfigurationPluginId = default) : IConfigurationObject
|
||||
Guid EnterpriseConfigurationPluginId = default) : ConfigurationBaseObject
|
||||
{
|
||||
public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], false)
|
||||
{
|
||||
@ -20,6 +22,8 @@ public record ChatTemplate(
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate));
|
||||
|
||||
private static readonly ILogger<ChatTemplate> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ChatTemplate>();
|
||||
|
||||
public static readonly ChatTemplate NO_CHAT_TEMPLATE = new()
|
||||
{
|
||||
Name = TB("Use no chat template"),
|
||||
@ -50,4 +54,96 @@ public record ChatTemplate(
|
||||
|
||||
return this.SystemPrompt;
|
||||
}
|
||||
|
||||
public static bool TryParseChatTemplateTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject template)
|
||||
{
|
||||
template = NO_CHAT_TEMPLATE;
|
||||
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid ID. The ID must be a valid GUID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead<string>(out var name))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("SystemPrompt", out var sysPromptValue) || !sysPromptValue.TryRead<string>(out var systemPrompt))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid system prompt.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var predefinedUserPrompt = string.Empty;
|
||||
if (table.TryGetValue("PredefinedUserPrompt", out var preUserValue) && preUserValue.TryRead<string>(out var preUser))
|
||||
predefinedUserPrompt = preUser;
|
||||
|
||||
var allowProfileUsage = false;
|
||||
if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow))
|
||||
allowProfileUsage = allow;
|
||||
|
||||
template = new ChatTemplate
|
||||
{
|
||||
Num = 0,
|
||||
Id = id.ToString(),
|
||||
Name = name,
|
||||
SystemPrompt = systemPrompt,
|
||||
PredefinedUserPrompt = predefinedUserPrompt,
|
||||
ExampleConversation = ParseExampleConversation(idx, table),
|
||||
AllowProfileUsage = allowProfileUsage,
|
||||
IsEnterpriseConfiguration = true,
|
||||
EnterpriseConfigurationPluginId = configPluginId,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<ContentBlock> ParseExampleConversation(int idx, LuaTable table)
|
||||
{
|
||||
var exampleConversation = new List<ContentBlock>();
|
||||
if (!table.TryGetValue("ExampleConversation", out var exConvValue) || !exConvValue.TryRead<LuaTable>(out var exConvTable))
|
||||
return exampleConversation;
|
||||
|
||||
var numBlocks = exConvTable.ArrayLength;
|
||||
for (var j = 1; j <= numBlocks; j++)
|
||||
{
|
||||
var blockValue = exConvTable[j];
|
||||
if (!blockValue.TryRead<LuaTable>(out var blockTable))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} is not a valid table.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blockTable.TryGetValue("Role", out var roleValue) || !roleValue.TryRead<string>(out var roleText) || !Enum.TryParse<ChatRole>(roleText, true, out var parsedRole))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid role.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blockTable.TryGetValue("Content", out var contentValue) || !contentValue.TryRead<string>(out var content))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid content message.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} contains an empty content message.");
|
||||
continue;
|
||||
}
|
||||
|
||||
exampleConversation.Add(new ContentBlock
|
||||
{
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
Role = parsedRole,
|
||||
Content = new ContentText { Text = content },
|
||||
ContentType = ContentType.TEXT,
|
||||
HideFromUser = true,
|
||||
});
|
||||
}
|
||||
|
||||
return exampleConversation;
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ using AIStudio.Provider;
|
||||
using AIStudio.Provider.HuggingFace;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Lua;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
@ -29,8 +31,9 @@ public record Provider(
|
||||
Guid EnterpriseConfigurationPluginId = default,
|
||||
string Hostname = "http://localhost:1234",
|
||||
Host Host = Host.NONE,
|
||||
HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE) : ISecretId, IConfigurationObject
|
||||
HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE) : ConfigurationBaseObject, ISecretId
|
||||
{
|
||||
private static readonly ILogger<Provider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<Provider>();
|
||||
|
||||
public static readonly Provider NONE = new();
|
||||
|
||||
@ -77,7 +80,92 @@ public record Provider(
|
||||
|
||||
#region Implementation of IConfigurationObject
|
||||
|
||||
public string Name => this.InstanceName;
|
||||
public override string Name
|
||||
{
|
||||
get => this.InstanceName;
|
||||
init => this.InstanceName = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static bool TryParseProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider)
|
||||
{
|
||||
provider = NONE;
|
||||
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid ID. The ID must be a valid GUID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("InstanceName", out var instanceNameValue) || !instanceNameValue.TryRead<string>(out var instanceName))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid instance name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("UsedLLMProvider", out var usedLLMProviderValue) || !usedLLMProviderValue.TryRead<string>(out var usedLLMProviderText) || !Enum.TryParse<LLMProviders>(usedLLMProviderText, true, out var usedLLMProvider))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid LLM provider enum value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead<string>(out var hostText) || !Enum.TryParse<Host>(hostText, true, out var host))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid host enum value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Hostname", out var hostnameValue) || !hostnameValue.TryRead<string>(out var hostname))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid hostname.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead<LuaTable>(out var modelTable))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryReadModelTable(idx, modelTable, out var model))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model configuration.");
|
||||
return false;
|
||||
}
|
||||
|
||||
provider = new Provider
|
||||
{
|
||||
Num = 0,
|
||||
Id = id.ToString(),
|
||||
InstanceName = instanceName,
|
||||
UsedLLMProvider = usedLLMProvider,
|
||||
Model = model,
|
||||
IsSelfHosted = usedLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
IsEnterpriseConfiguration = true,
|
||||
EnterpriseConfigurationPluginId = configPluginId,
|
||||
Hostname = hostname,
|
||||
Host = host
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadModelTable(int idx, LuaTable table, out Model model)
|
||||
{
|
||||
model = default;
|
||||
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var id))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model ID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("DisplayName", out var displayNameValue) || !displayNameValue.TryRead<string>(out var displayName))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model display name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
model = new(id, displayName);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public abstract record ConfigurationBaseObject : IConfigurationObject
|
||||
{
|
||||
#region Implementation of IConfigurationObject
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string Id { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract uint Num { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string Name { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsEnterpriseConfiguration { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Guid EnterpriseConfigurationPluginId { get; init; }
|
||||
|
||||
#endregion
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public sealed record NoConfigurationObject : ConfigurationBaseObject
|
||||
{
|
||||
public static readonly NoConfigurationObject INSTANCE = new();
|
||||
|
||||
private NoConfigurationObject()
|
||||
{
|
||||
this.Id = Guid.Empty.ToString();
|
||||
this.Name = "No Configuration";
|
||||
}
|
||||
|
||||
#region Overrides of ConfigurationBaseObject
|
||||
|
||||
public override string Id { get; init; }
|
||||
|
||||
public override uint Num { get; init; }
|
||||
|
||||
public override string Name { get; init; }
|
||||
|
||||
public override bool IsEnterpriseConfiguration { get; init; }
|
||||
|
||||
public override Guid EnterpriseConfigurationPluginId { get; init; }
|
||||
|
||||
#endregion
|
||||
}
|
@ -1,21 +1,15 @@
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Chat;
|
||||
|
||||
using Lua;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
using Model = AIStudio.Provider.Model;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginType type) : PluginBase(isInternal, state, type)
|
||||
{
|
||||
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 = [];
|
||||
private List<PluginConfigurationObject> configObjects = [];
|
||||
|
||||
/// <summary>
|
||||
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
|
||||
@ -51,317 +45,34 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// ===========================================
|
||||
// Configured settings
|
||||
// ===========================================
|
||||
//
|
||||
// Check for the main SETTINGS table:
|
||||
if (!mainTable.TryGetValue("SETTINGS", out var settingsValue) || !settingsValue.TryRead<LuaTable>(out var settingsTable))
|
||||
{
|
||||
message = TB("The SETTINGS table does not exist or is not a valid table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for updates, and if so, how often?
|
||||
// Config: check for updates, and if so, how often?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UpdateBehavior, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Allow the user to add providers?
|
||||
// Config: allow the user to add providers?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun);
|
||||
|
||||
//
|
||||
// Configured providers:
|
||||
//
|
||||
if (!mainTable.TryGetValue("LLM_PROVIDERS", out var providersValue) || !providersValue.TryRead<LuaTable>(out var providersTable))
|
||||
|
||||
// Handle configured LLM providers:
|
||||
if (!PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun))
|
||||
{
|
||||
message = TB("The LLM_PROVIDERS table does not exist or is not a valid table.");
|
||||
message = TB("At least one configured LLM provider is not valid or could not be parsed, or the LLM_PROVIDERS table does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Handle configured chat templates:
|
||||
if (!PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun))
|
||||
{
|
||||
message = TB("At least one configured chat template is not valid or could not be parsed, or the CHAT_TEMPLATES table does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
message = string.Empty;
|
||||
var numberProviders = providersTable.ArrayLength;
|
||||
var configuredProviders = new List<Settings.Provider>(numberProviders);
|
||||
for (var i = 1; i <= numberProviders; i++)
|
||||
{
|
||||
var providerLuaTableValue = providersTable[i];
|
||||
if (!providerLuaTableValue.TryRead<LuaTable>(out var providerLuaTable))
|
||||
{
|
||||
LOGGER.LogWarning($"The LLM_PROVIDERS table at index {i} is not a valid table.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this.TryReadProviderTable(i, providerLuaTable, out var provider))
|
||||
configuredProviders.Add(provider);
|
||||
else
|
||||
LOGGER.LogWarning($"The LLM_PROVIDERS table at index {i} does not contain a valid provider configuration.");
|
||||
}
|
||||
|
||||
//
|
||||
// Apply the configured providers to the system settings:
|
||||
//
|
||||
#pragma warning disable MWAIS0001
|
||||
foreach (var configuredProvider in configuredProviders)
|
||||
{
|
||||
// The iterating variable is immutable, so we need to create a local copy:
|
||||
var provider = configuredProvider;
|
||||
|
||||
// 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
|
||||
|
||||
//
|
||||
// Configured chat templates:
|
||||
//
|
||||
if (mainTable.TryGetValue("CHAT_TEMPLATES", out var templatesValue) && templatesValue.TryRead<LuaTable>(out var templatesTable))
|
||||
{
|
||||
var numberTemplates = templatesTable.ArrayLength;
|
||||
var configuredTemplates = new List<ChatTemplate>(numberTemplates);
|
||||
for (var i = 1; i <= numberTemplates; i++)
|
||||
{
|
||||
var templateLuaTableValue = templatesTable[i];
|
||||
if (!templateLuaTableValue.TryRead<LuaTable>(out var templateLuaTable))
|
||||
{
|
||||
LOGGER.LogWarning($"The CHAT_TEMPLATES table at index {i} is not a valid table.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this.TryReadChatTemplateTable(i, templateLuaTable, out var template) && template != ChatTemplate.NO_CHAT_TEMPLATE)
|
||||
configuredTemplates.Add(template);
|
||||
else
|
||||
LOGGER.LogWarning($"The CHAT_TEMPLATES table at index {i} does not contain a valid chat template configuration.");
|
||||
}
|
||||
|
||||
// Apply configured chat templates to the system settings:
|
||||
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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadProviderTable(int idx, LuaTable table, out Settings.Provider provider)
|
||||
{
|
||||
provider = default;
|
||||
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid ID. The ID must be a valid GUID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("InstanceName", out var instanceNameValue) || !instanceNameValue.TryRead<string>(out var instanceName))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid instance name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("UsedLLMProvider", out var usedLLMProviderValue) || !usedLLMProviderValue.TryRead<string>(out var usedLLMProviderText) || !Enum.TryParse<LLMProviders>(usedLLMProviderText, true, out var usedLLMProvider))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid LLM provider enum value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead<string>(out var hostText) || !Enum.TryParse<Host>(hostText, true, out var host))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid host enum value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Hostname", out var hostnameValue) || !hostnameValue.TryRead<string>(out var hostname))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid hostname.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead<LuaTable>(out var modelTable))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.TryReadModelTable(idx, modelTable, out var model))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model configuration.");
|
||||
return false;
|
||||
}
|
||||
|
||||
provider = new()
|
||||
{
|
||||
Num = 0,
|
||||
Id = id.ToString(),
|
||||
InstanceName = instanceName,
|
||||
UsedLLMProvider = usedLLMProvider,
|
||||
Model = model,
|
||||
IsSelfHosted = usedLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
IsEnterpriseConfiguration = true,
|
||||
EnterpriseConfigurationPluginId = this.Id,
|
||||
Hostname = hostname,
|
||||
Host = host
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadModelTable(int idx, LuaTable table, out Model model)
|
||||
{
|
||||
model = default;
|
||||
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var id))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model ID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("DisplayName", out var displayNameValue) || !displayNameValue.TryRead<string>(out var displayName))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model display name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
model = new(id, displayName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadChatTemplateTable(int idx, LuaTable table, out ChatTemplate template)
|
||||
{
|
||||
template = ChatTemplate.NO_CHAT_TEMPLATE;
|
||||
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid ID. The ID must be a valid GUID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead<string>(out var name))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!table.TryGetValue("SystemPrompt", out var sysPromptValue) || !sysPromptValue.TryRead<string>(out var systemPrompt))
|
||||
{
|
||||
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid system prompt.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var predefinedUserPrompt = string.Empty;
|
||||
if (table.TryGetValue("PredefinedUserPrompt", out var preUserValue) && preUserValue.TryRead<string>(out var preUser))
|
||||
predefinedUserPrompt = preUser;
|
||||
|
||||
var allowProfileUsage = false;
|
||||
if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow))
|
||||
allowProfileUsage = allow;
|
||||
|
||||
template = new()
|
||||
{
|
||||
Num = 0,
|
||||
Id = id.ToString(),
|
||||
Name = name,
|
||||
SystemPrompt = systemPrompt,
|
||||
PredefinedUserPrompt = predefinedUserPrompt,
|
||||
ExampleConversation = ParseExampleConversation(idx, table),
|
||||
AllowProfileUsage = allowProfileUsage,
|
||||
IsEnterpriseConfiguration = true,
|
||||
EnterpriseConfigurationPluginId = this.Id
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<ContentBlock> ParseExampleConversation(int idx, LuaTable table)
|
||||
{
|
||||
var exampleConversation = new List<ContentBlock>();
|
||||
if (!table.TryGetValue("ExampleConversation", out var exConvValue) || !exConvValue.TryRead<LuaTable>(out var exConvTable))
|
||||
return exampleConversation;
|
||||
|
||||
var numBlocks = exConvTable.ArrayLength;
|
||||
for (var j = 1; j <= numBlocks; j++)
|
||||
{
|
||||
var blockValue = exConvTable[j];
|
||||
if (!blockValue.TryRead<LuaTable>(out var blockTable))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} is not a valid table.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blockTable.TryGetValue("Role", out var roleValue) || !roleValue.TryRead<string>(out var roleText) || !Enum.TryParse<ChatRole>(roleText, true, out var parsedRole))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid role.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blockTable.TryGetValue("Content", out var contentValue) || !contentValue.TryRead<string>(out var content))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid content message.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} contains an empty content message.");
|
||||
continue;
|
||||
}
|
||||
|
||||
exampleConversation.Add(new ContentBlock
|
||||
{
|
||||
Time = DateTimeOffset.UtcNow,
|
||||
Role = parsedRole,
|
||||
Content = new ContentText { Text = content },
|
||||
ContentType = ContentType.TEXT,
|
||||
HideFromUser = true,
|
||||
});
|
||||
}
|
||||
|
||||
return exampleConversation;
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ using System.Linq.Expressions;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
using Lua;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
/// <summary>
|
||||
@ -29,6 +31,129 @@ public sealed record PluginConfigurationObject
|
||||
/// </summary>
|
||||
public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// Parses Lua table entries into configuration objects of the specified type, populating the
|
||||
/// provided list with results.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClass">The type of configuration object to parse, which must
|
||||
/// inherit from <see cref="ConfigurationBaseObject"/>.</typeparam>
|
||||
/// <param name="configObjectType">The type of configuration object to process, as specified
|
||||
/// in <see cref="PluginConfigurationObjectType"/>.</param>
|
||||
/// <param name="configObjectSelection">An expression to retrieve existing configuration objects from
|
||||
/// the main configuration data.</param>
|
||||
/// <param name="nextConfigObjectNumSelection">An expression to retrieve the next available configuration
|
||||
/// object number from the main configuration data.</param>
|
||||
/// <param name="mainTable">The Lua table containing entries to parse into configuration objects.</param>
|
||||
/// <param name="configPluginId">The unique identifier of the plugin associated with the configuration
|
||||
/// objects being parsed.</param>
|
||||
/// <param name="configObjects">The list to populate with the parsed configuration objects.
|
||||
/// This parameter is passed by reference.</param>
|
||||
/// <param name="dryRun">Specifies whether to perform the operation as a dry run, where changes
|
||||
/// are not persisted.</param>
|
||||
/// <returns>Returns true if parsing succeeds and configuration objects are added
|
||||
/// to the list; otherwise, false.</returns>
|
||||
public static bool TryParse<TClass>(
|
||||
PluginConfigurationObjectType configObjectType,
|
||||
Expression<Func<Data, List<TClass>>> configObjectSelection,
|
||||
Expression<Func<Data, uint>> nextConfigObjectNumSelection,
|
||||
LuaTable mainTable,
|
||||
Guid configPluginId,
|
||||
ref List<PluginConfigurationObject> configObjects,
|
||||
bool dryRun
|
||||
) where TClass : ConfigurationBaseObject
|
||||
{
|
||||
var luaTableName = configObjectType switch
|
||||
{
|
||||
PluginConfigurationObjectType.LLM_PROVIDER => "LLM_PROVIDERS",
|
||||
PluginConfigurationObjectType.CHAT_TEMPLATE => "CHAT_TEMPLATES",
|
||||
PluginConfigurationObjectType.DATA_SOURCE => "DATA_SOURCES",
|
||||
PluginConfigurationObjectType.EMBEDDING_PROVIDER => "EMBEDDING_PROVIDERS",
|
||||
PluginConfigurationObjectType.PROFILE => "PROFILES",
|
||||
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (luaTableName is null)
|
||||
{
|
||||
LOG.LogError($"The configuration object type '{configObjectType}' is not supported yet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mainTable.TryGetValue(luaTableName, out var luaValue) || !luaValue.TryRead<LuaTable>(out var luaTable))
|
||||
{
|
||||
LOG.LogWarning($"The {luaTableName} table does not exist or is not a valid table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var storedObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData);
|
||||
var numberObjects = luaTable.ArrayLength;
|
||||
ThreadSafeRandom? random = null;
|
||||
for (var i = 1; i <= numberObjects; i++)
|
||||
{
|
||||
var luaObjectTableValue = luaTable[i];
|
||||
if (!luaObjectTableValue.TryRead<LuaTable>(out var luaObjectTable))
|
||||
{
|
||||
LOG.LogWarning($"The {luaObjectTable} table at index {i} is not a valid table.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var (wasParsingSuccessful, configObject) = configObjectType switch
|
||||
{
|
||||
PluginConfigurationObjectType.LLM_PROVIDER => (Settings.Provider.TryParseProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Settings.Provider.NONE, configurationObject),
|
||||
PluginConfigurationObjectType.CHAT_TEMPLATE => (ChatTemplate.TryParseChatTemplateTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != ChatTemplate.NO_CHAT_TEMPLATE, configurationObject),
|
||||
|
||||
_ => (false, NoConfigurationObject.INSTANCE)
|
||||
};
|
||||
|
||||
if (wasParsingSuccessful)
|
||||
{
|
||||
// Store it in the config object list:
|
||||
configObjects.Add(new()
|
||||
{
|
||||
ConfigPluginId = configPluginId,
|
||||
Id = Guid.Parse(configObject.Id),
|
||||
Type = configObjectType,
|
||||
});
|
||||
|
||||
if (dryRun)
|
||||
continue;
|
||||
|
||||
var objectIndex = storedObjects.FindIndex(t => t.Id == configObject.Id);
|
||||
|
||||
// Case: The object already exists, we update it:
|
||||
if (objectIndex > -1)
|
||||
{
|
||||
var existingObject = storedObjects[objectIndex];
|
||||
configObject = configObject with { Num = existingObject.Num };
|
||||
storedObjects[objectIndex] = (TClass)configObject;
|
||||
}
|
||||
|
||||
// Case: The object does not exist, we have to add it
|
||||
else
|
||||
{
|
||||
// Case: Increment the next number was successful
|
||||
if (nextConfigObjectNumSelection.TryIncrement(SETTINGS_MANAGER.ConfigurationData) is { Success: true, UpdatedValue: var nextNum })
|
||||
{
|
||||
configObject = configObject with { Num = nextNum };
|
||||
storedObjects.Add((TClass)configObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Case: The next number could not be incremented, we use a random number
|
||||
random ??= new ThreadSafeRandom();
|
||||
configObject = configObject with { Num = (uint)random.Next(500_000, 1_000_000) };
|
||||
storedObjects.Add((TClass)configObject);
|
||||
LOG.LogWarning($"The next number for the configuration object '{configObject.Name}' (id={configObject.Id}) could not be incremented. Using a random number instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG.LogWarning($"The {luaObjectTable} table at index {i} does not contain a valid chat template configuration.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up configuration objects of a specified type that are no longer associated with any available plugin.
|
||||
/// </summary>
|
||||
|
Loading…
Reference in New Issue
Block a user