2025-01-13 18:51:26 +00:00
|
|
|
// ReSharper disable InconsistentNaming
|
2025-02-09 11:36:37 +00:00
|
|
|
|
2025-02-17 11:33:34 +00:00
|
|
|
using AIStudio.Assistants.ERI;
|
2025-02-17 13:12:46 +00:00
|
|
|
using AIStudio.Chat;
|
|
|
|
using AIStudio.Tools.ERIClient;
|
2025-02-09 11:36:37 +00:00
|
|
|
using AIStudio.Tools.ERIClient.DataModel;
|
2025-02-17 13:12:46 +00:00
|
|
|
using AIStudio.Tools.RAG;
|
|
|
|
using AIStudio.Tools.Services;
|
|
|
|
|
|
|
|
using ChatThread = AIStudio.Chat.ChatThread;
|
|
|
|
using ContentType = AIStudio.Tools.ERIClient.DataModel.ContentType;
|
2025-02-09 11:36:37 +00:00
|
|
|
|
2025-01-13 18:51:26 +00:00
|
|
|
namespace AIStudio.Settings.DataModel;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// An external data source, accessed via an ERI server, cf. https://github.com/MindWorkAI/ERI.
|
|
|
|
/// </summary>
|
|
|
|
public readonly record struct DataSourceERI_V1 : IERIDataSource
|
|
|
|
{
|
|
|
|
public DataSourceERI_V1()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public uint Num { get; init; }
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public string Id { get; init; } = Guid.Empty.ToString();
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public string Name { get; init; } = string.Empty;
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public DataSourceType Type { get; init; } = DataSourceType.NONE;
|
|
|
|
|
2025-02-09 11:36:37 +00:00
|
|
|
/// <inheritdoc />
|
2025-01-13 18:51:26 +00:00
|
|
|
public string Hostname { get; init; } = string.Empty;
|
|
|
|
|
2025-02-09 11:36:37 +00:00
|
|
|
/// <inheritdoc />
|
2025-01-13 18:51:26 +00:00
|
|
|
public int Port { get; init; }
|
|
|
|
|
2025-02-09 11:36:37 +00:00
|
|
|
/// <inheritdoc />
|
2025-01-13 18:51:26 +00:00
|
|
|
public AuthMethod AuthMethod { get; init; } = AuthMethod.NONE;
|
|
|
|
|
2025-02-09 11:36:37 +00:00
|
|
|
/// <inheritdoc />
|
2025-01-13 18:51:26 +00:00
|
|
|
public string Username { get; init; } = string.Empty;
|
2025-02-15 14:41:12 +00:00
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public DataSourceSecurity SecurityPolicy { get; init; } = DataSourceSecurity.NOT_SPECIFIED;
|
2025-02-17 11:33:34 +00:00
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public ERIVersion Version { get; init; } = ERIVersion.V1;
|
2025-02-17 13:12:46 +00:00
|
|
|
|
2025-03-08 20:04:17 +00:00
|
|
|
/// <inheritdoc />
|
2025-03-08 20:24:39 +00:00
|
|
|
public string SelectedRetrievalId { get; init; } = string.Empty;
|
2025-03-08 20:04:17 +00:00
|
|
|
|
2025-02-17 13:12:46 +00:00
|
|
|
/// <inheritdoc />
|
|
|
|
public async Task<IReadOnlyList<IRetrievalContext>> RetrieveDataAsync(IContent lastPrompt, ChatThread thread, CancellationToken token = default)
|
|
|
|
{
|
|
|
|
// Important: Do not dispose the RustService here, as it is a singleton.
|
|
|
|
var rustService = Program.SERVICE_PROVIDER.GetRequiredService<RustService>();
|
|
|
|
var logger = Program.SERVICE_PROVIDER.GetRequiredService<ILogger<DataSourceERI_V1>>();
|
|
|
|
|
|
|
|
using var eriClient = ERIClientFactory.Get(this.Version, this)!;
|
|
|
|
var authResponse = await eriClient.AuthenticateAsync(this, rustService, token);
|
|
|
|
if (authResponse.Successful)
|
|
|
|
{
|
|
|
|
var retrievalRequest = new RetrievalRequest
|
|
|
|
{
|
|
|
|
LatestUserPromptType = lastPrompt.ToERIContentType,
|
|
|
|
LatestUserPrompt = lastPrompt switch
|
|
|
|
{
|
|
|
|
ContentText text => text.Text,
|
|
|
|
ContentImage image => await image.AsBase64(token),
|
|
|
|
_ => string.Empty
|
|
|
|
},
|
|
|
|
|
|
|
|
Thread = await thread.ToERIChatThread(token),
|
|
|
|
MaxMatches = 10,
|
2025-03-08 20:04:17 +00:00
|
|
|
RetrievalProcessId = string.IsNullOrWhiteSpace(this.SelectedRetrievalId) ? null : this.SelectedRetrievalId,
|
2025-02-17 13:12:46 +00:00
|
|
|
Parameters = null, // The ERI server selects useful default parameters
|
|
|
|
};
|
|
|
|
|
|
|
|
var retrievalResponse = await eriClient.ExecuteRetrievalAsync(retrievalRequest, token);
|
|
|
|
if(retrievalResponse is { Successful: true, Data: not null })
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// Next, we have to transform the ERI context back to our generic retrieval context:
|
|
|
|
//
|
|
|
|
var genericRetrievalContexts = new List<IRetrievalContext>(retrievalResponse.Data.Count);
|
|
|
|
foreach (var eriContext in retrievalResponse.Data)
|
|
|
|
{
|
|
|
|
switch (eriContext.Type)
|
|
|
|
{
|
|
|
|
case ContentType.TEXT:
|
|
|
|
genericRetrievalContexts.Add(new RetrievalTextContext
|
|
|
|
{
|
|
|
|
Path = eriContext.Path ?? string.Empty,
|
|
|
|
Type = eriContext.ToRetrievalContentType(),
|
|
|
|
Links = eriContext.Links,
|
2025-02-22 19:51:06 +00:00
|
|
|
Category = eriContext.Type.ToRetrievalContentCategory(),
|
2025-02-17 13:12:46 +00:00
|
|
|
MatchedText = eriContext.MatchedContent,
|
2025-03-08 12:56:38 +00:00
|
|
|
DataSourceName = this.Name,
|
2025-02-17 13:12:46 +00:00
|
|
|
SurroundingContent = eriContext.SurroundingContent,
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ContentType.IMAGE:
|
|
|
|
genericRetrievalContexts.Add(new RetrievalImageContext
|
|
|
|
{
|
|
|
|
Path = eriContext.Path ?? string.Empty,
|
|
|
|
Type = eriContext.ToRetrievalContentType(),
|
|
|
|
Links = eriContext.Links,
|
|
|
|
Source = eriContext.MatchedContent,
|
2025-02-22 19:51:06 +00:00
|
|
|
Category = eriContext.Type.ToRetrievalContentCategory(),
|
2025-02-17 13:12:46 +00:00
|
|
|
SourceType = ContentImageSource.BASE64,
|
2025-03-08 12:56:38 +00:00
|
|
|
DataSourceName = this.Name,
|
2025-02-17 13:12:46 +00:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
logger.LogWarning($"The ERI context type '{eriContext.Type}' is not supported yet.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return genericRetrievalContexts;
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.LogWarning($"Was not able to retrieve data from the ERI data source '{this.Name}'. Message: {retrievalResponse.Message}");
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.LogWarning($"Was not able to authenticate with the ERI data source '{this.Name}'. Message: {authResponse.Message}");
|
|
|
|
return [];
|
|
|
|
}
|
2025-01-13 18:51:26 +00:00
|
|
|
}
|