Added a ERI ((E)xternal (R)etrieval (I)nterface) client for communication with any ERI server.

This commit is contained in:
Thorsten Sommer 2025-01-29 07:28:43 +01:00
parent abaa4fa7b7
commit da76be282b
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
31 changed files with 711 additions and 1307 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -2,10 +2,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MindWork AI Studio", "MindWork AI Studio\MindWork AI Studio.csproj", "{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ERIClients", "ERIClients", "{5C2AF789-287B-4FCB-B675-7273D8CD4579}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ERIClientV1", "ERIClientV1\ERIClientV1.csproj", "{9E35A273-0FA6-4BD5-8880-A1DDAC106926}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -16,12 +12,7 @@ Global
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.Build.0 = Release|Any CPU
{9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9E35A273-0FA6-4BD5-8880-A1DDAC106926} = {5C2AF789-287B-4FCB-B675-7273D8CD4579}
EndGlobalSection
EndGlobal

View File

@ -1,8 +1,7 @@
using AIStudio.Dialogs;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using ERI_Client.V1;
using AIStudio.Tools.ERIClient.DataModel;
using Microsoft.AspNetCore.Components;

View File

@ -53,10 +53,6 @@
<PackageReference Include="ReverseMarkdown" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ERIClientV1\ERIClientV1.csproj" />
</ItemGroup>
<!-- Read the meta data file -->
<Target Name="ReadMetaData" BeforeTargets="BeforeBuild">
<Error Text="The ../../metadata.txt file was not found!" Condition="!Exists('../../metadata.txt')" />

View File

@ -1,6 +1,7 @@
using ERI_Client.V1;
// ReSharper disable InconsistentNaming
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Settings.DataModel;
/// <summary>

View File

@ -1,4 +1,4 @@
using ERI_Client.V1;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Settings;

View File

@ -1,4 +1,4 @@
using ERI_Client.V1;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Tools;

View File

@ -0,0 +1,19 @@
namespace AIStudio.Tools.ERIClient;
public sealed class APIResponse<T>
{
/// <summary>
/// Was the API call successful?
/// </summary>
public bool Successful { get; set; }
/// <summary>
/// When the API call was not successful, this will contain the error message.
/// </summary>
public string Message { get; set; } = string.Empty;
/// <summary>
/// The data returned by the API call.
/// </summary>
public T? Data { get; set; }
}

View File

@ -0,0 +1,13 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// An authentication field.
/// </summary>
public enum AuthField
{
NONE,
USERNAME,
PASSWORD,
TOKEN,
KERBEROS_TICKET,
}

View File

@ -0,0 +1,8 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// The mapping between an AuthField and the field name in the authentication request.
/// </summary>
/// <param name="AuthField">The AuthField that is mapped to the field name.</param>
/// <param name="FieldName">The field name in the authentication request.</param>
public record AuthFieldMapping(AuthField AuthField, string FieldName);

View File

@ -0,0 +1,9 @@
namespace AIStudio.Tools.ERIClient.DataModel;
public enum AuthMethod
{
NONE,
KERBEROS,
USERNAME_PASSWORD,
TOKEN,
}

View File

@ -0,0 +1,9 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// The response to an authentication request.
/// </summary>
/// <param name="Success">True, when the authentication was successful.</param>
/// <param name="Token">The token to use for further requests.</param>
/// <param name="Message">When the authentication was not successful, this contains the reason.</param>
public readonly record struct AuthResponse(bool Success, string? Token, string? Message);

View File

@ -0,0 +1,9 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Describes one authentication scheme for this data source.
/// </summary>
/// <param name="AuthMethod">The method used for authentication, e.g., "API Key," "Username/Password," etc.</param>
/// <param name="AuthFieldMappings">A list of field mappings for the authentication method. The client must know,
/// e.g., how the password field is named in the request.</param>
public readonly record struct AuthScheme(AuthMethod AuthMethod, List<AuthFieldMapping> AuthFieldMappings);

View File

@ -0,0 +1,7 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// A chat thread, which is a list of content blocks.
/// </summary>
/// <param name="ContentBlocks">The content blocks in this chat thread.</param>
public readonly record struct ChatThread(List<ContentBlock> ContentBlocks);

View File

@ -0,0 +1,12 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// A block of content of a chat thread.
/// </summary>
/// <remarks>
/// Images and other media are base64 encoded.
/// </remarks>
/// <param name="Content">The content of the block. Remember that images and other media are base64 encoded.</param>
/// <param name="Role">The role of the content in the chat thread.</param>
/// <param name="Type">The type of the content, e.g., text, image, video, etc.</param>
public readonly record struct ContentBlock(string Content, Role Role, ContentType Type);

View File

@ -0,0 +1,16 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// The type of content.
/// </summary>
public enum ContentType
{
NONE,
UNKNOWN,
TEXT,
IMAGE,
VIDEO,
AUDIO,
SPEECH,
}

View File

@ -0,0 +1,27 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Matching context returned by the data source as a result of a retrieval request.
/// </summary>
/// <param name="Name">The name of the source, e.g., a document name, database name,
/// collection name, etc.</param>
/// <param name="Category">What are the contents of the source? For example, is it a
/// dictionary, a book chapter, business concept, a paper, etc.</param>
/// <param name="Path">The path to the content, e.g., a URL, a file path, a path in a
/// graph database, etc.</param>
/// <param name="Type">The type of the content, e.g., text, image, video, audio, speech, etc.</param>
/// <param name="MatchedContent">The content that matched the user prompt. For text, you
/// return the matched text and, e.g., three words before and after it.</param>
/// <param name="SurroundingContent">The surrounding content of the matched content.
/// For text, you may return, e.g., one sentence or paragraph before and after
/// the matched content.</param>
/// <param name="Links">Links to related content, e.g., links to Wikipedia articles,
/// links to sources, etc.</param>
public readonly record struct Context(
string Name,
string Category,
string? Path,
ContentType Type,
string MatchedContent,
string[] SurroundingContent,
string[] Links);

View File

@ -0,0 +1,9 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Information about the data source.
/// </summary>
/// <param name="Name">The name of the data source, e.g., "Internal Organization Documents."</param>
/// <param name="Description">A short description of the data source. What kind of data does it contain?
/// What is the data source used for?</param>
public readonly record struct DataSourceInfo(string Name, string Description);

View File

@ -0,0 +1,20 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Represents information about the used embedding for this data source. The purpose of this information is to give the
/// interested user an idea of what kind of embedding is used and what it does.
/// </summary>
/// <param name="EmbeddingType">What kind of embedding is used. For example, "Transformer Embedding," "Contextual Word
/// Embedding," "Graph Embedding," etc.</param>
/// <param name="EmbeddingName">Name the embedding used. This can be a library, a framework, or the name of the used
/// algorithm.</param>
/// <param name="Description">A short description of the embedding. Describe what the embedding is doing.</param>
/// <param name="UsedWhen">Describe when the embedding is used. For example, when the user prompt contains certain
/// keywords, or anytime?</param>
/// <param name="Link">A link to the embedding's documentation or the source code. Might be null.</param>
public readonly record struct EmbeddingInfo(
string EmbeddingType,
string EmbeddingName,
string Description,
string UsedWhen,
string? Link);

View File

@ -0,0 +1,22 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Known types of providers that can process data.
/// </summary>
public enum ProviderType
{
/// <summary>
/// The related data is not allowed to be sent to any provider.
/// </summary>
NONE,
/// <summary>
/// The related data can be sent to any provider.
/// </summary>
ANY,
/// <summary>
/// The related data can be sent to a provider that is hosted by the same organization, either on-premises or locally.
/// </summary>
SELF_HOSTED,
}

View File

@ -0,0 +1,20 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Information about a retrieval process, which this data source implements.
/// </summary>
/// <param name="Id">A unique identifier for the retrieval process. This can be a GUID, a unique name, or an increasing integer.</param>
/// <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 Id,
string Name,
string Description,
string? Link,
Dictionary<string, string>? ParametersDescription,
List<EmbeddingInfo> Embeddings);

