Added local RAG (#756)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Paul KoudelkaandThorsten Sommer authored and GitHub committed 2026-09-09 18:43:37 +02:00
1 parent d043fbc8f0
commit c7b42bee96
179 files changed
+276977 -1421

No files matched your search

@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using AIStudio.Settings;
@@ -206,6 +207,43 @@ public sealed record PluginConfigurationObject
return true;
}
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "Tokenizer synchronization needs indexed access to update enterprise-managed providers in place.")]
public static async Task<bool> SyncManagedTokenizersAsync(Guid configPluginId, string pluginPath)
{
var wasConfigurationChanged = false;
var localSettingsManager = SettingsManagerAccess;
for (var i = 0; i < localSettingsManager.ConfigurationData.Providers.Count; i++)
{
var provider = localSettingsManager.ConfigurationData.Providers[i];
if (!provider.IsEnterpriseConfiguration || provider.EnterpriseConfigurationPluginId != configPluginId)
continue;
var syncedProvider = await SyncProviderTokenizerAsync(provider, pluginPath);
if (syncedProvider == provider)
continue;
localSettingsManager.ConfigurationData.Providers[i] = syncedProvider;
wasConfigurationChanged = true;
}
for (var i = 0; i < localSettingsManager.ConfigurationData.EmbeddingProviders.Count; i++)
{
var provider = localSettingsManager.ConfigurationData.EmbeddingProviders[i];
if (!provider.IsEnterpriseConfiguration || provider.EnterpriseConfigurationPluginId != configPluginId)
continue;
var syncedProvider = await SyncEmbeddingTokenizerAsync(provider, pluginPath);
if (syncedProvider == provider)
continue;
localSettingsManager.ConfigurationData.EmbeddingProviders[i] = syncedProvider;
wasConfigurationChanged = true;
}
return wasConfigurationChanged;
}
/// <summary>
/// Parses configured data sources from a configuration plugin.
/// </summary>
@@ -396,6 +434,19 @@ public sealed record PluginConfigurationObject
var wasConfigurationChanged = leftOverObjects.Count > 0;
foreach (var item in leftOverObjects.Distinct())
{
if (item is Settings.Provider provider)
{
var deleteTokenizerResult = await RustService.DeleteTokenizer(TokenizerModelId.ForProvider(provider));
if (!deleteTokenizerResult.Success)
LOG.LogWarning("Failed to delete tokenizer for removed enterprise provider '{ProviderName}': {Issue}", provider.InstanceName, deleteTokenizerResult.Message);
}
else if (item is EmbeddingProvider embeddingProvider)
{
var deleteTokenizerResult = await RustService.DeleteTokenizer(TokenizerModelId.ForEmbeddingProvider(embeddingProvider));
if (!deleteTokenizerResult.Success)
LOG.LogWarning("Failed to delete tokenizer for removed enterprise embedding provider '{ProviderName}': {Issue}", embeddingProvider.Name, deleteTokenizerResult.Message);
}
configuredObjects.Remove(item);
// Delete the API key from the OS keyring if the removed object has one:
@@ -426,4 +477,89 @@ public sealed record PluginConfigurationObject
return wasConfigurationChanged;
}
private static async Task<Settings.Provider> SyncProviderTokenizerAsync(Settings.Provider provider, string pluginPath)
{
var syncedTokenizerPath = await SyncTokenizerAsync(
provider.TokenizerPath,
pluginPath,
TokenizerModelId.ForProvider(provider),
$"provider '{provider.InstanceName}'");
return provider with { TokenizerPath = syncedTokenizerPath };
}
private static async Task<EmbeddingProvider> SyncEmbeddingTokenizerAsync(EmbeddingProvider provider, string pluginPath)
{
var syncedTokenizerPath = await SyncTokenizerAsync(
provider.TokenizerPath,
pluginPath,
TokenizerModelId.ForEmbeddingProvider(provider),
$"embedding provider '{provider.Name}'");
return provider with { TokenizerPath = syncedTokenizerPath };
}
private static async Task<string> SyncTokenizerAsync(string configuredTokenizerPath, string pluginPath, string modelId, string logName)
{
if (string.IsNullOrWhiteSpace(configuredTokenizerPath))
{
var deleteResult = await RustService.DeleteTokenizer(modelId);
if (!deleteResult.Success)
LOG.LogWarning("Failed to delete tokenizer for {LogName}: {Issue}", logName, deleteResult.Message);
return string.Empty;
}
var resolvedPath = ResolvePluginTokenizerPath(configuredTokenizerPath, pluginPath);
if (resolvedPath is null)
{
var deleteResult = await RustService.DeleteTokenizer(modelId);
if (!deleteResult.Success)
LOG.LogWarning("Failed to delete tokenizer after invalid path for {LogName}: {Issue}", logName, deleteResult.Message);
LOG.LogWarning("The configured tokenizer path '{TokenizerPath}' for {LogName} is invalid. The tokenizer path must stay within the plugin directory '{PluginPath}'.", configuredTokenizerPath, logName, pluginPath);
return string.Empty;
}
var validateResult = await RustService.ValidateTokenizer(resolvedPath);
if (!validateResult.Success)
{
var deleteResult = await RustService.DeleteTokenizer(modelId);
if (!deleteResult.Success)
LOG.LogWarning("Failed to delete tokenizer after validation failure for {LogName}: {Issue}", logName, deleteResult.Message);
LOG.LogWarning("The configured tokenizer for {LogName} is invalid. Path='{TokenizerPath}', issue='{Issue}'", logName, resolvedPath, validateResult.Message);
return string.Empty;
}
var storeResult = await RustService.StoreTokenizer(modelId, resolvedPath);
if (!storeResult.Success)
{
LOG.LogWarning("Failed to store tokenizer for {LogName}. Path='{TokenizerPath}', issue='{Issue}'", logName, resolvedPath, storeResult.Message);
return string.Empty;
}
return storeResult.StoredPath;
}
private static string? ResolvePluginTokenizerPath(string configuredTokenizerPath, string pluginPath)
{
if (string.IsNullOrWhiteSpace(pluginPath))
return null;
var fullPluginPath = Path.GetFullPath(pluginPath);
var candidatePath = Path.GetFullPath(Path.Combine(fullPluginPath, configuredTokenizerPath));
if (candidatePath.Equals(fullPluginPath, StringComparison.OrdinalIgnoreCase))
return null;
var pluginPrefix = fullPluginPath.EndsWith(Path.DirectorySeparatorChar)
? fullPluginPath
: fullPluginPath + Path.DirectorySeparatorChar;
return candidatePath.StartsWith(pluginPrefix, StringComparison.OrdinalIgnoreCase)
? candidatePath
: null;
}
}