From 2bd3d4142122b4eb9aaa668af9f2343e1e70aeb0 Mon Sep 17 00:00:00 2001 From: Thibault Jaigu <84420566+Thibaultjaigu@users.noreply.github.com> Date: Tue, 13 Oct 2026 07:29:06 +0100 Subject: [PATCH] Added Requesty as a provider (#1006) Co-authored-by: Thorsten Sommer --- README.md | 1 + .../Assistants/I18N/allTexts.lua | 9 +- .../Models/Hosting/HostNaming.cs | 55 ++++-- .../Models/Hosting/Hosts/HostRequesty.cs | 50 +++++ app/MindWork AI Studio/Pages/Home.razor.cs | 2 +- app/MindWork AI Studio/Pages/Supporters.razor | 1 + .../Plugins/configuration/plugin.lua | 5 +- .../plugin.lua | 9 +- .../plugin.lua | 9 +- .../Plugins/models/plugin.lua | 2 +- .../AlibabaCloud/ProviderAlibabaCloud.cs | 2 +- .../Provider/Anthropic/ProviderAnthropic.cs | 2 +- .../Provider/DeepSeek/ProviderDeepSeek.cs | 2 +- .../Provider/Fireworks/ProviderFireworks.cs | 2 +- .../Provider/GWDG/ProviderGWDG.cs | 2 +- .../Provider/Google/ProviderGoogle.cs | 2 +- .../Provider/Groq/ProviderGroq.cs | 2 +- .../Provider/Helmholtz/ProviderHelmholtz.cs | 2 +- .../HuggingFace/ProviderHuggingFace.cs | 2 +- .../Provider/IONOS/ProviderIONOS.cs | 2 +- .../Provider/LLMProviders.cs | 1 + .../Provider/LLMProvidersExtensions.cs | 23 +++ .../Provider/LLMProvidersIconExtensions.cs | 1 + .../Provider/Mistral/ProviderMistral.cs | 2 +- .../Provider/OpenAI/ProviderOpenAI.cs | 2 +- .../Provider/OpenRouter/ProviderOpenRouter.cs | 32 +-- .../Provider/Perplexity/ProviderPerplexity.cs | 2 +- .../Provider/ProjectAttribution.cs | 39 ++++ .../Provider/Reasoning/ReasoningDispatcher.cs | 1 + .../Provider/Requesty/ProviderRequesty.cs | 185 ++++++++++++++++++ .../Provider/Requesty/RequestyModel.cs | 10 + .../Requesty/RequestyModelsResponse.cs | 7 + .../Provider/SelfHosted/ProviderSelfHosted.cs | 2 +- .../Provider/X/ProviderX.cs | 2 +- .../wwwroot/changelog/v26.10.1.md | 1 + .../wwwroot/images/provider-icons/README.md | 1 + .../images/provider-icons/requesty.svg | 1 + .../Models/Corpus/CapabilitySnapshot.txt | 6 + app/Tests/Models/Corpus/ModelCorpus.cs | 6 + app/Tests/Models/Hosting/HostNamingTests.cs | 27 +++ app/Tests/Models/Hosting/ModelHostTests.cs | 49 +++++ app/Tests/Provider/ModelListMetadataTests.cs | 14 ++ documentation/Models.md | 2 +- .../org.mindworkai.AIStudio.metainfo.xml | 4 +- 44 files changed, 518 insertions(+), 65 deletions(-) create mode 100644 app/MindWork AI Studio/Models/Hosting/Hosts/HostRequesty.cs create mode 100644 app/MindWork AI Studio/Provider/ProjectAttribution.cs create mode 100644 app/MindWork AI Studio/Provider/Requesty/ProviderRequesty.cs create mode 100644 app/MindWork AI Studio/Provider/Requesty/RequestyModel.cs create mode 100644 app/MindWork AI Studio/Provider/Requesty/RequestyModelsResponse.cs create mode 100644 app/MindWork AI Studio/wwwroot/images/provider-icons/requesty.svg diff --git a/README.md b/README.md index 212b2525..1ba7c4b4 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ MindWork AI Studio is a free desktop app for macOS, Windows, and Linux. It provi - [Hetzner](https://experiments.hetzner.com) (experimental inference API running open-source models in the EU) - [IONOS](https://cloud.ionos.com/managed/ai-model-hub) (AI Model Hub running open-source models in Germany) - [LiteLLM](https://www.litellm.ai/) (an AI gateway you run yourself, in front of models from many providers) + - [Requesty](https://www.requesty.ai/) (a hosted AI gateway from the UK, in front of models from many providers) - [Hugging Face](https://huggingface.co/) using their [inference providers](https://huggingface.co/docs/inference-providers/index) such as Cerebras, Nebius, Sambanova, Novita, Hyperbolic, Together AI, Fireworks, Hugging Face - Self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), and [vLLM](https://github.com/vllm-project/vllm) - [Groq](https://groq.com/) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 2def4eb0..e2ef58f1 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -9817,15 +9817,15 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Cost-effective" -- Flexibility UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibility" --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. -UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3832631168"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." - -- Privacy UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy" -- Voice input UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4194938377"] = "Voice input" +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4287735840"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." + -- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems." @@ -10585,6 +10585,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3805270964"] = "Our Titans" -- Moderation, Design, Wiki, and Documentation UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3821668394"] = "Moderation, Design, Wiki, and Documentation" +-- Thanks, Thibault, for bringing Requesty to AI Studio as a new provider. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3856759487"] = "Thanks, Thibault, for bringing Requesty to AI Studio as a new provider." + -- Thanks, Nils, for taking the time to learn Rust, for building the foundation for local retrieval, and for assistant plugins and the Assistant Builder. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4042792376"] = "Thanks, Nils, for taking the time to learn Rust, for building the foundation for local retrieval, and for assistant plugins and the Assistant Builder." diff --git a/app/MindWork AI Studio/Models/Hosting/HostNaming.cs b/app/MindWork AI Studio/Models/Hosting/HostNaming.cs index 387b1df4..c23b4246 100644 --- a/app/MindWork AI Studio/Models/Hosting/HostNaming.cs +++ b/app/MindWork AI Studio/Models/Hosting/HostNaming.cs @@ -9,8 +9,8 @@ namespace AIStudio.Models.Hosting; /// A wrapping is worked on the name as the provider reported it, never on the normalized one. That /// is not a detail: normalizing writes every separator as a hyphen, so "meta-llama/Llama-3.3-70B" /// and "meta-llama-llama-3.3-70b" are the same text afterwards and nobody can say where the -/// organization ended. The slash, the colon, and the spaces are the whole evidence, and they only -/// exist in the original. +/// organization ended. The slash, the colon, the at sign, and the spaces are the whole evidence, and +/// they only exist in the original. /// public static class HostNaming { @@ -20,10 +20,15 @@ public static class HostNaming private const char ORGANIZATION_SEPARATOR = '/'; /// - /// What separates the model from the inference provider it should be routed to. + /// What separates the model from how a request for it should be routed. /// private const char ROUTING_SEPARATOR = ':'; + /// + /// What separates the model from the region it should run in. + /// + private const char REGION_SEPARATOR = '@'; + /// /// Takes the organization off a hub style name. /// @@ -66,20 +71,46 @@ public static class HostNaming /// Takes the routing suffix off a name. /// /// - /// The suffix says where a request goes, not what the model is: "google/gemma-4-31B-it:novita" - /// is the same model as "google/gemma-4-31B-it". Names on the hub carry no colon of their own, - /// so the last one always starts the suffix. This is not true everywhere -- Ollama writes the - /// variant after a colon, as in "qwen3.8:27b-mlx", and taking that off would throw away which - /// model it is. That is why only the host which has a router asks for this. + /// The suffix says how a request is served, not what the model is: "google/gemma-4-31B-it:novita" + /// is the same model as "google/gemma-4-31B-it". On the Hugging Face router it names the + /// inference provider; Requesty writes the service tier there, as in "openai/gpt-6-sol:flex", or + /// a preset reasoning effort, as in "openai/o4-mini:high". Names at both hosts carry no colon of + /// their own, so the last one always starts the suffix. This is not true everywhere -- Ollama + /// writes the variant after a colon, as in "qwen3.8:27b-mlx", and taking that off would throw + /// away which model it is. That is why only the hosts which put a suffix there ask for this. /// /// The name as it arrived. /// The name without its routing suffix. /// True, when there was a suffix to take off. - public static bool TryStripRoutingSuffix(in ModelId id, out ModelId inner) + public static bool TryStripRoutingSuffix(in ModelId id, out ModelId inner) => TryStripSuffix(id, ROUTING_SEPARATOR, out inner); + + /// + /// Takes the region suffix off a name. + /// + /// + /// The suffix says where a request runs, not what the model is: + /// "bedrock/claude-opus-5@eu-central-1" is the same model as "bedrock/claude-opus-5", served from + /// Frankfurt. Requesty writes names this way, and every suffix it used on the day we checked was a + /// region: "eu", "us", or a data center of AWS, Azure, or Google. No model name carries an at sign + /// of its own, so the last one always starts the suffix. + /// + /// The name as it arrived. + /// The name without its region suffix. + /// True, when there was a suffix to take off. + public static bool TryStripRegionSuffix(in ModelId id, out ModelId inner) => TryStripSuffix(id, REGION_SEPARATOR, out inner); + + /// + /// Takes off everything from the last separator on. + /// + /// The name as it arrived. + /// The character which starts the suffix. + /// The name without its suffix. + /// True, when there was a suffix to take off. + private static bool TryStripSuffix(in ModelId id, char separator, out ModelId inner) { inner = id; - var separatorIndex = id.Original.LastIndexOf(ROUTING_SEPARATOR); + var separatorIndex = id.Original.LastIndexOf(separator); if (separatorIndex is -1) return false; @@ -166,8 +197,8 @@ public static class HostNaming "cohere" or "coherelabs" or "cohereforai" => ModelVendor.COHERE, "moonshot" or "moonshotai" => ModelVendor.MOONSHOT_AI, "tencent" or "tencent-hunyuan" => ModelVendor.TENCENT, - "z-ai" or "zai-org" => ModelVendor.Z_AI, - "minimax" or "minimaxai" => ModelVendor.MINIMAX, + "z-ai" or "zai" or "zai-org" => ModelVendor.Z_AI, + "minimax" or "minimaxai" or "minimaxi" => ModelVendor.MINIMAX, "ai2" or "allenai" => ModelVendor.AI2, "bytedance" or "bytedance-seed" => ModelVendor.BYTE_DANCE, "tii" or "tiiuae" => ModelVendor.TII, diff --git a/app/MindWork AI Studio/Models/Hosting/Hosts/HostRequesty.cs b/app/MindWork AI Studio/Models/Hosting/Hosts/HostRequesty.cs new file mode 100644 index 00000000..50ff1de4 --- /dev/null +++ b/app/MindWork AI Studio/Models/Hosting/Hosts/HostRequesty.cs @@ -0,0 +1,50 @@ +using AIStudio.Models.Matching; +using AIStudio.Provider; + +namespace AIStudio.Models.Hosting.Hosts; + +/// +/// Requesty, a gateway which serves other people's models and says who runs them. +/// +/// +/// A name comes as "provider/model", and the provider is whoever runs the model: the vendor's own +/// API, as in "openai/gpt-6-sol", or a cloud serving somebody else's model, as in +/// "bedrock/claude-opus-5" or "vertex/gemini-3.5-flash". A cloud which serves weights from the hub +/// keeps the hub's organization as a second segment, as in "novita/qwen/qwen3.8-max". The walk +/// takes off one segment at a time, and the innermost one which names a vendor is the one that +/// counts. +/// +/// Where the provider is also a vendor, its name states that vendor. That is right whenever a +/// vendor serves its own models, which is nearly always. It is wrong where the cloud of a vendor +/// resells other models, as in "tencent/kimi-k3": on the day we checked, about one in twenty of the +/// stated vendors was wrong this way. The price is small, because no built-in rule insists on a +/// vendor -- only a model plugin which does would miss such a name and leave it to the built-in +/// rules. +/// +/// Behind the model, a name may carry one more thing which does not change the model: the region it +/// runs in after an at sign, as in "@eu-central-1" or "@eu", or the service tier or a preset +/// reasoning effort after a colon, as in ":flex" or ":high". These come off first. Managed +/// policies, which Requesty curates and routes across several providers, carry no prefix at all, +/// as in "claude-opus-5@eu", so they only lose their region. +/// +public sealed class HostRequesty : ModelHost +{ + /// + public override LLMProviders Provider => LLMProviders.REQUESTY; + + /// + public override ModelSource Source => new("https://docs.requesty.ai/features/eu-routing", new DateOnly(2026, 10, 12), "Models are named \"provider/model\", where the provider is whoever runs the model, optionally followed by a region after an at sign; the model list adds a service tier or a reasoning effort after a colon, and managed policies without any prefix. All of them are served through the OpenAI-compatible chat completion API."); + + /// + public override bool TryUnwrap(in ModelId id, out ModelId inner, out ModelVendor? declaredVendor) + { + declaredVendor = null; + if (HostNaming.TryStripRegionSuffix(id, out inner)) + return true; + + if (HostNaming.TryStripRoutingSuffix(id, out inner)) + return true; + + return HostNaming.TrySplitOrganization(id, out inner, out declaredVendor); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Home.razor.cs b/app/MindWork AI Studio/Pages/Home.razor.cs index 6e32bc82..a912a3cb 100644 --- a/app/MindWork AI Studio/Pages/Home.razor.cs +++ b/app/MindWork AI Studio/Pages/Home.razor.cs @@ -63,7 +63,7 @@ public partial class Home : MSGComponentBase this.itemsAdvantages = [ new(this.T("Free of charge"), this.T("The app is free to use, both for personal and commercial purposes.")), new(this.T("Democratization of AI"), this.T("We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models.")), - new(this.T("Independence"), this.T("You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.")), + new(this.T("Independence"), this.T("You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.")), new(this.T("Assistants"), this.T("You just want to quickly translate a text? AI Studio has so-called assistants for such and other tasks. No prompting is necessary when working with these assistants.")), new(T("Tools"), T("AI models can use tools on their own, such as Web Search, Read Web Page, or Search Confluence. When you ask something a model cannot answer from what it knows, it looks it up and answers with the sources it used. You decide which tools a model may use, and you can watch it work.")), new(T("Voice input"), T("Dictate your messages instead of typing them, and have your recordings as well as audio and video files transcribed. You choose the transcription provider, including one you host yourself.")), diff --git a/app/MindWork AI Studio/Pages/Supporters.razor b/app/MindWork AI Studio/Pages/Supporters.razor index 7c4c9b59..703044b7 100644 --- a/app/MindWork AI Studio/Pages/Supporters.razor +++ b/app/MindWork AI Studio/Pages/Supporters.razor @@ -97,6 +97,7 @@ + diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index c762e839..6085dcae 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -1009,8 +1009,8 @@ CONFIG["SETTINGS"] = {} -- Configure a custom confidence scheme. -- This is used when DataConfidence.ConfidenceScheme is set to CUSTOM. -- Allowed provider keys are: OPEN_AI, ANTHROPIC, MISTRAL, GOOGLE, X, DEEP_SEEK, ALIBABA_CLOUD, --- PERPLEXITY, OPEN_ROUTER, HETZNER, IONOS, LITE_LLM, FIREWORKS, GROQ, HUGGINGFACE, SELF_HOSTED, --- HELMHOLTZ, GWDG +-- PERPLEXITY, OPEN_ROUTER, HETZNER, IONOS, LITE_LLM, REQUESTY, FIREWORKS, GROQ, HUGGINGFACE, +-- SELF_HOSTED, HELMHOLTZ, GWDG -- Allowed confidence values are: UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH -- -- Replaces, does not merge: a configuration with a higher priority replaces the whole @@ -1030,6 +1030,7 @@ CONFIG["SETTINGS"] = {} -- ["HETZNER"] = "HIGH", -- ["IONOS"] = "HIGH", -- ["LITE_LLM"] = "MODERATE", +-- ["REQUESTY"] = "MODERATE", -- ["FIREWORKS"] = "MODERATE", -- ["GROQ"] = "MODERATE", -- ["HUGGINGFACE"] = "MODERATE", diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index 13ac0fbe..c8d82e39 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -9819,15 +9819,15 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Kosteneffizient" -- Flexibility UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibilität" --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. -UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3832631168"] = "Sie sind nicht an einen einzigen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu Ihren Anforderungen passt. Derzeit unterstützen wir OpenAI (GPT-6, GPT-5 usw.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimentell), IONOS, LiteLLM, Hugging Face, Groq, Fireworks sowie selbst gehostete Modelle mit vLLM, llama.cpp, ollama oder LM Studio. Für Wissenschaftlerinnen und Wissenschaftler sowie Mitarbeitende von Forschungseinrichtungen unterstützen wir außerdem die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Logins wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen sowie viele internationale Universitäten verfügbar." - -- Privacy UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Datenschutz" -- Voice input UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4194938377"] = "Spracheingabe" +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4287735840"] = "Sie sind nicht an einen einzigen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu Ihren Anforderungen passt. Derzeit unterstützen wir OpenAI (GPT-6, GPT-5 usw.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimentell), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks sowie selbst gehostete Modelle mit vLLM, llama.cpp, ollama oder LM Studio. Für Wissenschaftlerinnen und Wissenschaftler sowie Mitarbeitende von Forschungseinrichtungen unterstützen wir außerdem die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Logins wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen sowie viele internationale Universitäten verfügbar." + -- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "Sie können über die Einstellungen zur Anbietervertrauenswürdigkeit steuern, welche Anbieter Ihre Daten erhalten. Zum Beispiel können Sie für das Schreiben von E-Mails einen anderen Schutzlevel festlegen als für allgemeine Chats usw. Außerdem garantieren die meisten Anbieter, dass Ihre Daten nicht zum Trainieren neuer KI-Systeme verwendet werden." @@ -10587,6 +10587,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3805270964"] = "Unsere Titanen" -- Moderation, Design, Wiki, and Documentation UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3821668394"] = "Moderation, Design, Wiki und Dokumentation" +-- Thanks, Thibault, for bringing Requesty to AI Studio as a new provider. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3856759487"] = "Danke, Thibault, dass du Requesty als neuen Anbieter in AI Studio integriert hast." + -- Thanks, Nils, for taking the time to learn Rust, for building the foundation for local retrieval, and for assistant plugins and the Assistant Builder. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4042792376"] = "Danke, Nils, dass du dir die Zeit genommen hast, Rust zu lernen, die Grundlage für die lokale Suche zu schaffen und Assistenten-Plugins sowie den Assistenten-Builder zu entwickeln." diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index df3e9a4d..f6fe7469 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -9819,15 +9819,15 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Cost-effective" -- Flexibility UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibility" --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. -UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3832631168"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." - -- Privacy UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy" -- Voice input UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4194938377"] = "Voice input" +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4287735840"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." + -- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems." @@ -10587,6 +10587,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3805270964"] = "Our Titans" -- Moderation, Design, Wiki, and Documentation UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3821668394"] = "Moderation, Design, Wiki, and Documentation" +-- Thanks, Thibault, for bringing Requesty to AI Studio as a new provider. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3856759487"] = "Thanks, Thibault, for bringing Requesty to AI Studio as a new provider." + -- Thanks, Nils, for taking the time to learn Rust, for building the foundation for local retrieval, and for assistant plugins and the Assistant Builder. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4042792376"] = "Thanks, Nils, for taking the time to learn Rust, for building the foundation for local retrieval, and for assistant plugins and the Assistant Builder." diff --git a/app/MindWork AI Studio/Plugins/models/plugin.lua b/app/MindWork AI Studio/Plugins/models/plugin.lua index a25c1846..aeb860dd 100644 --- a/app/MindWork AI Studio/Plugins/models/plugin.lua +++ b/app/MindWork AI Studio/Plugins/models/plugin.lua @@ -112,7 +112,7 @@ MODELS = {} -- -- the same name means different things depending on who serves it. -- -- Allowed values are: OPEN_AI, ANTHROPIC, MISTRAL, GOOGLE, X, DEEP_SEEK, -- -- ALIBABA_CLOUD, PERPLEXITY, OPEN_ROUTER, HETZNER, IONOS, LITE_LLM, --- -- FIREWORKS, GROQ, HUGGINGFACE, SELF_HOSTED, HELMHOLTZ, GWDG +-- -- REQUESTY, FIREWORKS, GROQ, HUGGINGFACE, SELF_HOSTED, HELMHOLTZ, GWDG -- ["ONLY_ON"] = "SELF_HOSTED", -- -- -- Optional: restrict this entry to models of one vendor. Only gateways diff --git a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs index af95d336..2edb690d 100644 --- a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs +++ b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs @@ -66,7 +66,7 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs index 6995c956..df3e6661 100644 --- a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs +++ b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs @@ -191,7 +191,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { throw this.CreateEmbeddingsNotSupportedException(); diff --git a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs index b830684f..ab70d172 100644 --- a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs +++ b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs @@ -66,7 +66,7 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, ne return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { throw this.CreateEmbeddingsNotSupportedException(); diff --git a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs index 410efac4..0c440068 100644 --- a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs +++ b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs @@ -68,7 +68,7 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, new Uri( return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } - /// + /// public override Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { throw this.CreateEmbeddingsNotSupportedException(); diff --git a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs index 3b77344a..add5fd60 100644 --- a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs +++ b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs @@ -81,7 +81,7 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs index 0d143ecd..bc08b617 100644 --- a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs +++ b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs @@ -69,7 +69,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, new Uri("https return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs index 134bc4ed..6a563074 100644 --- a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs +++ b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs @@ -72,7 +72,7 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, new Uri("https://a return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } - /// + /// public override Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { throw this.CreateEmbeddingsNotSupportedException(); diff --git a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs index b40f77c1..5b88e880 100644 --- a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs +++ b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs @@ -69,7 +69,7 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, n return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs index a50a23c5..6df9f700 100644 --- a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs +++ b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs @@ -217,7 +217,7 @@ public sealed class ProviderHuggingFace : BaseProvider return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/IONOS/ProviderIONOS.cs b/app/MindWork AI Studio/Provider/IONOS/ProviderIONOS.cs index 8bfe5806..a15fe376 100644 --- a/app/MindWork AI Studio/Provider/IONOS/ProviderIONOS.cs +++ b/app/MindWork AI Studio/Provider/IONOS/ProviderIONOS.cs @@ -74,7 +74,7 @@ public sealed class ProviderIONOS() : BaseProvider(LLMProviders.IONOS, new Uri(" return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/LLMProviders.cs b/app/MindWork AI Studio/Provider/LLMProviders.cs index f682bd0b..115b4803 100644 --- a/app/MindWork AI Studio/Provider/LLMProviders.cs +++ b/app/MindWork AI Studio/Provider/LLMProviders.cs @@ -19,6 +19,7 @@ public enum LLMProviders HETZNER = 16, IONOS = 17, LITE_LLM = 18, + REQUESTY = 19, FIREWORKS = 5, GROQ = 6, diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index 364464b1..b5174b5d 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -14,6 +14,7 @@ using AIStudio.Provider.Mistral; using AIStudio.Provider.OpenAI; using AIStudio.Provider.OpenRouter; using AIStudio.Provider.Perplexity; +using AIStudio.Provider.Requesty; using AIStudio.Provider.SelfHosted; using AIStudio.Provider.X; using AIStudio.Settings; @@ -62,6 +63,7 @@ public static class LLMProvidersExtensions LLMProviders.HETZNER => "Hetzner (Experimental)", LLMProviders.IONOS => "IONOS", LLMProviders.LITE_LLM => "LiteLLM", + LLMProviders.REQUESTY => "Requesty", LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", @@ -100,6 +102,7 @@ public static class LLMProvidersExtensions LLMProviders.HETZNER => "Hetzner", LLMProviders.IONOS => "IONOS", LLMProviders.LITE_LLM => "LiteLLM", + LLMProviders.REQUESTY => "Requesty", LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", @@ -170,6 +173,18 @@ public static class LLMProvidersExtensions // of a self-hosted model here and let the user assign the level themselves. LLMProviders.LITE_LLM => Confidence.USER_OPERATED_GATEWAY.WithSources("https://docs.litellm.ai/docs/data_security").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + // Requesty Ltd is based in London and works under the UK GDPR and the EU GDPR. Unless logging + // is switched off, it keeps requests for up to 30 days, and it may use the content of free + // accounts for training. Where a model runs depends on the model rather than on Requesty: + // names ending in "@eu" or in another European region stay in Europe, the others may not. + // That is why we state the region but leave the trust level unknown. + LLMProviders.REQUESTY => Confidence.UNKNOWN.WithRegion("Europe, United Kingdom").WithSources( + "https://www.requesty.ai/privacy", + "https://www.requesty.ai/privacy/subprocessors", + "https://www.requesty.ai/dpa", + "https://www.requesty.ai/terms" + ).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)), @@ -197,6 +212,7 @@ public static class LLMProvidersExtensions LLMProviders.GWDG => true, LLMProviders.OPEN_ROUTER => true, LLMProviders.LITE_LLM => true, + LLMProviders.REQUESTY => true, // // Providers that do not support embeddings: @@ -237,6 +253,7 @@ public static class LLMProvidersExtensions LLMProviders.HELMHOLTZ => true, LLMProviders.GROQ => true, LLMProviders.LITE_LLM => true, + LLMProviders.REQUESTY => true, // // Providers that support transcription but provide no OpenAI-compatible API yet: @@ -319,6 +336,7 @@ public static class LLMProvidersExtensions LLMProviders.HETZNER => new ProviderHetzner { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, TokenizerPath = tokenizerPath, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.IONOS => new ProviderIONOS { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, TokenizerPath = tokenizerPath, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.LITE_LLM => new ProviderLiteLLM(hostname) { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, TokenizerPath = tokenizerPath, IsEnterpriseConfiguration = isEnterpriseConfiguration }, + LLMProviders.REQUESTY => new ProviderRequesty { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, TokenizerPath = tokenizerPath, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.GROQ => new ProviderGroq { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, TokenizerPath = tokenizerPath, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.FIREWORKS => new ProviderFireworks { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, TokenizerPath = tokenizerPath, IsEnterpriseConfiguration = isEnterpriseConfiguration }, @@ -357,6 +375,7 @@ public static class LLMProvidersExtensions LLMProviders.OPEN_ROUTER => "https://openrouter.ai/keys", LLMProviders.HETZNER => "https://experiments.hetzner.com", LLMProviders.IONOS => "https://cloud.ionos.com/compute/sign-up", + LLMProviders.REQUESTY => "https://app.requesty.ai/api-keys", LLMProviders.GROQ => "https://console.groq.com/", LLMProviders.FIREWORKS => "https://fireworks.ai/login", @@ -384,6 +403,7 @@ public static class LLMProvidersExtensions LLMProviders.HUGGINGFACE => "https://huggingface.co/settings/billing", LLMProviders.HETZNER => "https://experiments.hetzner.com", LLMProviders.IONOS => "https://dcd.ionos.com/latest/?page=dcd-ai-model-hub", + LLMProviders.REQUESTY => "https://app.requesty.ai/", _ => string.Empty, }; @@ -404,6 +424,7 @@ public static class LLMProvidersExtensions LLMProviders.HUGGINGFACE => true, LLMProviders.HETZNER => true, LLMProviders.IONOS => true, + LLMProviders.REQUESTY => true, _ => false, }; @@ -492,6 +513,7 @@ public static class LLMProvidersExtensions LLMProviders.HETZNER => true, LLMProviders.IONOS => true, LLMProviders.LITE_LLM => true, + LLMProviders.REQUESTY => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true, @@ -521,6 +543,7 @@ public static class LLMProvidersExtensions LLMProviders.OPEN_ROUTER => true, LLMProviders.HETZNER => true, LLMProviders.IONOS => true, + LLMProviders.REQUESTY => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true, diff --git a/app/MindWork AI Studio/Provider/LLMProvidersIconExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersIconExtensions.cs index 66264cac..0caa1386 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersIconExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersIconExtensions.cs @@ -31,6 +31,7 @@ public static class LLMProvidersIconExtensions LLMProviders.HETZNER => $"{ICON_ROOT}/hetzner.svg", LLMProviders.IONOS => $"{ICON_ROOT}/ionos.svg", LLMProviders.LITE_LLM => $"{ICON_ROOT}/litellm.svg", + LLMProviders.REQUESTY => $"{ICON_ROOT}/requesty.svg", LLMProviders.GROQ => $"{ICON_ROOT}/groq.svg", LLMProviders.FIREWORKS => $"{ICON_ROOT}/fireworks.svg", LLMProviders.HUGGINGFACE => $"{ICON_ROOT}/hugging-face.svg", diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs index 17e65b98..f397922b 100644 --- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs +++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs @@ -75,7 +75,7 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } - /// + /// public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index e819c227..33a4d982 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -328,7 +328,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); diff --git a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs index 98bbbc17..7df8e332 100644 --- a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs +++ b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs @@ -10,9 +10,6 @@ namespace AIStudio.Provider.OpenRouter; public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER, new Uri("https://openrouter.ai/api/v1/"), ExternalHttpTrustPolicy.SYSTEM_TRUST_ONLY, LOGGER) { - private const string PROJECT_WEBSITE = "https://github.com/MindWorkAI/AI-Studio"; - private const string PROJECT_NAME = "MindWork AI Studio"; - private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); #region Implementation of IProvider @@ -54,12 +51,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER AdditionalApiParameters = apiParameters }; }, - headersAction: headers => - { - // Set custom headers for project identification: - headers.Add("HTTP-Referer", PROJECT_WEBSITE); - headers.Add("X-Title", PROJECT_NAME); - }, + headersAction: ProjectAttribution.Apply, token: token)) yield return content; } @@ -78,7 +70,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); @@ -120,12 +112,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER .Select(n => new Model(n.Id, n.Name)) .Where(model => model.IsChatModel(this.Provider)), apiKeyProvisional, - requestConfigurator: (request, secretKey) => - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey); - request.Headers.Add("HTTP-Referer", PROJECT_WEBSITE); - request.Headers.Add("X-Title", PROJECT_NAME); - }, + requestConfigurator: ConfigureRequest, listingFactory: modelResponse => modelResponse.Data.Select(n => ModelListing.For(n.Id, n.ContextWindowTokens)), token: token); } @@ -153,12 +140,13 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER "embeddings/models", modelResponse => modelResponse.Data.Select(n => new Model(n.Id, n.Name)), apiKeyProvisional, - requestConfigurator: (request, secretKey) => - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey); - request.Headers.Add("HTTP-Referer", PROJECT_WEBSITE); - request.Headers.Add("X-Title", PROJECT_NAME); - }, + requestConfigurator: ConfigureRequest, token: token); } + + private static void ConfigureRequest(HttpRequestMessage request, string secretKey) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey); + ProjectAttribution.Apply(request.Headers); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs b/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs index 53362730..1d615637 100644 --- a/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs +++ b/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs @@ -74,7 +74,7 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY, return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { throw this.CreateEmbeddingsNotSupportedException(); diff --git a/app/MindWork AI Studio/Provider/ProjectAttribution.cs b/app/MindWork AI Studio/Provider/ProjectAttribution.cs new file mode 100644 index 00000000..e965d870 --- /dev/null +++ b/app/MindWork AI Studio/Provider/ProjectAttribution.cs @@ -0,0 +1,39 @@ +using System.Net.Http.Headers; + +namespace AIStudio.Provider; + +/// +/// The headers which tell a gateway that a request comes from AI Studio. +/// +/// +/// OpenRouter and Requesty read the same two headers: "HTTP-Referer" for the address of the app, +/// and "X-Title" for its name. OpenRouter lists apps by them in its rankings (App Attribution), and +/// Requesty tags the analytics of its users with them (Origin Headers), so a person can tell the +/// requests of AI Studio apart from those of their other tools. OpenRouter calls the second header +/// "X-OpenRouter-Title" by now and still reads "X-Title"; staying with the name both gateways +/// understand is what lets them share this. +/// +/// Neither header says anything about the user: both values are the same for every installation. +/// +public static class ProjectAttribution +{ + /// + /// The address the gateways identify the app by. + /// + private const string PROJECT_WEBSITE = "https://github.com/MindWorkAI/AI-Studio"; + + /// + /// The name the gateways show for the app. + /// + private const string PROJECT_NAME = "MindWork AI Studio"; + + /// + /// Adds the attribution headers to a request. + /// + /// The headers of the request. + public static void Apply(HttpRequestHeaders headers) + { + headers.Add("HTTP-Referer", PROJECT_WEBSITE); + headers.Add("X-Title", PROJECT_NAME); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs b/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs index 1dc9006b..0695495a 100644 --- a/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs +++ b/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs @@ -108,6 +108,7 @@ public static class ReasoningDispatcher LLMProviders.HETZNER or LLMProviders.IONOS or LLMProviders.LITE_LLM or + LLMProviders.REQUESTY or LLMProviders.X or LLMProviders.DEEP_SEEK or LLMProviders.GROQ or diff --git a/app/MindWork AI Studio/Provider/Requesty/ProviderRequesty.cs b/app/MindWork AI Studio/Provider/Requesty/ProviderRequesty.cs new file mode 100644 index 00000000..49158ae4 --- /dev/null +++ b/app/MindWork AI Studio/Provider/Requesty/ProviderRequesty.cs @@ -0,0 +1,185 @@ +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; + +using AIStudio.Chat; +using AIStudio.Models.Live; +using AIStudio.Provider.OpenAI; +using AIStudio.Settings; + +namespace AIStudio.Provider.Requesty; + +public sealed class ProviderRequesty() : BaseProvider(LLMProviders.REQUESTY, new Uri("https://router.requesty.ai/v1/"), ExternalHttpTrustPolicy.SYSTEM_TRUST_ONLY, LOGGER) +{ + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + + #region Implementation of IProvider + + /// + public override string Id => LLMProviders.REQUESTY.ToSecretId(); + + /// + public override string InstanceName { get; set; } = "Requesty"; + + /// + public override bool HasModelLoadingCapability => true; + + /// + public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) + { + await foreach (var content in this.StreamOpenAICompatibleChatCompletion( + "Requesty", + chatModel, + chatThread, + settingsManager, + async (systemPrompt, apiParameters, tools) => + { + // Build the list of messages: + var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.CreateSettingsProvider(chatModel)); + + return new ChatCompletionAPIRequest + { + Model = chatModel.Id, + + // Build the messages: + // - First of all the system prompt + // - Then none-empty user and AI messages + Messages = [systemPrompt, ..messages], + + // Right now, we only support streaming completions: + Stream = true, + Tools = tools, + AdditionalApiParameters = apiParameters + }; + }, + headersAction: ProjectAttribution.Apply, + token: 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 async Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) + { + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); + return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); + } + + /// + public override async Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); + } + + /// + public override Task GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token); + } + + /// + public override Task GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(ModelLoadResult.FromModels([])); + } + + /// + public override Task GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return this.LoadModelsOfRoute(SecretStoreType.EMBEDDING_PROVIDER, "models/embedding", apiKeyProvisional, token); + } + + /// + public override Task GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return this.LoadModelsOfRoute(SecretStoreType.TRANSCRIPTION_PROVIDER, "models/transcription", apiKeyProvisional, token); + } + + #endregion + + /// + /// Loads the managed policies and the full model catalog, and offers both as one list. + /// + /// + /// A managed policy, such as "claude-opus-5@eu", bundles several clouds which serve the same + /// model, and Requesty decides which of them answers a request. Requesty lists these policies on + /// a route of their own, and the catalog does not contain them. Both lists are read before + /// anything is reported, because what is reported replaces everything this instance said + /// before. When the managed route fails, the catalog alone is still offered. + /// + /// Where the API key is stored. + /// An API key which is not stored yet. + /// The cancellation token to use. + /// The chat models. + private async Task LoadModels(SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token) + { + IList managedModels = []; + await this.LoadModelsResponse( + storeType, + "models/managed", + modelResponse => + { + managedModels = modelResponse.Data; + return []; + }, + apiKeyProvisional, + requestConfigurator: ConfigureRequest, + token: token); + + return await this.LoadModelsResponse( + storeType, + "models", + modelResponse => managedModels.Concat(modelResponse.Data) + .DistinctBy(n => n.Id) + .Select(n => new Model(n.Id, null)) + .Where(model => model.IsChatModel(this.Provider)), + apiKeyProvisional, + requestConfigurator: ConfigureRequest, + listingFactory: modelResponse => managedModels.Concat(modelResponse.Data) + .DistinctBy(n => n.Id) + .Select(n => ModelListing.For(n.Id, n.ContextWindowTokens)), + token: token); + } + + /// + /// Loads the models Requesty offers for embedding or for transcription, each of which lives on + /// a route of its own. + /// + /// + /// Nothing is reported from here: such a route answers with the models of its purpose alone, + /// and what is reported replaces everything an instance said before. The windows of the chat + /// models would go missing the moment somebody opens the embedding or transcription settings. + /// + /// Nothing is filtered either. The route is the statement: Requesty serves these models to + /// embed or to transcribe with, which is more than a name can say. Asking the registry on top + /// could only drop a model whose name we do not recognize. + /// + /// Where the API key is stored. + /// The route which lists the models, relative to the base URL. + /// An API key which is not stored yet. + /// The cancellation token to use. + /// The models of the route. + private Task LoadModelsOfRoute(SecretStoreType storeType, string route, string? apiKeyProvisional, CancellationToken token) + { + return this.LoadModelsResponse( + storeType, + route, + modelResponse => modelResponse.Data.Select(n => new Model(n.Id, null)), + apiKeyProvisional, + requestConfigurator: ConfigureRequest, + token: token); + } + + private static void ConfigureRequest(HttpRequestMessage request, string secretKey) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey); + ProjectAttribution.Apply(request.Headers); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Requesty/RequestyModel.cs b/app/MindWork AI Studio/Provider/Requesty/RequestyModel.cs new file mode 100644 index 00000000..35ac8205 --- /dev/null +++ b/app/MindWork AI Studio/Provider/Requesty/RequestyModel.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace AIStudio.Provider.Requesty; + +/// +/// A data model for a Requesty model from the API. +/// +/// The model's ID. +/// How much the model reads and writes in one conversation, in tokens. +public readonly record struct RequestyModel(string Id, [property: JsonPropertyName("context_window")] int? ContextWindowTokens); \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Requesty/RequestyModelsResponse.cs b/app/MindWork AI Studio/Provider/Requesty/RequestyModelsResponse.cs new file mode 100644 index 00000000..17f7a2e8 --- /dev/null +++ b/app/MindWork AI Studio/Provider/Requesty/RequestyModelsResponse.cs @@ -0,0 +1,7 @@ +namespace AIStudio.Provider.Requesty; + +/// +/// A data model for the response from the Requesty models endpoints. +/// +/// The list of models. +public readonly record struct RequestyModelsResponse(IList Data); \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index 999e0b44..07468c3c 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -83,7 +83,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, host, token); } - /// + /// public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER, isTrying: true); diff --git a/app/MindWork AI Studio/Provider/X/ProviderX.cs b/app/MindWork AI Studio/Provider/X/ProviderX.cs index b10f1ef2..b2227147 100644 --- a/app/MindWork AI Studio/Provider/X/ProviderX.cs +++ b/app/MindWork AI Studio/Provider/X/ProviderX.cs @@ -67,7 +67,7 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, new Uri("https:// return Task.FromResult(TranscriptionResult.Failure()); } - /// + /// public override Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) { throw this.CreateEmbeddingsNotSupportedException(); diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md index 1588d97c..ea4f496d 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md @@ -7,6 +7,7 @@ - Added a way for IT departments to allow mailboxes only on the mail servers of your organization. AI Studio then connects to no other server, and the AI no longer reads a mailbox somebody added elsewhere. - Added a setting to share which features of AI Studio you use with the operators of self-hosted AI servers, usually your organization. When you switch it on, requests to self-hosted providers and LiteLLM name the feature that sent them, such as the chat, an assistant, or an agent, together with the version of AI Studio and your operating system. Cloud providers never receive these details, and AI Studio sends nothing to its developers. The setting is off by default, and you find it in the app settings. Thanks, Dominic Neuburg (`donework`), for this contribution. - Added a way for IT departments to let requests to the self-hosted AI servers of your organization name the feature of AI Studio that sent them, either for everybody or as a default that users may change. This shows which features are used. The Enterprise IT documentation explains what is sent and what to clarify before you switch it on, for example, with your works council. +- Added Requesty as a new LLM provider for chats, embeddings, and transcriptions. Requesty is an AI gateway from the United Kingdom that gives you models from many providers with a single API key. Because where your data is processed depends on the model you choose, you set its trust level yourself. Thanks, Thibault Jaigu (`Thibaultjaigu`), for this first contribution. - Improved the Read Web Page tool. When you have not allowed the AI to choose web addresses freely, the tool now refuses every address that does not appear in your chat or in what the tools returned, instead of only asking the AI not to read it. - Improved the installation on macOS: AI Studio is now signed and checked by Apple, so it opens like any other app. You no longer need the Terminal. - Improved the installation on Windows: AI Studio is now signed, so Windows shows who published it. For a while, Windows may still ask you to confirm the installation until it knows the app better. diff --git a/app/MindWork AI Studio/wwwroot/images/provider-icons/README.md b/app/MindWork AI Studio/wwwroot/images/provider-icons/README.md index 0f9b501f..10683f9f 100644 --- a/app/MindWork AI Studio/wwwroot/images/provider-icons/README.md +++ b/app/MindWork AI Studio/wwwroot/images/provider-icons/README.md @@ -12,6 +12,7 @@ The icons serve the LLM providers as well as the mail providers of the mailbox d - `openai*.svg` uses the OpenAI mark path from [Simple Icons 15.15.0](https://github.com/simple-icons/simple-icons/blob/15.15.0/icons/openai.svg) and black/white variants following the [OpenAI Design Guidelines](https://openai.com/brand/). - `fireworks.svg` is adapted from the [Fireworks AI site icon](https://fireworks.ai/icon0.svg). - `groq.svg` is adapted from the [Groq site icon](https://groq.com/favicon.svg). +- `requesty.svg` is adapted from the [Requesty site icon](https://www.requesty.ai/apple-icon.png). - `litellm.svg` is the bullet train emoji from [Twemoji 17.0.3](https://github.com/jdecked/twemoji/tree/v17.0.3), licensed under [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). LiteLLM has no mark of its own and identifies itself with that emoji. Retrieved on 2026-08-30. - `provider*.svg` and `self-hosted*.svg` are neutral project-owned fallback graphics. - `gwdg.svg`, `openrouter.svg`, `google.svg` and `helmholtz.svg` were created by taking the official logo from their respective websites as images and creating a svg from them. diff --git a/app/MindWork AI Studio/wwwroot/images/provider-icons/requesty.svg b/app/MindWork AI Studio/wwwroot/images/provider-icons/requesty.svg new file mode 100644 index 00000000..e7bff286 --- /dev/null +++ b/app/MindWork AI Studio/wwwroot/images/provider-icons/requesty.svg @@ -0,0 +1 @@ + diff --git a/app/Tests/Models/Corpus/CapabilitySnapshot.txt b/app/Tests/Models/Corpus/CapabilitySnapshot.txt index 04ad4296..14529155 100644 --- a/app/Tests/Models/Corpus/CapabilitySnapshot.txt +++ b/app/Tests/Models/Corpus/CapabilitySnapshot.txt @@ -195,6 +195,12 @@ PERPLEXITY | sonar-deep-research | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIP PERPLEXITY | sonar-pro | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | (unknown) | (unknown) PERPLEXITY | sonar-reasoning | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | (unknown) | (unknown) PERPLEXITY | sonar-reasoning-pro | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | (unknown) | (unknown) +REQUESTY | anthropic/claude-opus-5 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | 600 per request | PROVIDER_API /v1/messages/count_tokens | (unknown) +REQUESTY | bedrock/claude-opus-5@eu-central-1 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | 600 per request | PROVIDER_API /v1/messages/count_tokens | (unknown) +REQUESTY | claude-opus-5@eu | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | 600 per request | PROVIDER_API /v1/messages/count_tokens | (unknown) +REQUESTY | google/gemma-4-31b-it | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown) | (unknown) +REQUESTY | novita/qwen/qwen3.8-max | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown) | (unknown) +REQUESTY | openai/gpt-6-sol:flex | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | (unknown) | DEVELOPER SELF_HOSTED | | (nothing) | CHAT | (unknown) | (unknown) | (unknown) | (unknown) SELF_HOSTED | --- | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown) | (unknown) SELF_HOSTED | 01-ai/yi-large | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown) | (unknown) diff --git a/app/Tests/Models/Corpus/ModelCorpus.cs b/app/Tests/Models/Corpus/ModelCorpus.cs index bb872c83..34a4fe2a 100644 --- a/app/Tests/Models/Corpus/ModelCorpus.cs +++ b/app/Tests/Models/Corpus/ModelCorpus.cs @@ -240,6 +240,12 @@ public static class ModelCorpus new(LITE_LLM, "azure/gpt-5.6", QUOTED_AS_A_NAME_SHAPE), new(LITE_LLM, "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0", NAMED_BY_NO_RULE), new(LITE_LLM, "the-fast-one", NAMED_BY_NO_RULE), + new(REQUESTY, "anthropic/claude-opus-5", QUOTED_AS_A_NAME_SHAPE), + new(REQUESTY, "google/gemma-4-31b-it", NAMED_BY_A_RULE), + new(REQUESTY, "bedrock/claude-opus-5@eu-central-1", QUOTED_AS_A_NAME_SHAPE), + new(REQUESTY, "claude-opus-5@eu", QUOTED_AS_A_NAME_SHAPE), + new(REQUESTY, "openai/gpt-6-sol:flex", QUOTED_AS_A_NAME_SHAPE), + new(REQUESTY, "novita/qwen/qwen3.8-max", QUOTED_AS_A_NAME_SHAPE), ]; /// diff --git a/app/Tests/Models/Hosting/HostNamingTests.cs b/app/Tests/Models/Hosting/HostNamingTests.cs index 693f37bb..8370ae21 100644 --- a/app/Tests/Models/Hosting/HostNamingTests.cs +++ b/app/Tests/Models/Hosting/HostNamingTests.cs @@ -54,6 +54,8 @@ public sealed class HostNamingTests Assert.That(HostNaming.VendorOfOrganization("Qwen"), Is.EqualTo(ModelVendor.ALIBABA)); Assert.That(HostNaming.VendorOfOrganization("deepseek-ai"), Is.EqualTo(ModelVendor.DEEP_SEEK)); Assert.That(HostNaming.VendorOfOrganization("HuggingFaceTB"), Is.EqualTo(ModelVendor.HUGGING_FACE)); + Assert.That(HostNaming.VendorOfOrganization("zai"), Is.EqualTo(ModelVendor.Z_AI)); + Assert.That(HostNaming.VendorOfOrganization("minimaxi"), Is.EqualTo(ModelVendor.MINIMAX)); Assert.That(HostNaming.VendorOfOrganization("somebody-else"), Is.EqualTo(ModelVendor.UNKNOWN)); }); } @@ -111,6 +113,31 @@ public sealed class HostNamingTests }); } + [Test] + public void TheRegionSuffixComesOffAndTheModelStaysWhatItWas() + { + var taken = HostNaming.TryStripRegionSuffix(new ModelId("bedrock/claude-opus-5@eu-central-1"), out var inner); + + Assert.Multiple(() => + { + Assert.That(taken, Is.True); + Assert.That(inner.Original, Is.EqualTo("bedrock/claude-opus-5")); + }); + } + + [TestCase("bedrock/claude-opus-5", TestName = "A name without an at sign has no region to take off")] + [TestCase("@eu", TestName = "A region with nothing in front of it is not a wrapping")] + public void WhatCarriesNoRegionIsLeftAlone(string modelId) + { + var taken = HostNaming.TryStripRegionSuffix(new ModelId(modelId), out var inner); + + Assert.Multiple(() => + { + Assert.That(taken, Is.False); + Assert.That(inner.Original, Is.EqualTo(modelId)); + }); + } + [Test] public void AMenuPositionComesOff() { diff --git a/app/Tests/Models/Hosting/ModelHostTests.cs b/app/Tests/Models/Hosting/ModelHostTests.cs index 4294d9e4..42f8bd4e 100644 --- a/app/Tests/Models/Hosting/ModelHostTests.cs +++ b/app/Tests/Models/Hosting/ModelHostTests.cs @@ -78,6 +78,55 @@ public sealed class ModelHostTests }); } + [Test] + public void ARequestyNameLosesItsRegionAndTheCloudRunningIt() + { + var unwrapped = Unwrap(LLMProviders.REQUESTY, "bedrock/claude-opus-5@eu-central-1", out var vendor); + + Assert.Multiple(() => + { + Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5")); + Assert.That(vendor, Is.Null, "Bedrock runs the model; it did not build it."); + }); + } + + [Test] + public void ARequestyManagedPolicyOnlyLosesItsRegion() + { + var unwrapped = Unwrap(LLMProviders.REQUESTY, "claude-opus-5@eu", out var vendor); + + Assert.Multiple(() => + { + Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5")); + Assert.That(vendor, Is.Null); + }); + } + + [Test] + public void TheRequestyServiceTierComesOffAndTheVendorStays() + { + var unwrapped = Unwrap(LLMProviders.REQUESTY, "openai/gpt-6-sol:flex", out var vendor); + + Assert.Multiple(() => + { + Assert.That(unwrapped.Original, Is.EqualTo("gpt-6-sol")); + Assert.That(vendor, Is.EqualTo(ModelVendor.OPEN_AI)); + }); + } + + [TestCase("novita/qwen/qwen3.8-max", "qwen3.8-max", ModelVendor.ALIBABA, TestName = "Behind the cloud, the organization from the hub names the vendor")] + [TestCase("azure/openai/text-embedding-3-large@francecentral", "text-embedding-3-large", ModelVendor.OPEN_AI, TestName = "Behind the cloud and before the region, the organization names the vendor")] + public void TheInnermostRequestyOrganizationNamesTheVendor(string modelId, string expectedModel, ModelVendor expectedVendor) + { + var unwrapped = Unwrap(LLMProviders.REQUESTY, modelId, out var vendor); + + Assert.Multiple(() => + { + Assert.That(unwrapped.Original, Is.EqualTo(expectedModel)); + Assert.That(vendor, Is.EqualTo(expectedVendor)); + }); + } + [Test] public void TheFireworksAccountPathComesOffWholeWithoutAnybodyCountingItsSegments() { diff --git a/app/Tests/Provider/ModelListMetadataTests.cs b/app/Tests/Provider/ModelListMetadataTests.cs index bc42bc76..e0a3cadb 100644 --- a/app/Tests/Provider/ModelListMetadataTests.cs +++ b/app/Tests/Provider/ModelListMetadataTests.cs @@ -2,6 +2,7 @@ using System.Text.Json; using AIStudio.Provider.Groq; using AIStudio.Provider.OpenRouter; +using AIStudio.Provider.Requesty; using MistralModelsResponse = AIStudio.Provider.Mistral.ModelsResponse; using SelfHostedModelsResponse = AIStudio.Provider.SelfHosted.ModelsResponse; @@ -105,6 +106,19 @@ public sealed class ModelListMetadataTests Assert.That(response.Data[0].ContextWindowTokens, Is.EqualTo(131_072)); } + [Test] + public void RequestyStatesTheWindowAsTheContextWindow() + { + var response = JsonSerializer.Deserialize(""" + { + "object": "list", + "data": [ { "id": "openai/gpt-4o-mini", "object": "model", "api": "chat", "context_window": 128000, "max_output_tokens": 16384 } ] + } + """, AS_THE_PROVIDERS_READ_IT); + + Assert.That(response.Data[0].ContextWindowTokens, Is.EqualTo(128_000)); + } + [Test] public void MistralStatesTheWindowAsAMaximumLength() { diff --git a/documentation/Models.md b/documentation/Models.md index 8b2860a9..6237bba9 100644 --- a/documentation/Models.md +++ b/documentation/Models.md @@ -108,7 +108,7 @@ Both are documented for administrators in `documentation/Enterprise IT.md`. Mode ## Live Metadata From The Model Lists -Some providers state the context window in the model list they answer with anyway. AI Studio reads it where it is there: OpenRouter (`context_length`), Groq (`context_window`), Mistral (`max_context_length`), the Hugging Face router (per inference provider), and any OpenAI-compatible self-hosted engine that fills `max_model_len`, which vLLM does. +Some providers state the context window in the model list they answer with anyway. AI Studio reads it where it is there: OpenRouter (`context_length`), Groq (`context_window`), Requesty (`context_window`), Mistral (`max_context_length`), the Hugging Face router (per inference provider), and any OpenAI-compatible self-hosted engine that fills `max_model_len`, which vLLM does. Three things to know when adding another one: diff --git a/runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml b/runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml index ae494baf..7cc316d5 100644 --- a/runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml +++ b/runtime/packaging/linux/org.mindworkai.AIStudio.metainfo.xml @@ -31,8 +31,8 @@
  • Independence: You are not tied to any single provider. Choose the providers that best suit your needs, including OpenAI, Perplexity, Mistral, Anthropic, Google Gemini, xAI, - DeepSeek, Alibaba Cloud, OpenRouter, Hetzner, IONOS, LiteLLM, Hugging Face, Groq, - Fireworks, Helmholtz, GWDG, and self-hosted models. + DeepSeek, Alibaba Cloud, OpenRouter, Hetzner, IONOS, LiteLLM, Requesty, Hugging Face, + Groq, Fireworks, Helmholtz, GWDG, and self-hosted models.
  • Assistants: Use ready-made assistants for common business and other tasks without writing prompts yourself.