View File

@ -0,0 +1,25 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// The retrieval request sent by AI Studio.
/// </summary>
/// <remarks>
/// Images and other media are base64 encoded.
/// </remarks>
/// <param name="LatestUserPrompt">The latest user prompt that AI Studio received.</param>
/// <param name="LatestUserPromptType">The type of the latest user prompt, e.g., text, image, etc.</param>
/// <param name="Thread">The chat thread that the user is currently in.</param>
/// <param name="RetrievalProcessId">Optional. The ID of the retrieval process that the data source should use.
/// When null, the data source chooses an appropriate retrieval process. Selecting a retrieval process is optional
/// for AI Studio users. Most users do not specify a retrieval process.</param>
/// <param name="Parameters">A dictionary of parameters that the data source should use for the retrieval process.
/// Although each parameter will be sent as a string, the retrieval process specifies the expected type and range.</param>
/// <param name="MaxMatches">The maximum number of matches that the data source should return. AI Studio uses
/// any value below 1 to indicate that the data source should return as many matches as appropriate.</param>
public readonly record struct RetrievalRequest(
string LatestUserPrompt,
ContentType LatestUserPromptType,
ChatThread Thread,
string? RetrievalProcessId,
Dictionary<string, string>? Parameters,
int MaxMatches);

View File

