Implemented the retrieval process configuration

This commit is contained in:
Thorsten Sommer 2024-12-27 13:16:39 +01:00
parent 5f542295fc
commit 67ab9c6d97
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
7 changed files with 553 additions and 0 deletions

View File

@ -201,6 +201,44 @@
Data retrieval settings Data retrieval settings
</MudText> </MudText>
<MudText Typo="Typo.body1" Class="mb-2">
For your ERI server, you need to retrieve data that matches a chat or prompt in some way. We call this the retrieval process.
You must describe at least one such process. You may offer several retrieval processes from which users can choose. This allows
you to test with beta users which process works better. Or you might generally want to give users the choice so they can select
the process that best suits their circumstances.
</MudText>
<MudTable Items="@this.retrievalProcesses" Hover="@true" Class="border-dashed border rounded-lg">
<ColGroup>
<col/>
<col style="width: 34em;"/>
</ColGroup>
<HeaderContent>
<MudTh>Name</MudTh>
<MudTh Style="text-align: left;">Actions</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Name</MudTd>
<MudTd Style="text-align: left;">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditRetrievalProcess(context)">
Edit
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteRetrievalProcess(context)">
Delete
</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
@if(this.retrievalProcesses.Count == 0)
{
<MudText Typo="Typo.h6" Class="mt-3">No retrieval process configured yet.</MudText>
}
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddRetrievalProcess">
Add Retrieval Process
</MudButton>
<MudText Typo="Typo.body1" Class="mb-1"> <MudText Typo="Typo.body1" Class="mb-1">
You can integrate additional libraries. Perhaps you want to evaluate the prompts in advance using a machine learning method or analyze them with a text You can integrate additional libraries. Perhaps you want to evaluate the prompts in advance using a machine learning method or analyze them with a text
mining approach? Or maybe you want to preprocess images in the prompts? For such advanced scenarios, you can specify which libraries you want to use here. mining approach? Or maybe you want to preprocess images in the prompts? For such advanced scenarios, you can specify which libraries you want to use here.

View File

