From 693fe5e161615b18c8394b8a207f159376839d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= Date: Thu, 10 Apr 2025 12:12:23 +0200 Subject: [PATCH] Create provider for the models from Alibaba Cloud (#394) --- .../AlibabaCloud/ProviderAlibabaCloud.cs | 172 ++++++++++++++++++ .../Provider/LLMProviders.cs | 1 + .../Provider/LLMProvidersExtensions.cs | 11 ++ 3 files changed, 184 insertions(+) create mode 100644 app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs diff --git a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs new file mode 100644 index 00000000..d8a633c2 --- /dev/null +++ b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs @@ -0,0 +1,172 @@ +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; + +using AIStudio.Chat; +using AIStudio.Provider.OpenAI; +using AIStudio.Settings; + +namespace AIStudio.Provider.AlibabaCloud; + +public sealed class ProviderAlibabaCloud(ILogger logger) : BaseProvider("https://dashscope-intl.aliyuncs.com/compatible-mode/v1/", logger) +{ + + #region Implementation of IProvider + + /// + public override string Id => LLMProviders.ALIBABA_CLOUD.ToName(); + + /// + public override string InstanceName { get; set; } = "AlibabaCloud"; + + /// + public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) + { + // Get the API key: + var requestedSecret = await RUST_SERVICE.GetAPIKey(this); + if(!requestedSecret.Success) + yield break; + + // Prepare the system prompt: + var systemPrompt = new Message + { + Role = "system", + Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread, this.logger), + }; + + // Prepare the AlibabaCloud HTTP chat request: + var alibabaCloudChatRequest = JsonSerializer.Serialize(new ChatRequest + { + Model = chatModel.Id, + + // Build the messages: + // - First of all the system prompt + // - Then none-empty user and AI messages + Messages = [systemPrompt, ..chatThread.Blocks.Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text)).Select(n => new Message + { + Role = n.Role switch + { + ChatRole.USER => "user", + ChatRole.AI => "assistant", + ChatRole.AGENT => "assistant", + ChatRole.SYSTEM => "system", + + _ => "user", + }, + + Content = n.Content switch + { + ContentText text => text.Text, + _ => string.Empty, + } + }).ToList()], + Stream = true, + }, JSON_SERIALIZER_OPTIONS); + + async Task RequestBuilder() + { + // Build the HTTP post request: + var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions"); + + // Set the authorization header: + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + + // Set the content: + request.Content = new StringContent(alibabaCloudChatRequest, Encoding.UTF8, "application/json"); + return request; + } + + await foreach (var content in this.StreamChatCompletionInternal("AlibabaCloud", RequestBuilder, token)) + yield return content; + } + + #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + /// + public override async IAsyncEnumerable 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 + + /// + public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + var additionalModels = new[] + { + new Model("qwq-plus", "QwQ plus"), // reasoning model + new Model("qwen-max-latest", "Qwen-Max (Latest)"), + new Model("qwen-plus-latest", "Qwen-Plus (Latest)"), + new Model("qwen-turbo-latest", "Qwen-Turbo (Latest)"), + new Model("qvq-max", "QVQ Max"), // visual reasoning model + new Model("qvq-max-latest", "QVQ Max (Latest)"), // visual reasoning model + new Model("qwen-vl-max", "Qwen-VL Max"), // text generation model that can understand and process images + new Model("qwen-vl-plus", "Qwen-VL Plus"), // text generation model that can understand and process images + new Model("qwen-mt-plus", "Qwen-MT Plus"), // machine translation + new Model("qwen-mt-turbo", "Qwen-MT Turbo"), // machine translation + + //Open source + new Model("qwen2.5-14b-instruct-1m", "Qwen2.5 14b 1m context"), + new Model("qwen2.5-7b-instruct-1m", "Qwen2.5 7b 1m context"), + new Model("qwen2.5-72b-instruct", "Qwen2.5 72b"), + new Model("qwen2.5-32b-instruct", "Qwen2.5 32b"), + new Model("qwen2.5-14b-instruct", "Qwen2.5 14b"), + new Model("qwen2.5-7b-instruct", "Qwen2.5 7b"), + new Model("qwen2.5-omni-7b", "Qwen2.5-Omni 7b"), // omni-modal understanding and generation model + new Model("qwen2.5-vl-72b-instruct", "Qwen2.5-VL 72b"), + new Model("qwen2.5-vl-32b-instruct", "Qwen2.5-VL 32b"), + new Model("qwen2.5-vl-7b-instruct", "Qwen2.5-VL 7b"), + new Model("qwen2.5-vl-3b-instruct", "Qwen2.5-VL 3b"), + }; + + return this.LoadModels(["q"],token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token); + } + + /// + public override Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(Enumerable.Empty()); + } + + /// + public override Task> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + + var additionalModels = new[] + { + new Model("text-embedding-v3", "text-embedding-v3"), + }; + + return this.LoadModels(["text-embedding-"], token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token); + } + + #endregion + + + private async Task> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null) + { + var secretKey = apiKeyProvisional switch + { + not null => apiKeyProvisional, + _ => await RUST_SERVICE.GetAPIKey(this) switch + { + { Success: true } result => await result.Secret.Decrypt(ENCRYPTION), + _ => null, + } + }; + + if (secretKey is null) + return []; + + using var request = new HttpRequestMessage(HttpMethod.Get, "models"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey); + + using var response = await this.httpClient.SendAsync(request, token); + if(!response.IsSuccessStatusCode) + return []; + + var modelResponse = await response.Content.ReadFromJsonAsync(token); + return modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture))); + } + +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/LLMProviders.cs b/app/MindWork AI Studio/Provider/LLMProviders.cs index 92c0873b..1c65835f 100644 --- a/app/MindWork AI Studio/Provider/LLMProviders.cs +++ b/app/MindWork AI Studio/Provider/LLMProviders.cs @@ -13,6 +13,7 @@ public enum LLMProviders GOOGLE = 7, X = 8, DEEP_SEEK = 11, + ALIBABA_CLOUD = 12, FIREWORKS = 5, GROQ = 6, diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index 7fb80e7f..c516ce7d 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -1,3 +1,4 @@ +using AIStudio.Provider.AlibabaCloud; using AIStudio.Provider.Anthropic; using AIStudio.Provider.DeepSeek; using AIStudio.Provider.Fireworks; @@ -32,6 +33,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => "Google", LLMProviders.X => "xAI", LLMProviders.DEEP_SEEK => "DeepSeek", + LLMProviders.ALIBABA_CLOUD => "ALIBABA_CLOUD", LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", @@ -75,6 +77,8 @@ public static class LLMProvidersExtensions LLMProviders.DEEP_SEEK => Confidence.CHINA_NO_TRAINING.WithRegion("Asia").WithSources("https://cdn.deepseek.com/policies/en-US/deepseek-open-platform-terms-of-service.html").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + LLMProviders.ALIBABA_CLOUD => Confidence.CHINA_NO_TRAINING.WithRegion("Asia").WithSources("https://www.alibabacloud.com/help/en/model-studio/support/faq-about-alibaba-cloud-model-studio").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + LLMProviders.SELF_HOSTED => Confidence.SELF_HOSTED.WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), LLMProviders.HELMHOLTZ => Confidence.GDPR_NO_TRAINING.WithRegion("Europe, Germany").WithSources("https://helmholtz.cloud/services/?serviceID=d7d5c597-a2f6-4bd1-b71e-4d6499d98570").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), @@ -97,6 +101,7 @@ public static class LLMProvidersExtensions LLMProviders.MISTRAL => true, LLMProviders.GOOGLE => true, LLMProviders.HELMHOLTZ => true, + LLMProviders.ALIBABA_CLOUD => true, // // Providers that do not support embeddings: @@ -150,6 +155,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = instanceName }, LLMProviders.X => new ProviderX(logger) { InstanceName = instanceName }, LLMProviders.DEEP_SEEK => new ProviderDeepSeek(logger) { InstanceName = instanceName }, + LLMProviders.ALIBABA_CLOUD => new ProviderAlibabaCloud(logger) { InstanceName = instanceName }, LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName }, LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName }, @@ -177,6 +183,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => "https://console.cloud.google.com/", LLMProviders.X => "https://accounts.x.ai/sign-up", LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/sign_up", + LLMProviders.ALIBABA_CLOUD => "https://account.alibabacloud.com/register/intl_register.htm", LLMProviders.GROQ => "https://console.groq.com/", LLMProviders.FIREWORKS => "https://fireworks.ai/login", @@ -197,6 +204,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => "https://console.cloud.google.com/billing", LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing", LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/usage", + LLMProviders.ALIBABA_CLOUD => "https://usercenter2-intl.aliyun.com/billing", _ => string.Empty, }; @@ -211,6 +219,7 @@ public static class LLMProvidersExtensions LLMProviders.FIREWORKS => true, LLMProviders.GOOGLE => true, LLMProviders.DEEP_SEEK => true, + LLMProviders.ALIBABA_CLOUD => true, _ => false, }; @@ -253,6 +262,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => true, LLMProviders.X => true, LLMProviders.DEEP_SEEK => true, + LLMProviders.ALIBABA_CLOUD => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true, @@ -272,6 +282,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => true, LLMProviders.X => true, LLMProviders.DEEP_SEEK => true, + LLMProviders.ALIBABA_CLOUD => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true,