@ -0,0 +1,15 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Possible roles of any chat thread.
/// </summary>
public enum Role
{
NONE,
UNKNOW,
SYSTEM,
USER,
AI,
AGENT,
}

View File

@ -0,0 +1,7 @@
namespace AIStudio.Tools.ERIClient.DataModel;
/// <summary>
/// Represents the security requirements for this data source.
/// </summary>
/// <param name="AllowedProviderType">Which provider types are allowed to process the data?</param>
public readonly record struct SecurityRequirements(ProviderType AllowedProviderType);

View File

@ -0,0 +1,36 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AIStudio.Tools.ERIClient;
public abstract class ERIClientBase(string baseAddress) : IDisposable
{
protected static readonly JsonSerializerOptions JSON_OPTIONS = new()
{
WriteIndented = true,
AllowTrailingCommas = true,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
}
};
protected readonly HttpClient httpClient = new()
{
BaseAddress = new Uri(baseAddress),
};
protected string securityToken = string.Empty;
#region Implementation of IDisposable
public void Dispose()
{
this.httpClient.Dispose();
}
#endregion
}

View File

@ -0,0 +1,14 @@
using AIStudio.Assistants.ERI;
using AIStudio.Settings;
namespace AIStudio.Tools.ERIClient;
public static class ERIClientFactory
{
public static IERIClient? Get(ERIVersion version, IERIDataSource dataSource) => version switch
{
ERIVersion.V1 => new ERIClientV1($"{dataSource.Hostname}:{dataSource.Port}"),
_ => null
};
}

View File