@ -64,6 +64,7 @@ public partial class AssistantERI : AssistantBaseCore
this.selectedOperatingSystem = OperatingSystem.NONE; this.selectedOperatingSystem = OperatingSystem.NONE;
this.allowedLLMProviders = AllowedLLMProviders.NONE; this.allowedLLMProviders = AllowedLLMProviders.NONE;
this.embeddings = new(); this.embeddings = new();
this.retrievalProcesses = new();
this.additionalLibraries = string.Empty; this.additionalLibraries = string.Empty;
} }
} }
@ -92,6 +93,7 @@ public partial class AssistantERI : AssistantBaseCore
this.selectedOperatingSystem = this.SettingsManager.ConfigurationData.ERI.PreselectedOperatingSystem; this.selectedOperatingSystem = this.SettingsManager.ConfigurationData.ERI.PreselectedOperatingSystem;
this.allowedLLMProviders = this.SettingsManager.ConfigurationData.ERI.PreselectedAllowedLLMProviders; this.allowedLLMProviders = this.SettingsManager.ConfigurationData.ERI.PreselectedAllowedLLMProviders;
this.embeddings = this.SettingsManager.ConfigurationData.ERI.PreselectedEmbeddingInfos; this.embeddings = this.SettingsManager.ConfigurationData.ERI.PreselectedEmbeddingInfos;
this.retrievalProcesses = this.SettingsManager.ConfigurationData.ERI.PreselectedRetrievalInfos;
this.additionalLibraries = this.SettingsManager.ConfigurationData.ERI.PreselectedAdditionalLibraries; this.additionalLibraries = this.SettingsManager.ConfigurationData.ERI.PreselectedAdditionalLibraries;
return true; return true;
} }
@ -125,6 +127,7 @@ public partial class AssistantERI : AssistantBaseCore
this.SettingsManager.ConfigurationData.ERI.PreselectedOperatingSystem = this.selectedOperatingSystem; this.SettingsManager.ConfigurationData.ERI.PreselectedOperatingSystem = this.selectedOperatingSystem;
this.SettingsManager.ConfigurationData.ERI.PreselectedAllowedLLMProviders = this.allowedLLMProviders; this.SettingsManager.ConfigurationData.ERI.PreselectedAllowedLLMProviders = this.allowedLLMProviders;
this.SettingsManager.ConfigurationData.ERI.PreselectedEmbeddingInfos = this.embeddings; this.SettingsManager.ConfigurationData.ERI.PreselectedEmbeddingInfos = this.embeddings;
this.SettingsManager.ConfigurationData.ERI.PreselectedRetrievalInfos = this.retrievalProcesses;
this.SettingsManager.ConfigurationData.ERI.PreselectedAdditionalLibraries = this.additionalLibraries; this.SettingsManager.ConfigurationData.ERI.PreselectedAdditionalLibraries = this.additionalLibraries;
await this.SettingsManager.StoreSettings(); await this.SettingsManager.StoreSettings();
} }
@ -146,6 +149,7 @@ public partial class AssistantERI : AssistantBaseCore
private OperatingSystem selectedOperatingSystem = OperatingSystem.NONE; private OperatingSystem selectedOperatingSystem = OperatingSystem.NONE;
private AllowedLLMProviders allowedLLMProviders = AllowedLLMProviders.NONE; private AllowedLLMProviders allowedLLMProviders = AllowedLLMProviders.NONE;
private List<EmbeddingInfo> embeddings = new(); private List<EmbeddingInfo> embeddings = new();
private List<RetrievalInfo> retrievalProcesses = new();
private string additionalLibraries = string.Empty; private string additionalLibraries = string.Empty;
private string? ValidateServerName(string name) private string? ValidateServerName(string name)
@ -460,6 +464,65 @@ public partial class AssistantERI : AssistantBaseCore
this.embeddings.Remove(embeddingInfo); this.embeddings.Remove(embeddingInfo);
await this.AutoSave(); await this.AutoSave();
} }
private async Task AddRetrievalProcess()
{
var dialogParameters = new DialogParameters<RetrievalProcessDialog>
{
{ x => x.IsEditing, false },
{ x => x.AvailableEmbeddings, this.embeddings },
};
var dialogReference = await this.DialogService.ShowAsync<RetrievalProcessDialog>("Add Retrieval Process", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var addedRetrievalProcess = (RetrievalInfo)dialogResult.Data!;
this.retrievalProcesses.Add(addedRetrievalProcess);
await this.AutoSave();
}
private async Task EditRetrievalProcess(RetrievalInfo retrievalInfo)
{
var dialogParameters = new DialogParameters<RetrievalProcessDialog>
{
{ x => x.DataName, retrievalInfo.Name },
{ x => x.DataDescription, retrievalInfo.Description },
{ x => x.DataLink, retrievalInfo.Link },
{ x => x.DataParametersDescription, retrievalInfo.ParametersDescription },
{ x => x.DataEmbeddings, retrievalInfo.Embeddings?.ToHashSet() },
{ x => x.IsEditing, true },
{ x => x.AvailableEmbeddings, this.embeddings },
};
var dialogReference = await this.DialogService.ShowAsync<EmbeddingMethodDialog>("Edit Retrieval Process", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var editedRetrievalProcess = (RetrievalInfo)dialogResult.Data!;
this.retrievalProcesses[this.retrievalProcesses.IndexOf(retrievalInfo)] = editedRetrievalProcess;
await this.AutoSave();
}
private async Task DeleteRetrievalProcess(RetrievalInfo retrievalInfo)
{
var dialogParameters = new DialogParameters
{
{ "Message", $"Are you sure you want to delete the retrieval process '{retrievalInfo.Name}'?" },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Retrieval Process", dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
this.retrievalProcesses.Remove(retrievalInfo);
await this.AutoSave();
}
private async Task GenerateServer() private async Task GenerateServer()
{ {

View File

@ -0,0 +1,18 @@
namespace AIStudio.Assistants.ERI;
/// <summary>
/// Information about a retrieval process, which this data source implements.
/// </summary>
/// <param name="Name">The name of the retrieval process, e.g., "Keyword-Based Wikipedia Article Retrieval".</param>
/// <param name="Description">A short description of the retrieval process. What kind of retrieval process is it?</param>
/// <param name="Link">A link to the retrieval process's documentation, paper, Wikipedia article, or the source code. Might be null.</param>
/// <param name="ParametersDescription">A dictionary that describes the parameters of the retrieval process. The key is the parameter name,
/// and the value is a description of the parameter. Although each parameter will be sent as a string, the description should indicate the
/// expected type and range, e.g., 0.0 to 1.0 for a float parameter.</param>
/// <param name="Embeddings">A list of embeddings used in this retrieval process. It might be empty in case no embedding is used.</param>
public readonly record struct RetrievalInfo(
string Name,
string Description,
string? Link,
Dictionary<string, string>? ParametersDescription,
List<EmbeddingInfo>? Embeddings);

View File

@ -0,0 +1,14 @@
namespace AIStudio.Assistants.ERI;
public sealed class RetrievalParameter
{
/// <summary>
/// The name of the parameter.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// The description of the parameter.
/// </summary>
public string Description { get; set; } = string.Empty;
}

View File

@ -0,0 +1,213 @@
@using AIStudio.Assistants.ERI
@using MudExtensions
<MudDialog>
<DialogContent>
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
<MudText Typo="Typo.h5" Class="mb-3">
General Information
</MudText>
<MudText Typo="Typo.body1" Class="mb-3">
Please provide some general information about your retrieval process first. This data may be
displayed to the users.
</MudText>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="Retrieval Process Name"
HelperText="The name of your retrieval process."
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Label"
AdornmentColor="Color.Info"
Validation="@this.ValidateName"
Counter="26"
MaxLength="26"
Immediate="@true"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataDescription"
Label="Retrieval Process Description"
HelperText="A short description of the retrieval process."
Lines="3"
AutoGrow="@true"
MaxLines="6"
Immediate="@true"
Variant="Variant.Outlined"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Extension"
AdornmentColor="Color.Info"
Validation="@this.ValidateDescription"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataLink"
Label="Retrieval Process Link"
HelperText="A link to the retrieval process, e.g., the source code, the paper, it's Wikipedia page, etc. Make sense for common retrieval processes. Leave empty if not applicable."
Class="mb-6"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Link"
AdornmentColor="Color.Info"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
<MudText Typo="Typo.h5" Class="mb-3">
Retrieval Process Parameters
</MudText>
<MudText Typo="Typo.body1" Class="mb-3">
You may want to parameterize your retrieval process. However, this is optional. You can specify any
parameters that can be set by the user or the system during the call. Nevertheless, you should use
sensible default values in your code so that users are not forced to set the parameters manually.
</MudText>
<MudStack Row="@true" Spacing="6" AlignItems="AlignItems.Start" StretchItems="StretchItems.None">
@* The left side of the stack is another stack to show the list *@
<MudStack Row="@false" AlignItems="AlignItems.Start" StretchItems="StretchItems.None">
@if (this.retrievalParameters.Count > 0)
{
<MudList T="RetrievalParameter" Class="mb-1" @bind-SelectedValue="@this.selectedParameter">
@foreach (var parameter in this.retrievalParameters)
{
<MudListItem T="RetrievalParameter" Icon="@Icons.Material.Filled.Tune" Value="@parameter">
@parameter.Name
</MudListItem>
}
</MudList>
}
<MudButton OnClick="@this.AddRetrievalProcessParameter" Variant="Variant.Filled" Color="Color.Primary" Class="mt-1">
Add Parameter
</MudButton>
</MudStack>
@* The right side of the stack is another stack to display the parameter's data *@
<MudStack Row="@false" AlignItems="AlignItems.Stretch" StretchItems="StretchItems.End" Class="pa-3 mb-8 border-solid border rounded-lg">
@if (this.selectedParameter is null)
{
@if(this.retrievalParameters.Count == 0)
{
<MudText>
Add a parameter first, then select it to edit.
</MudText>
}
else
{
<MudText>
Select a parameter to show and edit it.
</MudText>
}
}
else
{
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.selectedParameter.Name"
Label="Parameter Name"
HelperText="The parameter name. It must be unique within the retrieval process."
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Label"
AdornmentColor="Color.Info"
Counter="26"
MaxLength="26"
Validation="@this.ValidateParameterName"
Immediate="@true"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.selectedParameter.Description"
Label="Parameter Description"
HelperText="A short description of the parameter. What data type is it? What is it used for? What are the possible values?"
Lines="3"
AutoGrow="@true"
MaxLines="6"
Immediate="@true"
Variant="Variant.Outlined"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Extension"
AdornmentColor="Color.Info"
Validation="@this.ValidateParameterDescription"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
<MudStack Row="@true">
<MudButton OnClick="@this.RemoveRetrievalProcessParameter" Variant="Variant.Filled" Color="Color.Secondary">
Delete this parameter
</MudButton>
</MudStack>
}
</MudStack>
</MudStack>
<MudText Typo="Typo.h5" Class="mb-3">
Embeddings
</MudText>
@if(this.AvailableEmbeddings.Count == 0)
{
<MudText Typo="Typo.body1" Class="mb-3">
Currently, you have not defined any embedding methods. If your retrieval process does not require embedding, you can ignore this part.
Otherwise, you can define one or more embedding methods in the previous view to assign them to your retrieval process here.
</MudText>
}
else
{
<MudText Typo="Typo.body1" Class="mb-3">
Here you can select which embedding methods are used for this retrieval process. Embeddings are optional;
if your retrieval process works without embedding, you can ignore this part. You can only choose the embedding
methods you have previously defined.
</MudText>
<MudSelectExtended
T="EmbeddingInfo"
MultiSelection="@true"
MultiSelectionTextFunc="@this.GetMultiSelectionText"
SelectedValues="@this.DataEmbeddings"
SelectedValuesChanged="@this.EmbeddingsChanged"
Strict="@true"
Margin="Margin.Dense"
Label="Embeddings methods"
ShrinkLabel="@true"
Class="mb-3"
Variant="Variant.Outlined"
HelperText="Optional. Select the embedding methods that are used for this retrieval process.">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItemExtended Value="@embedding">
@embedding.EmbeddingName
</MudSelectItemExtended>
}
</MudSelectExtended>
}
</MudForm>
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@:Update
}
else
{
@:Add
}
</MudButton>
</DialogActions>
</MudDialog>

View File

@ -0,0 +1,204 @@
using AIStudio.Assistants.ERI;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class RetrievalProcessDialog : ComponentBase
{
[CascadingParameter]
private MudDialogInstance MudDialog { get; set; } = null!;
/// <summary>
/// The user chosen retrieval process name.
/// </summary>
[Parameter]
public string DataName { get; set; } = string.Empty;
/// <summary>
/// The retrieval process description.
/// </summary>
[Parameter]
public string DataDescription { get; set; } = string.Empty;
/// <summary>
/// A link to the retrieval process documentation, paper, Wikipedia article, or the source code.
/// </summary>
[Parameter]
public string DataLink { get; set; } = string.Empty;
/// <summary>
/// A dictionary that describes the parameters of the retrieval process. The key is the parameter name,
/// and the value is a description of the parameter. Although each parameter will be sent as a string,
/// the description should indicate the expected type and range, e.g., 0.0 to 1.0 for a float parameter.
/// </summary>
[Parameter]
public Dictionary<string, string> DataParametersDescription { get; set; } = new();
/// <summary>
/// A list of embeddings used in this retrieval process. It might be empty in case no embedding is used.
/// </summary>
[Parameter]
public HashSet<EmbeddingInfo> DataEmbeddings { get; set; } = new();
/// <summary>
/// The available embeddings for the user to choose from.
/// </summary>
[Parameter]
public IReadOnlyList<EmbeddingInfo> AvailableEmbeddings { get; set; } = new List<EmbeddingInfo>();
/// <summary>
/// Should the dialog be in editing mode?
/// </summary>
[Parameter]
public bool IsEditing { get; init; }
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private bool dataIsValid;
private string[] dataIssues = [];
private List<RetrievalParameter> retrievalParameters = new();
private RetrievalParameter? selectedParameter;
private uint nextParameterId = 1;
// We get the form reference from Blazor code to validate it manually:
private MudForm form = null!;
private RetrievalInfo CreateRetrievalInfo() => new(this.DataName, this.DataDescription, this.DataLink, this.retrievalParameters.ToDictionary(parameter => parameter.Name, parameter => parameter.Description), this.DataEmbeddings.ToList());
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
// Configure the spellchecking for the instance name input:
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
// Convert the parameters:
this.retrievalParameters = this.DataParametersDescription.Select(pair => new RetrievalParameter { Name = pair.Key, Description = pair.Value }).ToList();
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Reset the validation when not editing and on the first render.
// We don't want to show validation errors when the user opens the dialog.
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
await base.OnAfterRenderAsync(firstRender);
}
#endregion
private string? ValidateName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return "The retrieval process name must not be empty. Please name your retrieval process.";
if (name.Length > 26)
return "The retrieval process name must not be longer than 26 characters.";
return null;
}
private string? ValidateDescription(string description)
{
if (string.IsNullOrWhiteSpace(description))
return "The description must not be empty. Please describe the retrieval process.";
return null;
}
private void AddRetrievalProcessParameter()
{
this.retrievalParameters.Add(new() { Name = $"New Parameter {this.nextParameterId++}", Description = string.Empty });
}
private string? ValidateParameterName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return "The parameter name must not be empty. Please name the parameter.";
if(name.Length > 26)
return "The parameter name must not be longer than 26 characters.";
if (this.retrievalParameters.Count(parameter => parameter.Name == name) > 1)
return $"The parameter name '{name}' must be unique. Please choose a different name.";
return null;
}
private string? ValidateParameterDescription(string description)
{
if (string.IsNullOrWhiteSpace(description))
return $"The parameter description must not be empty. Please describe the parameter '{this.selectedParameter?.Name}'. What data type is it? What is it used for? What are the possible values?";
return null;
}
private string? ValidateParameter(RetrievalParameter parameter)
{
if(this.ValidateParameterName(parameter.Name) is { } nameIssue)
return nameIssue;
if (string.IsNullOrWhiteSpace(parameter.Description))
return $"The parameter description must not be empty. Please describe the parameter '{parameter.Name}'. What data type is it? What is it used for? What are the possible values?";
return null;
}
private void RemoveRetrievalProcessParameter()
{
if (this.selectedParameter is not null)
this.retrievalParameters.Remove(this.selectedParameter);
this.selectedParameter = null;
}
private string GetMultiSelectionText(List<EmbeddingInfo> selectedEmbeddings)
{
if(selectedEmbeddings.Count == 0)
return "No embedding methods selected.";
if(selectedEmbeddings.Count == 1)
return "You have selected 1 embedding method.";
return $"You have selected {selectedEmbeddings.Count} embedding methods.";
}
private void EmbeddingsChanged(IEnumerable<EmbeddingInfo>? updatedEmbeddings)
{
if(updatedEmbeddings is null)
this.DataEmbeddings = new();
else
this.DataEmbeddings = updatedEmbeddings.ToHashSet();
}
private async Task Store()
{
await this.form.Validate();
foreach (var parameter in this.retrievalParameters)
{
if (this.ValidateParameter(parameter) is { } issue)
{
this.dataIsValid = false;
Array.Resize(ref this.dataIssues, this.dataIssues.Length + 1);
this.dataIssues[^1] = issue;
}
}
// When the data is not valid, we don't store it:
if (!this.dataIsValid || this.dataIssues.Any())
return;
var retrievalInfo = this.CreateRetrievalInfo();
this.MudDialog.Close(DialogResult.Ok(retrievalInfo));
}
private void Cancel() => this.MudDialog.Cancel();
}

View File

@ -98,7 +98,10 @@ public sealed class DataERI
public List<EmbeddingInfo> PreselectedEmbeddingInfos { get; set; } = new(); public List<EmbeddingInfo> PreselectedEmbeddingInfos { get; set; } = new();
/// <summary> /// <summary>
/// Do you want to predefine any retrieval information?
/// </summary> /// </summary>
public List<RetrievalInfo> PreselectedRetrievalInfos { get; set; } = new();
/// <summary> /// <summary>
/// Do you want to preselect any additional libraries? /// Do you want to preselect any additional libraries?
/// </summary> /// </summary>