Added embedding provider config (#622)

This commit is contained in:
Thorsten Sommer 2026-01-09 15:41:54 +01:00 committed by GitHub
parent 30197cf507
commit 7110fb866f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 166 additions and 26 deletions

View File

@ -36,18 +36,27 @@
<MudTd>@context.Name</MudTd> <MudTd>@context.Name</MudTd>
<MudTd>@context.UsedLLMProvider.ToName()</MudTd> <MudTd>@context.UsedLLMProvider.ToName()</MudTd>
<MudTd>@GetEmbeddingProviderModelName(context)</MudTd> <MudTd>@GetEmbeddingProviderModelName(context)</MudTd>
<MudTd> <MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap"> <MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
<MudTooltip Text="@T("Open Dashboard")"> @if (context.IsEnterpriseConfiguration)
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"/> {
</MudTooltip> <MudTooltip Text="@T("This embedding provider is managed by your organization.")">
<MudTooltip Text="@T("Edit")"> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditEmbeddingProvider(context))"/> </MudTooltip>
</MudTooltip> }
<MudTooltip Text="@T("Delete")"> else
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/> {
</MudTooltip> <MudTooltip Text="@T("Open Dashboard")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"/>
</MudTooltip>
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditEmbeddingProvider(context))"/>
</MudTooltip>
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
</MudTooltip>
}
</MudStack> </MudStack>
</MudTd> </MudTd>
</RowTemplate> </RowTemplate>

View File

@ -96,7 +96,7 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
#endregion #endregion
private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted; private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsSelfHosted ?? false;
private DataSourceLocalDirectory CreateDataSource() => new() private DataSourceLocalDirectory CreateDataSource() => new()
{ {

View File

@ -27,7 +27,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId); this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId) ?? EmbeddingProvider.NONE;
this.directoryInfo = new DirectoryInfo(this.DataSource.Path); this.directoryInfo = new DirectoryInfo(this.DataSource.Path);
if (this.directoryInfo.Exists) if (this.directoryInfo.Exists)
@ -46,7 +46,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
private readonly CancellationTokenSource cts = new(); private readonly CancellationTokenSource cts = new();
private EmbeddingProvider embeddingProvider; private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
private DirectoryInfo directoryInfo = null!; private DirectoryInfo directoryInfo = null!;
private long directorySizeBytes; private long directorySizeBytes;
private long directorySizeNumFiles; private long directorySizeNumFiles;

View File

@ -96,7 +96,7 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
#endregion #endregion
private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted; private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsSelfHosted ?? false;
private DataSourceLocalFile CreateDataSource() => new() private DataSourceLocalFile CreateDataSource() => new()
{ {

View File

@ -18,14 +18,14 @@ public partial class DataSourceLocalFileInfoDialog : MSGComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId); this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId) ?? EmbeddingProvider.NONE;
this.fileInfo = new FileInfo(this.DataSource.FilePath); this.fileInfo = new FileInfo(this.DataSource.FilePath);
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
#endregion #endregion
private EmbeddingProvider embeddingProvider; private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
private FileInfo fileInfo = null!; private FileInfo fileInfo = null!;
private bool IsCloudEmbedding => !this.embeddingProvider.IsSelfHosted; private bool IsCloudEmbedding => !this.embeddingProvider.IsSelfHosted;

View File

@ -129,6 +129,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED, IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname, Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
Host = this.DataHost, Host = this.DataHost,
IsEnterpriseConfiguration = false,
EnterpriseConfigurationPluginId = Guid.Empty,
}; };
} }

View File

@ -70,6 +70,24 @@ CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
} }
} }
-- 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"] = "<user-friendly name for the embedding provider>",
-- ["UsedLLMProvider"] = "SELF_HOSTED",
--
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
-- ["Host"] = "OLLAMA",
-- ["Hostname"] = "<https address of the server>",
-- ["Model"] = {
-- ["Id"] = "<the model ID, e.g., nomic-embed-text>",
-- ["DisplayName"] = "<user-friendly name of the model>",
-- }
-- }
CONFIG["SETTINGS"] = {} CONFIG["SETTINGS"] = {}
-- Configure the update check interval: -- Configure the update check interval:

View File

@ -1,32 +1,134 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Tools.PluginSystem;
using Lua;
using Host = AIStudio.Provider.SelfHosted.Host; using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Settings; namespace AIStudio.Settings;
public readonly record struct EmbeddingProvider( public sealed record EmbeddingProvider(
uint Num, uint Num,
string Id, string Id,
string Name, string Name,
LLMProviders UsedLLMProvider, LLMProviders UsedLLMProvider,
Model Model, Model Model,
bool IsSelfHosted = false, bool IsSelfHosted = false,
bool IsEnterpriseConfiguration = false,
Guid EnterpriseConfigurationPluginId = default,
string Hostname = "http://localhost:1234", string Hostname = "http://localhost:1234",
Host Host = Host.NONE) : ISecretId Host Host = Host.NONE) : ConfigurationBaseObject, ISecretId
{ {
private static readonly ILogger<EmbeddingProvider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<EmbeddingProvider>();
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; public override string ToString() => this.Name;
#region Implementation of ISecretId #region Implementation of ISecretId
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore] [JsonIgnore]
public string SecretId => this.Id; public string SecretId => this.Id;
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore] [JsonIgnore]
public string SecretName => this.Name; public string SecretName => this.Name;
#endregion #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<string>(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<string>(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<string>(out var usedLLMProviderText) || !Enum.TryParse<LLMProviders>(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<string>(out var hostText) || !Enum.TryParse<Host>(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<string>(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<LuaTable>(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<string>(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<string>(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;
}
} }

View File

@ -69,7 +69,10 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Handle configured LLM providers: // Handle configured LLM providers:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun); PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, 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: // Handle configured chat templates:
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun); PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun);

View File

@ -102,7 +102,8 @@ public sealed record PluginConfigurationObject
PluginConfigurationObjectType.LLM_PROVIDER => (Settings.Provider.TryParseProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Settings.Provider.NONE, configurationObject), 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), 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.PROFILE => (Profile.TryParseProfileTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Profile.NO_PROFILE, configurationObject),
PluginConfigurationObjectType.EMBEDDING_PROVIDER => (EmbeddingProvider.TryParseEmbeddingProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != EmbeddingProvider.NONE, configurationObject),
_ => (false, NoConfigurationObject.INSTANCE) _ => (false, NoConfigurationObject.INSTANCE)
}; };

View File

@ -132,7 +132,11 @@ public static partial class PluginFactory
// Check LLM providers: // Check LLM providers:
var wasConfigurationChanged = PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList); var wasConfigurationChanged = PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList);
// Check embedding providers:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, configObjectList))
wasConfigurationChanged = true;
// Check chat templates: // Check chat templates:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList)) if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList))
wasConfigurationChanged = true; wasConfigurationChanged = true;

View File

@ -3,5 +3,6 @@
- Added the option to use source code files in chats and document analysis. This supports software development workflows. - Added the option to use source code files in chats and document analysis. This supports software development workflows.
- Added a preview feature that lets you record your own voice in preparation for the transcription feature. The feature remains in development and appears only when the preview feature is enabled. - Added a preview feature that lets you record your own voice in preparation for the transcription feature. 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. - 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.
- 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. - 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. - 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.