@ -0,0 +1,344 @@
using System.Text;
using System.Text.Json;
using AIStudio.Settings;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Tools.ERIClient;
public class ERIClientV1(string baseAddress) : ERIClientBase(baseAddress), IERIClient
{
#region Implementation of IERIClient
public async Task<APIResponse<List<AuthScheme>>> GetAuthMethodsAsync(CancellationToken cancellationToken = default)
{
using var response = await this.httpClient.GetAsync("/auth/methods", cancellationToken);
if(!response.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to retrieve the authentication methods: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
};
}
var authMethods = await response.Content.ReadFromJsonAsync<List<AuthScheme>>(JSON_OPTIONS, cancellationToken);
if(authMethods is null)
{
return new()
{
Successful = false,
Message = "Failed to retrieve the authentication methods: the ERI server did not return a valid response."
};
}
return new()
{
Successful = true,
Data = authMethods
};
}
public async Task<APIResponse<AuthResponse>> AuthenticateAsync(IERIDataSource dataSource, RustService rustService, CancellationToken cancellationToken = default)
{
var authMethod = dataSource.AuthMethod;
var username = dataSource.Username;
switch (dataSource.AuthMethod)
{
case AuthMethod.NONE:
using (var request = new HttpRequestMessage(HttpMethod.Post, $"auth?authMethod={authMethod}"))
{
using var noneAuthResponse = await this.httpClient.SendAsync(request, cancellationToken);
if(!noneAuthResponse.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to authenticate with the ERI server. Code: {noneAuthResponse.StatusCode}, Reason: {noneAuthResponse.ReasonPhrase}"
};
}
var noneAuthResult = await noneAuthResponse.Content.ReadFromJsonAsync<AuthResponse>(JSON_OPTIONS, cancellationToken);
if(noneAuthResult == default)
{
return new()
{
Successful = false,
Message = "Failed to authenticate with the ERI server: the response was invalid."
};
}
this.securityToken = noneAuthResult.Token ?? string.Empty;
return new()
{
Successful = true,
Data = noneAuthResult
};
}
case AuthMethod.USERNAME_PASSWORD:
var passwordResponse = await rustService.GetSecret(dataSource);
if (!passwordResponse.Success)
{
return new()
{
Successful = false,
Message = "Failed to retrieve the password."
};
}
var password = await passwordResponse.Secret.Decrypt(Program.ENCRYPTION);
using (var request = new HttpRequestMessage(HttpMethod.Post, $"auth?authMethod={authMethod}"))
{
// We must send both values inside the header. The username field is named 'user'.
// The password field is named 'password'.
request.Headers.Add("user", username);
request.Headers.Add("password", password);
using var usernamePasswordAuthResponse = await this.httpClient.SendAsync(request, cancellationToken);
if(!usernamePasswordAuthResponse.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to authenticate with the ERI server. Code: {usernamePasswordAuthResponse.StatusCode}, Reason: {usernamePasswordAuthResponse.ReasonPhrase}"
};
}
var usernamePasswordAuthResult = await usernamePasswordAuthResponse.Content.ReadFromJsonAsync<AuthResponse>(JSON_OPTIONS, cancellationToken);
if(usernamePasswordAuthResult == default)
{
return new()
{
Successful = false,
Message = "Failed to authenticate with the server: the response was invalid."
};
}
this.securityToken = usernamePasswordAuthResult.Token ?? string.Empty;
return new()
{
Successful = true,
Data = usernamePasswordAuthResult
};
}
case AuthMethod.TOKEN:
var tokenResponse = await rustService.GetSecret(dataSource);
if (!tokenResponse.Success)
{
return new()
{
Successful = false,
Message = "Failed to retrieve the access token."
};
}
var token = await tokenResponse.Secret.Decrypt(Program.ENCRYPTION);
using (var request = new HttpRequestMessage(HttpMethod.Post, $"auth?authMethod={authMethod}"))
{
request.Headers.Add("Authorization", $"Bearer {token}");
using var tokenAuthResponse = await this.httpClient.SendAsync(request, cancellationToken);
if(!tokenAuthResponse.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to authenticate with the ERI server. Code: {tokenAuthResponse.StatusCode}, Reason: {tokenAuthResponse.ReasonPhrase}"
};
}
var tokenAuthResult = await tokenAuthResponse.Content.ReadFromJsonAsync<AuthResponse>(JSON_OPTIONS, cancellationToken);
if(tokenAuthResult == default)
{
return new()
{
Successful = false,
Message = "Failed to authenticate with the ERI server: the response was invalid."
};
}
this.securityToken = tokenAuthResult.Token ?? string.Empty;
return new()
{
Successful = true,
Data = tokenAuthResult
};
}
default:
this.securityToken = string.Empty;
return new()
{
Successful = false,
Message = "The authentication method is not supported yet."
};
}
}
public async Task<APIResponse<DataSourceInfo>> GetDataSourceInfoAsync(CancellationToken cancellationToken = default)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "/dataSource");
request.Headers.Add("token", this.securityToken);
using var response = await this.httpClient.SendAsync(request, cancellationToken);
if(!response.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to retrieve the data source information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
};
}
var dataSourceInfo = await response.Content.ReadFromJsonAsync<DataSourceInfo>(JSON_OPTIONS, cancellationToken);
if(dataSourceInfo == default)
{
return new()
{
Successful = false,
Message = "Failed to retrieve the data source information: the ERI server did not return a valid response."
};
}
return new()
{
Successful = true,
Data = dataSourceInfo
};
}
public async Task<APIResponse<List<EmbeddingInfo>>> GetEmbeddingInfoAsync(CancellationToken cancellationToken = default)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "/embedding/info");
request.Headers.Add("token", this.securityToken);
using var response = await this.httpClient.SendAsync(request, cancellationToken);
if(!response.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to retrieve the embedding information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
};
}
var embeddingInfo = await response.Content.ReadFromJsonAsync<List<EmbeddingInfo>>(JSON_OPTIONS, cancellationToken);
if(embeddingInfo is null)
{
return new()
{
Successful = false,
Message = "Failed to retrieve the embedding information: the ERI server did not return a valid response."
};
}
return new()
{
Successful = true,
Data = embeddingInfo
};
}
public async Task<APIResponse<List<RetrievalInfo>>> GetRetrievalInfoAsync(CancellationToken cancellationToken = default)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "/retrieval/info");
request.Headers.Add("token", this.securityToken);
using var response = await this.httpClient.SendAsync(request, cancellationToken);
if(!response.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to retrieve the retrieval information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
};
}
var retrievalInfo = await response.Content.ReadFromJsonAsync<List<RetrievalInfo>>(JSON_OPTIONS, cancellationToken);
if(retrievalInfo is null)
{
return new()
{
Successful = false,
Message = "Failed to retrieve the retrieval information: the ERI server did not return a valid response."
};
}
return new()
{
Successful = true,
Data = retrievalInfo
};
}
public async Task<APIResponse<List<Context>>> ExecuteRetrievalAsync(RetrievalRequest request, CancellationToken cancellationToken = default)
{
using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/retrieval");
requestMessage.Headers.Add("token", this.securityToken);
using var content = new StringContent(JsonSerializer.Serialize(request, JSON_OPTIONS), Encoding.UTF8, "application/json");
requestMessage.Content = content;
using var response = await this.httpClient.SendAsync(requestMessage, cancellationToken);
if(!response.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to execute the retrieval request: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
};
}
var contexts = await response.Content.ReadFromJsonAsync<List<Context>>(JSON_OPTIONS, cancellationToken);
if(contexts is null)
{
return new()
{
Successful = false,
Message = "Failed to execute the retrieval request: the ERI server did not return a valid response."
};
}
return new()
{
Successful = true,
Data = contexts
};
}
public async Task<APIResponse<SecurityRequirements>> GetSecurityRequirementsAsync(CancellationToken cancellationToken = default)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "/security/requirements");
request.Headers.Add("token", this.securityToken);
using var response = await this.httpClient.SendAsync(request, cancellationToken);
if(!response.IsSuccessStatusCode)
{
return new()
{
Successful = false,
Message = $"Failed to retrieve the security requirements: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
};
}
var securityRequirements = await response.Content.ReadFromJsonAsync<SecurityRequirements>(JSON_OPTIONS, cancellationToken);
if(securityRequirements == default)
{
return new()
{
Successful = false,
Message = "Failed to retrieve the security requirements: the ERI server did not return a valid response."
};
}
return new()
{
Successful = true,
Data = securityRequirements
};
}
#endregion
}

