Added embedding configurations (#224)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2024-12-03 15:24:40 +01:00
1 parent f806ee28c5
commit 799112eb9d
39 files changed
+1359 -439

No files matched your search

@@ -7,7 +7,7 @@ using AIStudio.Provider.OpenAI;
namespace AIStudio.Provider.Anthropic;
public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://api.anthropic.com/v1/", logger), IProvider
public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://api.anthropic.com/v1/", logger)
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
@@ -16,12 +16,12 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
#region Implementation of IProvider
public string Id => "Anthropic";
public override string Id => LLMProviders.ANTHROPIC.ToName();
public string InstanceName { get; set; } = "Anthropic";
public override string InstanceName { get; set; } = "Anthropic";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
@@ -136,14 +136,14 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(new[]
{
@@ -162,13 +162,17 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
}.AsEnumerable());
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
#endregion
}
@@ -1,3 +1,5 @@
using AIStudio.Chat;
using RustService = AIStudio.Tools.RustService;
namespace AIStudio.Provider;
@@ -5,10 +7,10 @@ namespace AIStudio.Provider;
/// <summary>
/// The base class for all providers.
/// </summary>
public abstract class BaseProvider
public abstract class BaseProvider : IProvider, ISecretId
{
/// <summary>
/// The HTTP client to use for all requests.
/// The HTTP client to use it for all requests.
/// </summary>
protected readonly HttpClient httpClient = new();
@@ -39,4 +41,37 @@ public abstract class BaseProvider
// Set the base URL:
this.httpClient.BaseAddress = new(url);
}
#region Handling of IProvider, which all providers must implement
/// <inheritdoc />
public abstract string Id { get; }
/// <inheritdoc />
public abstract string InstanceName { get; set; }
/// <inheritdoc />
public abstract IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, CancellationToken token = default);
/// <inheritdoc />
public abstract IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default);
/// <inheritdoc />
public abstract Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default);
/// <inheritdoc />
public abstract Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default);
/// <inheritdoc />
public abstract Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
#endregion
#region Implementation of ISecretId
public string SecretId => this.Id;
public string SecretName => this.InstanceName;
#endregion
}
@@ -7,7 +7,7 @@ using AIStudio.Chat;
namespace AIStudio.Provider.Fireworks;
public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.fireworks.ai/inference/v1/", logger), IProvider
public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.fireworks.ai/inference/v1/", logger)
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
@@ -17,13 +17,13 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
#region Implementation of IProvider
/// <inheritdoc />
public string Id => "Fireworks.ai";
public override string Id => LLMProviders.FIREWORKS.ToName();
/// <inheritdoc />
public string InstanceName { get; set; } = "Fireworks.ai";
public override string InstanceName { get; set; } = "Fireworks.ai";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
@@ -138,20 +138,26 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
/// <inheritdoc />
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
namespace AIStudio.Provider.Google;
public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativelanguage.googleapis.com/v1beta/", logger), IProvider
public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativelanguage.googleapis.com/v1beta/", logger)
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
@@ -18,13 +18,13 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
#region Implementation of IProvider
/// <inheritdoc />
public string Id => "Google";
public override string Id => LLMProviders.GOOGLE.ToName();
/// <inheritdoc />
public string InstanceName { get; set; } = "Google Gemini";
public override string InstanceName { get; set; } = "Google Gemini";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
@@ -139,27 +139,44 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModels(token, apiKeyProvisional);
var modelResponse = await this.LoadModels(token, apiKeyProvisional);
if(modelResponse == default)
return [];
return modelResponse.Models.Where(model =>
model.Name.StartsWith("models/gemini-", StringComparison.InvariantCultureIgnoreCase))
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
}
/// <inheritdoc />
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
var modelResponse = await this.LoadModels(token, apiKeyProvisional);
if(modelResponse == default)
return [];
return modelResponse.Models.Where(model =>
model.Name.StartsWith("models/text-embedding-", StringComparison.InvariantCultureIgnoreCase))
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
}
#endregion
private async Task<IEnumerable<Provider.Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
private async Task<ModelsResponse> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
{
var secretKey = apiKeyProvisional switch
{
@@ -170,19 +187,17 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
_ => null,
}
};
if (secretKey is null)
return [];
return default;
var request = new HttpRequestMessage(HttpMethod.Get, $"models?key={secretKey}");
var response = await this.httpClient.SendAsync(request, token);
if(!response.IsSuccessStatusCode)
return [];
return default;
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
return modelResponse.Models.Where(model =>
model.Name.StartsWith("models/gemini-", StringComparison.InvariantCultureIgnoreCase))
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
return modelResponse;
}
}
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
namespace AIStudio.Provider.Groq;
public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/openai/v1/", logger), IProvider
public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/openai/v1/", logger)
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
@@ -18,13 +18,13 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o
#region Implementation of IProvider
/// <inheritdoc />
public string Id => "Groq";
public override string Id => LLMProviders.GROQ.ToName();
/// <inheritdoc />
public string InstanceName { get; set; } = "Groq";
public override string InstanceName { get; set; } = "Groq";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
@@ -141,23 +141,29 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModels(token, apiKeyProvisional);
}
/// <inheritdoc />
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult<IEnumerable<Model>>(Array.Empty<Model>());
}
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion
+9 -1
View File
@@ -16,7 +16,7 @@ public interface IProvider
/// The provider's instance name. Useful for multiple instances of the same provider,
/// e.g., to distinguish between different OpenAI API keys.
/// </summary>
public string InstanceName { get; set; }
public string InstanceName { get; }
/// <summary>
/// Starts a chat completion stream.
@@ -53,4 +53,12 @@ public interface IProvider
/// <param name="token">The cancellation token.</param>
/// <returns>The list of image models.</returns>
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default);
/// <summary>
/// Load all possible embedding models that can be used with this provider.
/// </summary>
/// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The list of embedding models.</returns>
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
}
@@ -7,6 +7,8 @@ using AIStudio.Provider.OpenAI;
using AIStudio.Provider.SelfHosted;
using AIStudio.Settings;
using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Provider;
public static class LLMProvidersExtensions
@@ -89,7 +91,7 @@ public static class LLMProvidersExtensions
//
// Self-hosted providers are treated as a special case anyway.
//
LLMProviders.SELF_HOSTED => false,
LLMProviders.SELF_HOSTED => true,
_ => false,
};
@@ -101,20 +103,36 @@ public static class LLMProvidersExtensions
/// <param name="logger">The logger to use.</param>
/// <returns>The provider instance.</returns>
public static IProvider CreateProvider(this Settings.Provider providerSettings, ILogger logger)
{
return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, logger);
}
/// <summary>
/// Creates a new provider instance based on the embedding provider value.
/// </summary>
/// <param name="embeddingProviderSettings">The embedding provider settings.</param>
/// <param name="logger">The logger to use.</param>
/// <returns>The provider instance.</returns>
public static IProvider CreateProvider(this EmbeddingProvider embeddingProviderSettings, ILogger logger)
{
return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, logger);
}
private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, ILogger logger)
{
try
{
return providerSettings.UsedLLMProvider switch
return provider switch
{
LLMProviders.OPEN_AI => new ProviderOpenAI(logger) { InstanceName = providerSettings.InstanceName },
LLMProviders.ANTHROPIC => new ProviderAnthropic(logger) { InstanceName = providerSettings.InstanceName },
LLMProviders.MISTRAL => new ProviderMistral(logger) { InstanceName = providerSettings.InstanceName },
LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = providerSettings.InstanceName },
LLMProviders.OPEN_AI => new ProviderOpenAI(logger) { InstanceName = instanceName },
LLMProviders.ANTHROPIC => new ProviderAnthropic(logger) { InstanceName = instanceName },
LLMProviders.MISTRAL => new ProviderMistral(logger) { InstanceName = instanceName },
LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = instanceName },
LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = providerSettings.InstanceName },
LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = providerSettings.InstanceName },
LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName },
LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName },
LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, providerSettings) { InstanceName = providerSettings.InstanceName },
LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, host, hostname) { InstanceName = instanceName },
_ => new NoProvider(),
};
@@ -125,4 +143,125 @@ public static class LLMProvidersExtensions
return new NoProvider();
}
}
public static string GetCreationURL(this LLMProviders provider) => provider switch
{
LLMProviders.OPEN_AI => "https://platform.openai.com/signup",
LLMProviders.MISTRAL => "https://console.mistral.ai/",
LLMProviders.ANTHROPIC => "https://console.anthropic.com/dashboard",
LLMProviders.GOOGLE => "https://console.cloud.google.com/",
LLMProviders.GROQ => "https://console.groq.com/",
LLMProviders.FIREWORKS => "https://fireworks.ai/login",
_ => string.Empty,
};
public static string GetDashboardURL(this LLMProviders provider) => provider switch
{
LLMProviders.OPEN_AI => "https://platform.openai.com/usage",
LLMProviders.MISTRAL => "https://console.mistral.ai/usage/",
LLMProviders.ANTHROPIC => "https://console.anthropic.com/settings/plans",
LLMProviders.GROQ => "https://console.groq.com/settings/usage",
LLMProviders.GOOGLE => "https://console.cloud.google.com/billing",
LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing",
_ => string.Empty,
};
public static bool HasDashboard(this LLMProviders provider) => provider switch
{
LLMProviders.OPEN_AI => true,
LLMProviders.MISTRAL => true,
LLMProviders.ANTHROPIC => true,
LLMProviders.GROQ => true,
LLMProviders.FIREWORKS => true,
LLMProviders.GOOGLE => true,
_ => false,
};
public static string GetModelsOverviewURL(this LLMProviders provider) => provider switch
{
LLMProviders.FIREWORKS => "https://fireworks.ai/models?show=Serverless",
_ => string.Empty,
};
public static bool IsLLMModelProvidedManually(this LLMProviders provider) => provider switch
{
LLMProviders.FIREWORKS => true,
_ => false,
};
public static bool IsEmbeddingModelProvidedManually(this LLMProviders provider, Host host) => provider switch
{
LLMProviders.SELF_HOSTED => host is not Host.LM_STUDIO,
_ => false,
};
public static bool IsHostNeeded(this LLMProviders provider) => provider switch
{
LLMProviders.SELF_HOSTED => true,
_ => false,
};
public static bool IsHostnameNeeded(this LLMProviders provider) => provider switch
{
LLMProviders.SELF_HOSTED => true,
_ => false,
};
public static bool IsAPIKeyNeeded(this LLMProviders provider, Host host) => provider switch
{
LLMProviders.OPEN_AI => true,
LLMProviders.MISTRAL => true,
LLMProviders.ANTHROPIC => true,
LLMProviders.GOOGLE => true,
LLMProviders.GROQ => true,
LLMProviders.FIREWORKS => true,
LLMProviders.SELF_HOSTED => host is Host.OLLAMA,
_ => false,
};
public static bool ShowRegisterButton(this LLMProviders provider) => provider switch
{
LLMProviders.OPEN_AI => true,
LLMProviders.MISTRAL => true,
LLMProviders.ANTHROPIC => true,
LLMProviders.GOOGLE => true,
LLMProviders.GROQ => true,
LLMProviders.FIREWORKS => true,
_ => false,
};
public static bool CanLoadModels(this LLMProviders provider, Host host, string? apiKey)
{
if (provider is LLMProviders.SELF_HOSTED)
{
switch (host)
{
case Host.NONE:
case Host.LLAMACPP:
default:
return false;
case Host.OLLAMA:
case Host.LM_STUDIO:
return true;
}
}
if(provider is LLMProviders.NONE)
return false;
if(string.IsNullOrWhiteSpace(apiKey))
return false;
return true;
}
}
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
namespace AIStudio.Provider.Mistral;
public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.mistral.ai/v1/", logger), IProvider
public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.mistral.ai/v1/", logger)
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
@@ -17,12 +17,12 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
#region Implementation of IProvider
public string Id => "Mistral";
public override string Id => LLMProviders.MISTRAL.ToName();
public string InstanceName { get; set; } = "Mistral";
public override string InstanceName { get; set; } = "Mistral";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
@@ -140,14 +140,45 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
var modelResponse = await this.LoadModelList(apiKeyProvisional, token);
if(modelResponse == default)
return [];
return modelResponse.Data.Where(n =>
!n.Id.StartsWith("code", StringComparison.InvariantCulture) &&
!n.Id.Contains("embed", StringComparison.InvariantCulture))
.Select(n => new Provider.Model(n.Id, null));
}
/// <inheritdoc />
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
var modelResponse = await this.LoadModelList(apiKeyProvisional, token);
if(modelResponse == default)
return [];
return modelResponse.Data.Where(n => n.Id.Contains("embed", StringComparison.InvariantCulture))
.Select(n => new Provider.Model(n.Id, null));
}
/// <inheritdoc />
public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
#endregion
private async Task<ModelsResponse> LoadModelList(string? apiKeyProvisional, CancellationToken token)
{
var secretKey = apiKeyProvisional switch
{
@@ -160,29 +191,16 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
};
if (secretKey is null)
return [];
return default;
var request = new HttpRequestMessage(HttpMethod.Get, "models");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
var response = await this.httpClient.SendAsync(request, token);
if(!response.IsSuccessStatusCode)
return [];
return default;
var modelResponse = await response.Content.ReadFromJsonAsync<ModelsResponse>(token);
return modelResponse.Data.Where(n =>
!n.Id.StartsWith("code", StringComparison.InvariantCulture) &&
!n.Id.Contains("embed", StringComparison.InvariantCulture))
.Select(n => new Provider.Model(n.Id, null));
return modelResponse;
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
#endregion
}
@@ -15,6 +15,8 @@ public class NoProvider : IProvider
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, [EnumeratorCancellation] CancellationToken token = default)
{
@@ -10,7 +10,7 @@ namespace AIStudio.Provider.OpenAI;
/// <summary>
/// The OpenAI provider.
/// </summary>
public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.openai.com/v1/", logger), IProvider
public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.openai.com/v1/", logger)
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
@@ -20,13 +20,13 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
#region Implementation of IProvider
/// <inheritdoc />
public string Id => "OpenAI";
public override string Id => LLMProviders.OPEN_AI.ToName();
/// <inheritdoc />
public string InstanceName { get; set; } = "OpenAI";
public override string InstanceName { get; set; } = "OpenAI";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
@@ -144,23 +144,29 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModels(["gpt-", "o1-"], token, apiKeyProvisional);
}
/// <inheritdoc />
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModels(["dall-e-"], token, apiKeyProvisional);
}
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModels(["text-embedding-"], token, apiKeyProvisional);
}
#endregion
@@ -7,36 +7,4 @@ public enum Host
LM_STUDIO,
LLAMACPP,
OLLAMA,
}
public static class HostExtensions
{
public static string Name(this Host host) => host switch
{
Host.NONE => "None",
Host.LM_STUDIO => "LM Studio",
Host.LLAMACPP => "llama.cpp",
Host.OLLAMA => "ollama",
_ => "Unknown",
};
public static string BaseURL(this Host host) => host switch
{
Host.LM_STUDIO => "/v1/",
Host.LLAMACPP => "/v1/",
Host.OLLAMA => "/v1/",
_ => "/v1/",
};
public static string ChatURL(this Host host) => host switch
{
Host.LM_STUDIO => "chat/completions",
Host.LLAMACPP => "chat/completions",
Host.OLLAMA => "chat/completions",
_ => "chat/completions",
};
}
@@ -0,0 +1,47 @@
namespace AIStudio.Provider.SelfHosted;
public static class HostExtensions
{
public static string Name(this Host host) => host switch
{
Host.NONE => "None",
Host.LM_STUDIO => "LM Studio",
Host.LLAMACPP => "llama.cpp",
Host.OLLAMA => "ollama",
_ => "Unknown",
};
public static string BaseURL(this Host host) => host switch
{
Host.LM_STUDIO => "/v1/",
Host.LLAMACPP => "/v1/",
Host.OLLAMA => "/v1/",
_ => "/v1/",
};
public static string ChatURL(this Host host) => host switch
{
Host.LM_STUDIO => "chat/completions",
Host.LLAMACPP => "chat/completions",
Host.OLLAMA => "chat/completions",
_ => "chat/completions",
};
public static bool AreEmbeddingsSupported(this Host host)
{
switch (host)
{
case Host.LM_STUDIO:
case Host.OLLAMA:
return true;
default:
case Host.LLAMACPP:
return false;
}
}
}
@@ -8,7 +8,7 @@ using AIStudio.Provider.OpenAI;
namespace AIStudio.Provider.SelfHosted;
public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provider) : BaseProvider($"{provider.Hostname}{provider.Host.BaseURL()}", logger), IProvider
public sealed class ProviderSelfHosted(ILogger logger, Host host, string hostname) : BaseProvider($"{hostname}{host.BaseURL()}", logger)
{
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new()
{
@@ -17,12 +17,12 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
#region Implementation of IProvider
public string Id => "Self-hosted";
public override string Id => LLMProviders.SELF_HOSTED.ToName();
public string InstanceName { get; set; } = "Self-hosted";
public override string InstanceName { get; set; } = "Self-hosted";
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, isTrying: true);
@@ -70,7 +70,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
try
{
// Build the HTTP post request:
var request = new HttpRequestMessage(HttpMethod.Post, provider.Host.ChatURL());
var request = new HttpRequestMessage(HttpMethod.Post, host.ChatURL());
// Set the authorization header:
if (requestedSecret.Success)
@@ -148,18 +148,18 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Provider.Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
{
yield break;
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
try
{
switch (provider.Host)
switch (host)
{
case Host.LLAMACPP:
// Right now, llama.cpp only supports one model.
@@ -168,27 +168,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
case Host.LM_STUDIO:
case Host.OLLAMA:
var secretKey = apiKeyProvisional switch
{
not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch
{
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null,
}
};
var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
if(secretKey is not null)
lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional);
var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
if(!lmStudioResponse.IsSuccessStatusCode)
return [];
var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token);
return lmStudioModelResponse.Data.Select(n => new Provider.Model(n.Id, null));
return await this.LoadModels(["embed"], [], token, apiKeyProvisional);
}
return [];
@@ -200,13 +180,58 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
}
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
public override Task<IEnumerable<Provider.Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
try
{
switch (host)
{
case Host.LM_STUDIO:
case Host.OLLAMA:
return await this.LoadModels([], ["embed"], token, apiKeyProvisional);
}
return [];
}
catch(Exception e)
{
this.logger.LogError($"Failed to load text models from self-hosted provider: {e.Message}");
return [];
}
}
#endregion
private async Task<IEnumerable<Provider.Model>> LoadModels(string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null)
{
var secretKey = apiKeyProvisional switch
{
not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch
{
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null,
}
};
var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
if(secretKey is not null)
lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional);
var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
if(!lmStudioResponse.IsSuccessStatusCode)
return [];
var lmStudioModelResponse = await lmStudioResponse.Content.ReadFromJsonAsync<ModelsResponse>(token);
return lmStudioModelResponse.Data.
Where(model => !ignorePhrases.Any(ignorePhrase => model.Id.Contains(ignorePhrase, StringComparison.InvariantCulture)) &&
filterPhrases.All( filter => model.Id.Contains(filter, StringComparison.InvariantCulture)))
.Select(n => new Provider.Model(n.Id, null));
}
}