mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-10-09 08:40: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.Chat;
|
||||||
using AIStudio.Tools.PluginSystem;
|
using AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
|
using Lua;
|
||||||
|
|
||||||
namespace AIStudio.Settings;
|
namespace AIStudio.Settings;
|
||||||
|
|
||||||
public record ChatTemplate(
|
public record ChatTemplate(
|
||||||
@ -12,7 +14,7 @@ public record ChatTemplate(
|
|||||||
List<ContentBlock> ExampleConversation,
|
List<ContentBlock> ExampleConversation,
|
||||||
bool AllowProfileUsage,
|
bool AllowProfileUsage,
|
||||||
bool IsEnterpriseConfiguration = false,
|
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)
|
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 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()
|
public static readonly ChatTemplate NO_CHAT_TEMPLATE = new()
|
||||||
{
|
{
|
||||||
Name = TB("Use no chat template"),
|
Name = TB("Use no chat template"),
|
||||||
@ -50,4 +54,96 @@ public record ChatTemplate(
|
|||||||
|
|
||||||
return this.SystemPrompt;
|
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.Provider.HuggingFace;
|
||||||
using AIStudio.Tools.PluginSystem;
|
using AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
|
using Lua;
|
||||||
|
|
||||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||||
|
|
||||||
namespace AIStudio.Settings;
|
namespace AIStudio.Settings;
|
||||||
@ -29,8 +31,9 @@ public record Provider(
|
|||||||
Guid EnterpriseConfigurationPluginId = default,
|
Guid EnterpriseConfigurationPluginId = default,
|
||||||
string Hostname = "http://localhost:1234",
|
string Hostname = "http://localhost:1234",
|
||||||
Host Host = Host.NONE,
|
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();
|
public static readonly Provider NONE = new();
|
||||||
|
|
||||||
@ -77,7 +80,92 @@ public record Provider(
|
|||||||
|
|
||||||
#region Implementation of IConfigurationObject
|
#region Implementation of IConfigurationObject
|
||||||
|
|
||||||
public string Name => this.InstanceName;
|
public override string Name
|
||||||
|
{
|
||||||
|
get => this.InstanceName;
|
||||||
|
init => this.InstanceName = value;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#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.Settings;
|
||||||
using AIStudio.Chat;
|
|
||||||
|
|
||||||
using Lua;
|
using Lua;
|
||||||
|
|
||||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
|
||||||
using Model = AIStudio.Provider.Model;
|
|
||||||
|
|
||||||
namespace AIStudio.Tools.PluginSystem;
|
namespace AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginType type) : PluginBase(isInternal, state, type)
|
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 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 static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||||
|
|
||||||
private readonly List<PluginConfigurationObject> configObjects = [];
|
private List<PluginConfigurationObject> configObjects = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
|
/// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// Check for the main SETTINGS table:
|
||||||
// ===========================================
|
|
||||||
// Configured settings
|
|
||||||
// ===========================================
|
|
||||||
//
|
|
||||||
if (!mainTable.TryGetValue("SETTINGS", out var settingsValue) || !settingsValue.TryRead<LuaTable>(out var settingsTable))
|
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.");
|
message = TB("The SETTINGS table does not exist or is not a valid table.");
|
||||||
return false;
|
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);
|
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);
|
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun);
|
||||||
|
|
||||||
//
|
// Handle configured LLM providers:
|
||||||
// Configured providers:
|
if (!PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun))
|
||||||
//
|
|
||||||
if (!mainTable.TryGetValue("LLM_PROVIDERS", out var providersValue) || !providersValue.TryRead<LuaTable>(out var providersTable))
|
|
||||||
{
|
{
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
message = string.Empty;
|
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;
|
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;
|
||||||
using AIStudio.Settings.DataModel;
|
using AIStudio.Settings.DataModel;
|
||||||
|
|
||||||
|
using Lua;
|
||||||
|
|
||||||
namespace AIStudio.Tools.PluginSystem;
|
namespace AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -29,6 +31,129 @@ public sealed record PluginConfigurationObject
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE;
|
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>
|
/// <summary>
|
||||||
/// Cleans up configuration objects of a specified type that are no longer associated with any available plugin.
|
/// Cleans up configuration objects of a specified type that are no longer associated with any available plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
Reference in New Issue
Block a user