View File

@ -0,0 +1,62 @@
using AIStudio.Settings;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Tools.ERIClient;
public interface IERIClient : IDisposable
{
/// <summary>
/// Retrieves the available authentication methods from the ERI server.
/// </summary>
/// <remarks>
/// No authentication is required to retrieve the available authentication methods.
/// </remarks>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The available authentication methods.</returns>
public Task<APIResponse<List<AuthScheme>>> GetAuthMethodsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Authenticate the user to the ERI server.
/// </summary>
/// <param name="dataSource">The data source to use.</param>
/// <param name="rustService">The Rust service.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The authentication response.</returns>
public Task<APIResponse<AuthResponse>> AuthenticateAsync(IERIDataSource dataSource, RustService rustService, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the data source information from the ERI server.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The data source information.</returns>
public Task<APIResponse<DataSourceInfo>> GetDataSourceInfoAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the embedding information from the ERI server.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of embedding information.</returns>
public Task<APIResponse<List<EmbeddingInfo>>> GetEmbeddingInfoAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the retrieval information from the ERI server.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of retrieval information.</returns>
public Task<APIResponse<List<RetrievalInfo>>> GetRetrievalInfoAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Executes a retrieval request on the ERI server.
/// </summary>
/// <param name="request">The retrieval request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The retrieved contexts to use for augmentation and generation.</returns>
public Task<APIResponse<List<Context>>> ExecuteRetrievalAsync(RetrievalRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves the security requirements from the ERI server.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The security requirements.</returns>
public Task<APIResponse<SecurityRequirements>> GetSecurityRequirementsAsync(CancellationToken cancellationToken = default);
}

View File

@ -1,4 +1,4 @@
using ERI_Client.V1;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Tools.Validation;

View File

@ -1,3 +1,4 @@
# v0.9.28, build 203 (2025-0x-xx xx:xx UTC)
- Added an information view to all data sources to the data source configuration page. The data source configuration is a preview feature behind the RAG feature flag.
- Added a ERI ((E)xternal (R)etrieval (I)nterface) client for communication with any ERI server.
- Improved the resource handling when loading models.