From 0819e91eb41048c18f64a154b06da64793aa25a7 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 9 Jan 2026 15:30:34 +0100 Subject: [PATCH] Add support for configurable embedding providers --- .../Settings/SettingsPanelEmbeddings.razor | 31 +++-- .../Dialogs/EmbeddingProviderDialog.razor.cs | 2 + .../Plugins/configuration/plugin.lua | 18 +++ .../Settings/EmbeddingProvider.cs | 114 +++++++++++++++++- .../Tools/PluginSystem/PluginConfiguration.cs | 3 + .../PluginSystem/PluginConfigurationObject.cs | 1 + .../PluginSystem/PluginFactory.Loading.cs | 4 + .../wwwroot/changelog/v26.1.1.md | 1 + 8 files changed, 157 insertions(+), 17 deletions(-) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor index 42dd29f9..4ffc743f 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor @@ -36,18 +36,27 @@ @context.Name @context.UsedLLMProvider.ToName() @GetEmbeddingProviderModelName(context) - + - - - - - - - - - - + + @if (context.IsEnterpriseConfiguration) + { + + + + } + else + { + + + + + + + + + + } diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs index d586a213..a6677686 100644 --- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs @@ -129,6 +129,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED, Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname, Host = this.DataHost, + IsEnterpriseConfiguration = false, + EnterpriseConfigurationPluginId = Guid.Empty, }; } diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 823669c0..7441eabc 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -88,6 +88,24 @@ CONFIG["TRANSCRIPTION_PROVIDERS"] = {} -- } -- } +-- Embedding providers for local RAG (Retrieval-Augmented Generation) functionality: +CONFIG["EMBEDDING_PROVIDERS"] = {} + +-- An example of an embedding provider configuration: +-- CONFIG["EMBEDDING_PROVIDERS"][#CONFIG["EMBEDDING_PROVIDERS"]+1] = { +-- ["Id"] = "00000000-0000-0000-0000-000000000000", +-- ["Name"] = "", +-- ["UsedLLMProvider"] = "SELF_HOSTED", +-- +-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM +-- ["Host"] = "OLLAMA", +-- ["Hostname"] = "", +-- ["Model"] = { +-- ["Id"] = "", +-- ["DisplayName"] = "", +-- } +-- } + CONFIG["SETTINGS"] = {} -- Configure the update check interval: diff --git a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs index 126a0be2..c5e48d4f 100644 --- a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs +++ b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs @@ -1,32 +1,134 @@ using System.Text.Json.Serialization; using AIStudio.Provider; +using AIStudio.Tools.PluginSystem; + +using Lua; using Host = AIStudio.Provider.SelfHosted.Host; namespace AIStudio.Settings; -public readonly record struct EmbeddingProvider( +public sealed record EmbeddingProvider( uint Num, string Id, string Name, LLMProviders UsedLLMProvider, Model Model, bool IsSelfHosted = false, + bool IsEnterpriseConfiguration = false, + Guid EnterpriseConfigurationPluginId = default, string Hostname = "http://localhost:1234", - Host Host = Host.NONE) : ISecretId + Host Host = Host.NONE) : ConfigurationBaseObject, ISecretId { + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + + public static readonly EmbeddingProvider NONE = new(); + + public EmbeddingProvider() : this( + 0, + Guid.Empty.ToString(), + string.Empty, + LLMProviders.NONE, + default, + false, + false, + Guid.Empty) + { + } + public override string ToString() => this.Name; - + #region Implementation of ISecretId - + /// [JsonIgnore] public string SecretId => this.Id; - + /// [JsonIgnore] public string SecretName => this.Name; - + #endregion + + public static bool TryParseEmbeddingProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider) + { + provider = NONE; + if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(out var idText) || !Guid.TryParse(idText, out var id)) + { + LOGGER.LogWarning($"The configured embedding provider {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(out var name)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid name."); + return false; + } + + if (!table.TryGetValue("UsedLLMProvider", out var usedLLMProviderValue) || !usedLLMProviderValue.TryRead(out var usedLLMProviderText) || !Enum.TryParse(usedLLMProviderText, true, out var usedLLMProvider)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid LLM provider enum value."); + return false; + } + + if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead(out var hostText) || !Enum.TryParse(hostText, true, out var host)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid host enum value."); + return false; + } + + if (!table.TryGetValue("Hostname", out var hostnameValue) || !hostnameValue.TryRead(out var hostname)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid hostname."); + return false; + } + + if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead(out var modelTable)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model table."); + return false; + } + + if (!TryReadModelTable(idx, modelTable, out var model)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model configuration."); + return false; + } + + provider = new EmbeddingProvider + { + Num = 0, + Id = id.ToString(), + Name = name, + 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(out var id)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model ID."); + return false; + } + + if (!table.TryGetValue("DisplayName", out var displayNameValue) || !displayNameValue.TryRead(out var displayName)) + { + LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model display name."); + return false; + } + + model = new(id, displayName); + return true; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 3b3cb5f1..76148218 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -73,6 +73,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Handle configured transcription providers: PluginConfigurationObject.TryParse(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, x => x.NextTranscriptionNum, mainTable, this.Id, ref this.configObjects, dryRun); + // Handle configured embedding providers: + PluginConfigurationObject.TryParse(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, x => x.NextEmbeddingNum, mainTable, this.Id, ref this.configObjects, dryRun); + // Handle configured chat templates: PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs index 2f04fd46..da5c46c2 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -104,6 +104,7 @@ public sealed record PluginConfigurationObject PluginConfigurationObjectType.CHAT_TEMPLATE => (ChatTemplate.TryParseChatTemplateTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != ChatTemplate.NO_CHAT_TEMPLATE, configurationObject), PluginConfigurationObjectType.PROFILE => (Profile.TryParseProfileTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Profile.NO_PROFILE, configurationObject), PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER => (TranscriptionProvider.TryParseTranscriptionProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != TranscriptionProvider.NONE, configurationObject), + PluginConfigurationObjectType.EMBEDDING_PROVIDER => (EmbeddingProvider.TryParseEmbeddingProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != EmbeddingProvider.NONE, configurationObject), _ => (false, NoConfigurationObject.INSTANCE) }; diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index 38ff3977..2efbde97 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -137,6 +137,10 @@ public static partial class PluginFactory if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, configObjectList)) wasConfigurationChanged = true; + // Check embedding providers: + if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, configObjectList)) + wasConfigurationChanged = true; + // Check chat templates: if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList)) wasConfigurationChanged = true; diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md index f7287390..1f241e20 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.1.1.md @@ -4,5 +4,6 @@ - Added a preview feature that lets you record your own voice for transcription. The feature remains in development and appears only when the preview feature is enabled. - Added the option to configure transcription providers (for example, using Whisper models). As usual, there can be local as well as cloud models configured. This option is part of the transcription preview and remains hidden until the preview is activated or the feature gets released. Transcription providers can be configured through configuration plugins as well. - Added an option to the app settings to select a provider for transcribing dictated text. If no provider is selected, dictation and text transcription are disabled. +- Added the option to configure embedding providers through a config plugin and distribute them within an organization. - Improved the app versioning. Starting in 2026, each version number includes the year, followed by the month. The last digit shows the release number for that month. For example, version `26.1.1` is the first release in January 2026. - Fixed a bug in the profile selection where the "Use no profile" entry could not be localized, causing English text to appear in languages such as German. This behavior has now been fixed.