mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-08 00:09:40 +00:00
Added local RAG (#756)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
d043fbc8f0
commit
c7b42bee96
179 files changed
+276977
-1421
No files matched your search
@@ -2,11 +2,12 @@ using AIStudio.Components;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Provider.HuggingFace;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
using AIStudio.Tools.Validation;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
@@ -82,6 +83,15 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
[Parameter]
|
||||
public bool IsEditing { get; init; }
|
||||
|
||||
[Parameter]
|
||||
public string DataTokenizerPath { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public int DataTokenLimit { get; set; } = EmbeddingProvider.DEFAULT_TOKEN_LIMIT;
|
||||
|
||||
[Parameter]
|
||||
public int DataEmbeddingBatchSize { get; set; } = EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this embedding provider is managed by an enterprise configuration plugin. When true,
|
||||
/// every field except the API key is locked, matching Settings.EmbeddingProvider.IsEnterpriseConfiguration.
|
||||
@@ -110,6 +120,13 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
private string dataAPIKeyStorageIssue = string.Empty;
|
||||
private string dataEditingPreviousInstanceName = string.Empty;
|
||||
private string dataLoadingModelsIssue = string.Empty;
|
||||
private string dataFilePath = string.Empty;
|
||||
private string dataCustomTokenizerValidationIssue = string.Empty;
|
||||
private Task dataTokenizerValidationTask = Task.CompletedTask;
|
||||
private bool dataStoreWasAttempted;
|
||||
private bool isTokenizerFileDialogOpen;
|
||||
private bool showExpertSettings;
|
||||
private int dataTokenizerValidationRevision;
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
private MudForm form = null!;
|
||||
@@ -117,7 +134,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
private readonly List<Model> availableModels = new();
|
||||
private readonly Encryption encryption = Program.ENCRYPTION;
|
||||
private readonly ProviderValidation providerValidation;
|
||||
|
||||
|
||||
public EmbeddingProviderDialog()
|
||||
{
|
||||
this.providerValidation = new()
|
||||
@@ -127,7 +144,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is Host.OLLAMA,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost),
|
||||
GetCustomTokenizerValidationIssue = () => this.dataCustomTokenizerValidationIssue,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,7 +155,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
Model model = default;
|
||||
if(this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
if (this.DataHost is Host.OLLAMA)
|
||||
if (this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost))
|
||||
model = new Model(this.dataManuallyModel, null);
|
||||
else if (this.DataHost is Host.LM_STUDIO)
|
||||
model = this.DataModel;
|
||||
@@ -157,6 +175,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
Host = this.DataHost,
|
||||
IsEnterpriseConfiguration = this.IsEnterpriseConfiguration,
|
||||
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||
TokenizerPath = this.dataFilePath,
|
||||
EmbeddingBatchSize = this.DataEmbeddingBatchSize,
|
||||
TokenLimit = this.DataTokenLimit,
|
||||
CustomIconDataUrl = this.DataCustomIconDataUrl,
|
||||
HFInferenceProvider = this.HFInferenceProviderId,
|
||||
};
|
||||
@@ -179,6 +200,10 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
if(this.IsEditing)
|
||||
{
|
||||
this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant();
|
||||
this.dataFilePath = this.DataTokenizerPath;
|
||||
this.showExpertSettings = !string.IsNullOrWhiteSpace(this.DataTokenizerPath)
|
||||
|| this.DataTokenLimit != EmbeddingProvider.DEFAULT_TOKEN_LIMIT
|
||||
|| this.DataEmbeddingBatchSize != EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||
|
||||
// When using self-hosted embedding, we must copy the model name:
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
@@ -187,7 +212,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
//
|
||||
// We cannot load the API key for self-hosted providers:
|
||||
//
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is not Host.OLLAMA)
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is not Host.OLLAMA && this.DataHost is not Host.VLLM)
|
||||
{
|
||||
await this.ReloadModels();
|
||||
await base.OnInitializedAsync();
|
||||
@@ -244,6 +269,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
private async Task Store()
|
||||
{
|
||||
this.dataStoreWasAttempted = true;
|
||||
await this.dataTokenizerValidationTask;
|
||||
await this.form.Validate();
|
||||
this.dataAPIKeyStorageIssue = string.Empty;
|
||||
|
||||
@@ -260,6 +287,15 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
|
||||
var response = await this.StoreOrDeleteTokenizerAsync();
|
||||
if (!response.Success)
|
||||
{
|
||||
this.dataCustomTokenizerValidationIssue = string.IsNullOrWhiteSpace(response.Message) ? string.Empty : response.Message;
|
||||
await this.form.Validate();
|
||||
return;
|
||||
}
|
||||
this.dataFilePath = response.StoredPath;
|
||||
|
||||
// Use the data model to store the provider.
|
||||
// We just return this data to the parent component:
|
||||
var addedProviderSettings = this.CreateEmbeddingProviderSettings();
|
||||
@@ -302,6 +338,22 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateTokenLimit(int tokenLimit)
|
||||
{
|
||||
if (tokenLimit < 1)
|
||||
return T("Please enter a token limit greater than 0.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateEmbeddingBatchSize(int embeddingBatchSize)
|
||||
{
|
||||
if (embeddingBatchSize < 1)
|
||||
return T("Please enter an embedding batch size greater than 0.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
|
||||
private async Task OnAPIKeyChanged(string apiKey)
|
||||
@@ -314,6 +366,90 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenTokenizerFileDialog()
|
||||
{
|
||||
if (this.isTokenizerFileDialogOpen)
|
||||
return;
|
||||
|
||||
this.isTokenizerFileDialogOpen = true;
|
||||
try
|
||||
{
|
||||
var response = await this.RustService.SelectFile(T("Choose a custom tokenizer here"), [ FileTypes.JSON ], string.IsNullOrWhiteSpace(this.dataFilePath) ? null : this.dataFilePath);
|
||||
if (!response.UserCancelled)
|
||||
await this.OnDataFilePathChanged(response.SelectedFilePath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.isTokenizerFileDialogOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Task ClearPathTokenizer(MouseEventArgs _)
|
||||
{
|
||||
return this.OnDataFilePathChanged(string.Empty);
|
||||
}
|
||||
|
||||
private async Task OnDataFilePathChanged(string filePath)
|
||||
{
|
||||
this.dataFilePath = filePath;
|
||||
var validationRevision = ++this.dataTokenizerValidationRevision;
|
||||
this.dataTokenizerValidationTask = this.ValidateCustomTokenizer(filePath, validationRevision);
|
||||
await this.dataTokenizerValidationTask;
|
||||
|
||||
if (validationRevision != this.dataTokenizerValidationRevision)
|
||||
return;
|
||||
|
||||
if (this.dataStoreWasAttempted)
|
||||
await this.form.Validate();
|
||||
else
|
||||
this.form.ResetValidation();
|
||||
}
|
||||
|
||||
private async Task ValidateCustomTokenizer(string filePath, int validationRevision)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
if (validationRevision == this.dataTokenizerValidationRevision)
|
||||
this.dataCustomTokenizerValidationIssue = string.Empty;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await this.RustService.ValidateTokenizer(filePath);
|
||||
if (validationRevision != this.dataTokenizerValidationRevision)
|
||||
return;
|
||||
|
||||
if (response.Success)
|
||||
this.dataCustomTokenizerValidationIssue = string.Empty;
|
||||
else
|
||||
this.dataCustomTokenizerValidationIssue = T("Invalid tokenizer: ") + response.Message;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (validationRevision != this.dataTokenizerValidationRevision)
|
||||
return;
|
||||
|
||||
this.Logger.LogError(e, "Failed to validate custom tokenizer.");
|
||||
this.dataCustomTokenizerValidationIssue = T("Failed to validate the selected tokenizer. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores a new tokenizer or deletes the existing one, based on the specified tokenizer path.
|
||||
/// If the path is null or empty, any existing tokenizer is removed.
|
||||
/// Otherwise, the tokenizer is stored at the specified path.
|
||||
/// </summary>
|
||||
private Task<TokenizerResponse> StoreOrDeleteTokenizerAsync()
|
||||
{
|
||||
var tokenizerId = TokenizerModelId.ForEmbeddingProviderId(this.DataId);
|
||||
if (string.IsNullOrWhiteSpace(this.dataFilePath))
|
||||
return this.RustService.DeleteTokenizer(tokenizerId);
|
||||
|
||||
return this.RustService.StoreTokenizer(tokenizerId, this.dataFilePath);
|
||||
}
|
||||
|
||||
private void OnHostChanged(Host selectedHost)
|
||||
{
|
||||
// When the host changes, reset the model selection state:
|
||||
@@ -374,4 +510,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
};
|
||||
|
||||
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
|
||||
|
||||
private void ToggleExpertSettings() => this.showExpertSettings = !this.showExpertSettings;
|
||||
|
||||
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
|
||||
}
|
||||
Reference in new issue
Block a user