Added IONOS as a provider and unified how providers sort their model lists (#941)

This commit is contained in:
Thorsten Sommer 2026-08-29 20:45:39 +02:00 committed by GitHub
parent edbb75dd9f
commit 53da222562
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 410 additions and 50 deletions

View File

@ -116,6 +116,7 @@ MindWork AI Studio is a free desktop app for macOS, Windows, and Linux. It provi
- [Alibaba Cloud](https://www.alibabacloud.com) (Qwen)
- [OpenRouter](https://openrouter.ai/)
- [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)
- [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/)

View File

@ -8,6 +8,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HF/@EntryIndexedValue">HF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IERI/@EntryIndexedValue">IERI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IMIME/@EntryIndexedValue">IMIME</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IONOS/@EntryIndexedValue">IONOS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
@ -27,6 +28,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=huggingface/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ieri/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=IONOS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mime/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>

View File

@ -8413,15 +8413,15 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy"
-- 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."
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental, open-source models hosted in the EU), 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::T558496815"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental, open-source models hosted in the EU), 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."
-- Free of charge
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T617579208"] = "Free of charge"
-- Independence
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T649448159"] = "Independence"
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, 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::T662688722"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, 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."
-- No bloatware
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T858047957"] = "No bloatware"

View File

@ -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 (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental, open-source models hosted in the EU), 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 (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, 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(this.T("Unrestricted usage"), this.T("Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.")),
new(this.T("Cost-effective"), this.T("You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit.")),

View File

@ -711,7 +711,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, FIREWORKS, GROQ, HUGGINGFACE, SELF_HOSTED, HELMHOLTZ, GWDG
-- PERPLEXITY, OPEN_ROUTER, HETZNER, IONOS, 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
@ -729,6 +730,7 @@ CONFIG["SETTINGS"] = {}
-- ["PERPLEXITY"] = "MODERATE",
-- ["OPEN_ROUTER"] = "MODERATE",
-- ["HETZNER"] = "HIGH",
-- ["IONOS"] = "HIGH",
-- ["FIREWORKS"] = "MODERATE",
-- ["GROQ"] = "MODERATE",
-- ["HUGGINGFACE"] = "MODERATE",

View File

@ -8415,15 +8415,15 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Datenschutz"
-- 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."
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental, open-source models hosted in the EU), 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::T558496815"] = "Sie sind nicht an einen einzelnen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu Ihren Bedürfnissen passt. Derzeit unterstützen wir OpenAI (GPT-5, o1 usw.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimentell, in der EU gehostete Open-Source-Modelle), Hugging Face, Groq, Fireworks sowie selbst gehostete Modelle mit vLLM, llama.cpp, ollama oder LM Studio. Für Forschende und Mitarbeitende von Forschungseinrichtungen unterstützen wir außerdem die KI-Dienste von Helmholtz und GWDG. Diese stehen über föderierte Anmeldungen wie eduGAIN allen 18 Helmholtz-Zentren, der Max-Planck-Gesellschaft, den meisten deutschen und vielen internationalen Universitäten zur Verfügung."
-- Free of charge
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T617579208"] = "Kostenlos"
-- Independence
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T649448159"] = "Unabhängigkeit"
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, 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::T662688722"] = "Sie sind nicht an einen einzelnen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu Ihren Bedürfnissen passt. Derzeit unterstützen wir OpenAI (GPT-5, o1 usw.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimentell), IONOS, 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 und viele internationale Universitäten verfügbar."
-- No bloatware
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T858047957"] = "Keinen unnötigen Software-Ballast"

View File

@ -8415,15 +8415,15 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy"
-- 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."
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental, open-source models hosted in the EU), 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::T558496815"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental, open-source models hosted in the EU), 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."
-- Free of charge
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T617579208"] = "Free of charge"
-- Independence
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T649448159"] = "Independence"
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, 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::T662688722"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, 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."
-- No bloatware
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T858047957"] = "No bloatware"

View File

@ -105,7 +105,9 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
var result = await this.LoadModels(["q"], SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
return result with
{
Models = [..result.Models.Concat(additionalModels).OrderBy(x => x.Id)]
// The API is the authority: when it reports a model we also keep as a fallback above,
// its entry comes first and the fallback is dropped.
Models = [..result.Models.Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
};
}
@ -127,7 +129,9 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
var result = await this.LoadModels(["text-embedding-"], SecretStoreType.EMBEDDING_PROVIDER, token, apiKeyProvisional);
return result with
{
Models = [..result.Models.Concat(additionalModels).OrderBy(x => x.Id)]
// The API is the authority: when it reports a model we also keep as a fallback above,
// its entry comes first and the fallback is dropped.
Models = [..result.Models.Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
};
}

View File

@ -143,7 +143,9 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
return result with
{
Models = [..result.Models.Concat(additionalModels).OrderBy(x => x.Id)]
// The API is the authority: when it reports a model we also keep as a fallback above,
// its entry comes first and the fallback is dropped.
Models = [..result.Models.Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
};
}

View File

@ -102,7 +102,7 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, ne
return this.LoadModelsResponse<ModelsResponse>(
storeType,
"models",
modelResponse => modelResponse.Data,
modelResponse => modelResponse.Data.Where(model => model.IsChatModel()),
token,
apiKeyProvisional);
}

View File

@ -78,7 +78,7 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
return result with
{
Models = [..result.Models.Where(model => !model.Id.StartsWith("e5-mistral-7b-instruct", StringComparison.InvariantCultureIgnoreCase))]
Models = [..result.Models.Where(model => model.IsChatModel())]
};
}
@ -94,7 +94,7 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht
var result = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, token, apiKeyProvisional);
return result with
{
Models = [..result.Models.Where(model => model.Id.StartsWith("e5-", StringComparison.InvariantCultureIgnoreCase))]
Models = [..result.Models.Where(model => model.IsEmbeddingModel())]
};
}
@ -124,4 +124,4 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht
return result;
}
}
}

View File

@ -82,9 +82,7 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, n
{
Models =
[
..result.Models.Where(model => !model.Id.StartsWith("text-", StringComparison.InvariantCultureIgnoreCase) &&
!model.Id.Contains("-embedding", StringComparison.InvariantCultureIgnoreCase)
)
..result.Models.Where(model => model.IsChatModel())
]
};
}
@ -103,10 +101,7 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, n
{
Models =
[
..result.Models.Where(model =>
model.Id.Contains("-embedding", StringComparison.InvariantCultureIgnoreCase) ||
model.Id.StartsWith("text-", StringComparison.InvariantCultureIgnoreCase) ||
model.Id.Contains("gritlm", StringComparison.InvariantCultureIgnoreCase))
..result.Models.Where(model => model.IsEmbeddingModel())
]
};
}

View File

@ -68,7 +68,7 @@ public sealed class ProviderHetzner() : BaseProvider(LLMProviders.HETZNER, new U
/// <inheritdoc />
public override Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModelsResponse<ModelsResponse>(SecretStoreType.LLM_PROVIDER, "models", modelResponse => modelResponse.Data, token, apiKeyProvisional);
return this.LoadModelsResponse<ModelsResponse>(SecretStoreType.LLM_PROVIDER, "models", modelResponse => modelResponse.Data.Where(model => model.IsChatModel()), token, apiKeyProvisional);
}
/// <inheritdoc />

View File

@ -0,0 +1,133 @@
using System.Runtime.CompilerServices;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
using AIStudio.Settings;
namespace AIStudio.Provider.IONOS;
public sealed class ProviderIONOS() : BaseProvider(LLMProviders.IONOS, new Uri("https://openai.inference.de-txl.ionos.com/v1/"), ExternalHttpTrustPolicy.SYSTEM_TRUST_ONLY, LOGGER)
{
/// <summary>
/// IONOS keeps an alias of some embedding models around, so that customers can migrate away from
/// the previous naming. Those aliases point to the very same models we already offer, which is
/// why we hide them instead of listing every embedding model twice.
/// </summary>
private const string MIGRATION_ALIAS_SUFFIX = "-migration";
private static readonly ILogger<ProviderIONOS> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ProviderIONOS>();
#region Implementation of IProvider
/// <inheritdoc />
public override string Id => LLMProviders.IONOS.ToSecretId();
/// <inheritdoc />
public override string InstanceName { get; set; } = "IONOS";
/// <inheritdoc />
public override bool HasModelLoadingCapability => true;
/// <inheritdoc />
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
await foreach (var content in this.StreamOpenAICompatibleChatCompletion<ChatCompletionAPIRequest, ChatCompletionDeltaStreamLine, NoChatCompletionAnnotationStreamLine>(
"IONOS",
chatModel,
chatThread,
settingsManager,
async (systemPrompt, apiParameters) =>
{
// Build the list of messages:
var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, 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,
AdditionalApiParameters = apiParameters
};
},
token: token))
yield return content;
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
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 override Task<TranscriptionResult> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(TranscriptionResult.Failure());
}
/// <inhertidoc />
public override async Task<IReadOnlyList<IReadOnlyList<float>>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List<string> texts)
{
var requestedSecret = await Program.RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER);
return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts);
}
/// <inheritdoc />
public override Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModels(SecretStoreType.LLM_PROVIDER, model => model.IsChatModel(), token, apiKeyProvisional);
}
/// <inheritdoc />
public override Task<ModelLoadResult> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(ModelLoadResult.FromModels([]));
}
/// <inheritdoc />
public override Task<ModelLoadResult> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, model => model.IsEmbeddingModel(), token, apiKeyProvisional);
}
/// <inheritdoc />
public override Task<ModelLoadResult> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(ModelLoadResult.FromModels([]));
}
#endregion
/// <summary>
/// Loads the models of one kind from IONOS.
/// </summary>
/// <remarks>
/// IONOS serves chat, embedding, reranking, OCR, and image models through one endpoint, and its
/// response tells us nothing but the model's name. We therefore let the shared model kind
/// detection sort them apart.
/// </remarks>
/// <param name="storeType">The secret store to read the API key from.</param>
/// <param name="isWantedKind">Decides whether a model belongs to the requested kind.</param>
/// <param name="token">The cancellation token.</param>
/// <param name="apiKeyProvisional">An API key which was not stored yet.</param>
/// <returns>The models of the requested kind.</returns>
private Task<ModelLoadResult> LoadModels(SecretStoreType storeType, Func<Model, bool> isWantedKind, CancellationToken token, string? apiKeyProvisional = null)
{
return this.LoadModelsResponse<ModelsResponse>(
storeType,
"models",
modelResponse => modelResponse.Data
.Where(model => !model.Id.EndsWith(MIGRATION_ALIAS_SUFFIX, StringComparison.OrdinalIgnoreCase))
.Where(isWantedKind),
token,
apiKeyProvisional);
}
}

View File

@ -17,6 +17,7 @@ public enum LLMProviders
PERPLEXITY = 14,
OPEN_ROUTER = 15,
HETZNER = 16,
IONOS = 17,
FIREWORKS = 5,
GROQ = 6,

View File

@ -8,6 +8,7 @@ using AIStudio.Provider.GWDG;
using AIStudio.Provider.Helmholtz;
using AIStudio.Provider.Hetzner;
using AIStudio.Provider.HuggingFace;
using AIStudio.Provider.IONOS;
using AIStudio.Provider.Mistral;
using AIStudio.Provider.OpenAI;
using AIStudio.Provider.OpenRouter;
@ -58,6 +59,7 @@ public static class LLMProvidersExtensions
LLMProviders.PERPLEXITY => "Perplexity",
LLMProviders.OPEN_ROUTER => "OpenRouter",
LLMProviders.HETZNER => "Hetzner (Experimental)",
LLMProviders.IONOS => "IONOS",
LLMProviders.GROQ => "Groq",
LLMProviders.FIREWORKS => "Fireworks.ai",
@ -94,6 +96,7 @@ public static class LLMProvidersExtensions
LLMProviders.PERPLEXITY => "Perplexity",
LLMProviders.OPEN_ROUTER => "OpenRouter",
LLMProviders.HETZNER => "Hetzner",
LLMProviders.IONOS => "IONOS",
LLMProviders.GROQ => "Groq",
LLMProviders.FIREWORKS => "Fireworks.ai",
@ -153,6 +156,11 @@ public static class LLMProvidersExtensions
"https://www.hetzner.com/legal/terms-and-conditions/"
).WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
LLMProviders.IONOS => Confidence.GDPR_NO_TRAINING.WithRegion("Europe, Germany").WithSources(
"https://docs.ionos.com/cloud/ai/ai-model-hub/governance-and-compliance/data-handling",
"https://www.ionos.com/terms-gtc/privacy-policy/"
).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)),
@ -176,6 +184,7 @@ public static class LLMProvidersExtensions
LLMProviders.GOOGLE => true,
LLMProviders.HELMHOLTZ => true,
LLMProviders.ALIBABA_CLOUD => true,
LLMProviders.IONOS => true,
//
// Providers that do not support embeddings:
@ -220,6 +229,7 @@ public static class LLMProvidersExtensions
//
LLMProviders.OPEN_ROUTER => false,
LLMProviders.HETZNER => false,
LLMProviders.IONOS => false,
LLMProviders.GROQ => false,
LLMProviders.ANTHROPIC => false,
LLMProviders.X => false,
@ -283,6 +293,7 @@ public static class LLMProvidersExtensions
LLMProviders.PERPLEXITY => new ProviderPerplexity { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
LLMProviders.OPEN_ROUTER => new ProviderOpenRouter { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
LLMProviders.HETZNER => new ProviderHetzner { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
LLMProviders.IONOS => new ProviderIONOS { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
LLMProviders.GROQ => new ProviderGroq { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
LLMProviders.FIREWORKS => new ProviderFireworks { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration },
@ -315,6 +326,7 @@ public static class LLMProvidersExtensions
LLMProviders.PERPLEXITY => "https://www.perplexity.ai/account/api",
LLMProviders.OPEN_ROUTER => "https://openrouter.ai/keys",
LLMProviders.HETZNER => "https://experiments.hetzner.com",
LLMProviders.IONOS => "https://cloud.ionos.com/compute/sign-up",
LLMProviders.GROQ => "https://console.groq.com/",
LLMProviders.FIREWORKS => "https://fireworks.ai/login",
@ -341,6 +353,7 @@ public static class LLMProvidersExtensions
LLMProviders.OPEN_ROUTER => "https://openrouter.ai/activity",
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",
_ => string.Empty,
};
@ -360,6 +373,7 @@ public static class LLMProvidersExtensions
LLMProviders.OPEN_ROUTER => true,
LLMProviders.HUGGINGFACE => true,
LLMProviders.HETZNER => true,
LLMProviders.IONOS => true,
_ => false,
};
@ -438,6 +452,7 @@ public static class LLMProvidersExtensions
LLMProviders.PERPLEXITY => true,
LLMProviders.OPEN_ROUTER => true,
LLMProviders.HETZNER => true,
LLMProviders.IONOS => true,
LLMProviders.GROQ => true,
LLMProviders.FIREWORKS => true,
@ -462,6 +477,7 @@ public static class LLMProvidersExtensions
LLMProviders.PERPLEXITY => true,
LLMProviders.OPEN_ROUTER => true,
LLMProviders.HETZNER => true,
LLMProviders.IONOS => true,
LLMProviders.GROQ => true,
LLMProviders.FIREWORKS => true,

View File

@ -29,6 +29,7 @@ public static class LLMProvidersIconExtensions
LLMProviders.PERPLEXITY => $"{ICON_ROOT}/perplexity.svg",
LLMProviders.OPEN_ROUTER => $"{ICON_ROOT}/openrouter.svg",
LLMProviders.HETZNER => $"{ICON_ROOT}/hetzner.svg",
LLMProviders.IONOS => $"{ICON_ROOT}/ionos.svg",
LLMProviders.GROQ => $"{ICON_ROOT}/groq.svg",
LLMProviders.FIREWORKS => $"{ICON_ROOT}/fireworks.svg",
LLMProviders.HUGGINGFACE => $"{ICON_ROOT}/hugging-face.svg",

View File

@ -91,10 +91,12 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
{
Models =
[
// Codestral is a fill-in-the-middle model, which we cannot use for chats. That is
// specific to Mistral's catalog, which is why it is not part of the shared model
// kind detection:
..modelResponse.Models.Where(n =>
!n.Id.StartsWith("code", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("embed", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("moderation", StringComparison.OrdinalIgnoreCase))
n.IsChatModel())
]
};
}
@ -108,7 +110,7 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
return modelResponse with
{
Models = [..modelResponse.Models.Where(n => n.Id.Contains("embed", StringComparison.InvariantCulture))]
Models = [..modelResponse.Models.Where(n => n.IsEmbeddingModel())]
};
}

View File

@ -42,15 +42,33 @@ public readonly record struct Model(string Id, string? DisplayName)
#endregion
#region Implementation of IEquatable<Model?>
#region Implementation of IEquatable<Model>
public bool Equals(Model? other)
{
if(other is null)
return false;
return this.Id == other.Value.Id;
}
/// <summary>
/// Two models are the same model when they carry the same ID.
/// </summary>
/// <remarks>
/// The display name is decoration. A provider may report a model under a display name of its own,
/// while we know the very same model as a hardcoded fallback under a different one. Comparing the
/// ID alone keeps those two the same model, so that removing duplicates works.
///
/// Note that this overload is the one the runtime uses, for example for Distinct(). The overload
/// taking a nullable model below is a separate one and never gets called on its behalf, which is
/// why the hash code has to follow this one.
/// </remarks>
/// <param name="other">The model to compare with.</param>
/// <returns>True, when both models carry the same ID.</returns>
public bool Equals(Model other) => string.Equals(this.Id, other.Id, StringComparison.Ordinal);
/// <summary>
/// Two models are the same model when they carry the same ID.
/// </summary>
/// <param name="other">The model to compare with, which may be null.</param>
/// <returns>True, when the other model exists and carries the same ID.</returns>
public bool Equals(Model? other) => other is not null && this.Equals(other.Value);
/// <inheritdoc />
public override int GetHashCode() => this.Id?.GetHashCode(StringComparison.Ordinal) ?? 0;
#endregion
}

View File

@ -0,0 +1,69 @@
namespace AIStudio.Provider;
/// <summary>
/// The kind of an AI model, i.e. what the model is made for.
/// </summary>
/// <remarks>
/// This describes what kind of model we are dealing with. It answers a different question than the
/// Capability enum: capabilities describe what a chat model is able to do, for example whether it
/// accepts images or performs reasoning. Note that Capability.EMBEDDING marks a chat model which is
/// able to create embeddings as well, whereas ModelKind.EMBEDDING marks a model whose only purpose
/// is creating embeddings.
/// </remarks>
public enum ModelKind
{
/// <summary>
/// The model is used for chat completions.
/// </summary>
/// <remarks>
/// This is the fallback: we report a model as a chat model whenever we do not recognize any
/// other kind. Providers keep adding models we have never heard of, and a model we fail to
/// recognize must stay visible to the user instead of silently disappearing from their list.
/// </remarks>
CHAT,
/// <summary>
/// The model continues a text instead of answering in a conversation.
/// </summary>
/// <remarks>
/// These are the models from the era before chat completions, such as OpenAI's text-davinci-003.
/// Some providers still offer them, but they only work through the completions endpoint. Asking
/// them for a chat completion fails, so they must not show up as chat models.
/// </remarks>
TEXT_COMPLETION,
/// <summary>
/// The model maps text or images into a vector space.
/// </summary>
EMBEDDING,
/// <summary>
/// The model scores documents against a query to reorder search results.
/// </summary>
RERANKING,
/// <summary>
/// The model generates or edits images.
/// </summary>
IMAGE_GENERATION,
/// <summary>
/// The model transcribes audio into text.
/// </summary>
TRANSCRIPTION,
/// <summary>
/// The model synthesizes speech from text.
/// </summary>
SPEECH_SYNTHESIS,
/// <summary>
/// The model extracts text from images or scanned documents.
/// </summary>
OCR,
/// <summary>
/// The model classifies content for policy violations.
/// </summary>
MODERATION,
}

View File

@ -0,0 +1,116 @@
namespace AIStudio.Provider;
/// <summary>
/// Determines what kind of model we are dealing with, based on its name.
/// </summary>
/// <remarks>
/// Many providers serve every kind of model through one models endpoint, without telling us what
/// kind each model is. Before this class existed, every provider carried its own list of name
/// fragments to sort those models apart. Those lists disagreed with each other: a model like
/// nomic-embed-text was recognized as an embedding model by some providers, while others offered it
/// as a chat model. The knowledge about model families is the same for all providers, so it lives
/// here now.
///
/// This class recognizes what a model is NOT made for. Everything we do not recognize is reported as
/// a chat model. That direction matters: when a provider adds a model family we have never seen, the
/// user still gets to use it. Getting it wrong the other way around would hide a model the user is
/// paying for.
///
/// What this class must not become is a place for provider-specific knowledge. That a model called
/// "codestral" is a fill-in-the-middle model at Mistral, or that Alibaba's chat models all start
/// with a "q", is true for that one provider only. Such rules stay in the provider.
/// </remarks>
public static class ModelKindExtensions
{
//
// Reranking is checked before embedding: rerankers are commonly named after the embedding model
// they belong to, e.g. Qwen3-VL-Reranker-8B next to Qwen3-VL-Embedding-8B.
//
private static readonly string[] RERANKING_MARKERS = ["rerank"];
private static readonly string[] EMBEDDING_MARKERS = ["embed", "bge", "mpnet", "paraphrase", "sentence-transformers", "gte-", "e5-", "gritlm"];
//
// The models from before chat completions existed. Providers keep offering some of them, and
// Helmholtz Blablador still reports 'text-davinci-003', but asking any of them for a chat
// completion fails. We deliberately do not look for 'ada' here: three letters appear in far too
// many unrelated model names, and losing a chat model weighs heavier than keeping a dead one.
//
private static readonly string[] TEXT_COMPLETION_MARKERS = ["davinci", "babbage", "curie", "gpt-3.5-turbo-instruct"];
private static readonly string[] IMAGE_GENERATION_MARKERS = ["flux", "stable-diffusion", "sdxl", "dall-e", "midjourney", "gpt-image"];
//
// Voxtral is marketed as an audio model which understands speech, so one could expect it to work
// in a chat as well. It does not: asking Mistral for a chat completion with 'voxtral-mini-latest'
// is answered with 'Invalid model'. Voxtral therefore belongs here, next to the models which do
// nothing but transcribe.
//
private static readonly string[] TRANSCRIPTION_MARKERS = ["whisper", "-transcribe", "wav2vec", "parakeet", "voxtral"];
private static readonly string[] SPEECH_SYNTHESIS_MARKERS = ["-tts", "tts-", "-speech", "speech-"];
private static readonly string[] OCR_MARKERS = ["ocr"];
private static readonly string[] MODERATION_MARKERS = ["moderation", "guard"];
/// <summary>
/// Determines what kind of model this is, based on its name.
/// </summary>
/// <param name="model">The model to inspect.</param>
/// <returns>The recognized kind, or ModelKind.CHAT when we recognize no other kind.</returns>
public static ModelKind DetermineKind(this Model model)
{
if (string.IsNullOrWhiteSpace(model.Id) || model.IsSystemModel)
return ModelKind.CHAT;
if (HasAnyMarker(model.Id, RERANKING_MARKERS))
return ModelKind.RERANKING;
if (HasAnyMarker(model.Id, EMBEDDING_MARKERS))
return ModelKind.EMBEDDING;
if (HasAnyMarker(model.Id, TEXT_COMPLETION_MARKERS))
return ModelKind.TEXT_COMPLETION;
if (HasAnyMarker(model.Id, IMAGE_GENERATION_MARKERS))
return ModelKind.IMAGE_GENERATION;
if (HasAnyMarker(model.Id, TRANSCRIPTION_MARKERS))
return ModelKind.TRANSCRIPTION;
if (HasAnyMarker(model.Id, SPEECH_SYNTHESIS_MARKERS))
return ModelKind.SPEECH_SYNTHESIS;
if (HasAnyMarker(model.Id, OCR_MARKERS))
return ModelKind.OCR;
if (HasAnyMarker(model.Id, MODERATION_MARKERS))
return ModelKind.MODERATION;
return ModelKind.CHAT;
}
/// <summary>
/// Checks whether this model can be used for chatting.
/// </summary>
/// <param name="model">The model to check.</param>
/// <returns>True, when the model is a chat model or when we recognize no other kind.</returns>
public static bool IsChatModel(this Model model) => model.DetermineKind() is ModelKind.CHAT;
/// <summary>
/// Checks whether this model creates embeddings.
/// </summary>
/// <param name="model">The model to check.</param>
/// <returns>True, when the model is an embedding model.</returns>
public static bool IsEmbeddingModel(this Model model) => model.DetermineKind() is ModelKind.EMBEDDING;
private static bool HasAnyMarker(string modelId, string[] markers)
{
foreach (var marker in markers)
if (modelId.Contains(marker, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
}

View File

@ -115,16 +115,8 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
storeType,
"models",
modelResponse => modelResponse.Data
.Where(n =>
!n.Id.Contains("whisper", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("dall-e", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("tts", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("embedding", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("moderation", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("stable-diffusion", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("flux", StringComparison.OrdinalIgnoreCase) &&
!n.Id.Contains("midjourney", StringComparison.OrdinalIgnoreCase))
.Select(n => new Model(n.Id, n.Name)),
.Select(n => new Model(n.Id, n.Name))
.Where(model => model.IsChatModel()),
token,
apiKeyProvisional,
requestConfigurator: (request, secretKey) =>

View File

@ -91,6 +91,7 @@ public static partial class ProviderExtensions
LLMProviders.OPEN_ROUTER or
LLMProviders.HETZNER or
LLMProviders.IONOS or
LLMProviders.X or
LLMProviders.DEEP_SEEK or
LLMProviders.GROQ or

View File

@ -55,6 +55,7 @@ public static partial class ProviderExtensions
LLMProviders.PERPLEXITY => GetModelCapabilitiesPerplexity(model),
LLMProviders.OPEN_ROUTER => GetModelCapabilitiesOpenRouter(model),
LLMProviders.HETZNER => GetModelCapabilitiesOpenSource(model),
LLMProviders.IONOS => GetModelCapabilitiesOpenSource(model),
LLMProviders.GROQ => GetModelCapabilitiesOpenSource(model),
LLMProviders.FIREWORKS => GetModelCapabilitiesOpenSource(model),

View File

@ -817,6 +817,7 @@ public sealed class SettingsManager
LLMProviders.HELMHOLTZ => ConfidenceLevel.LOW,
LLMProviders.GWDG => ConfidenceLevel.LOW,
LLMProviders.HETZNER => ConfidenceLevel.LOW,
LLMProviders.IONOS => ConfidenceLevel.LOW,
LLMProviders.DEEP_SEEK => ConfidenceLevel.LOW,
LLMProviders.ALIBABA_CLOUD => ConfidenceLevel.LOW,
@ -831,6 +832,7 @@ public sealed class SettingsManager
LLMProviders.HELMHOLTZ => ConfidenceLevel.MEDIUM,
LLMProviders.GWDG => ConfidenceLevel.MEDIUM,
LLMProviders.HETZNER => ConfidenceLevel.MEDIUM,
LLMProviders.IONOS => ConfidenceLevel.MEDIUM,
_ => ConfidenceLevel.LOW,
};

View File

@ -4,6 +4,7 @@
- Added provider logos throughout AI Studio, making models easier to recognize at a glance. Configuration plugins can now give managed LLM, transcription, and embedding providers their own project icon with the optional `IconPath` field.
- Added the option for IT departments to enable assistant plugins they rolled out. Approving an assistant only stated that it is safe, so everybody still had to switch it on themselves. An approval can now also enable the assistant, either as a default which you may switch off again, or in a way your IT department keeps in place. The plugin page and the security card of the assistant tell you which of the two applies.
- Added knowledge about the latest AI models. AI Studio now recognizes Qwen 3.8 Flash, GLM-5.3 Flash, Meta's Muse Glimmer, NVIDIA's Nemotron 3.5, Tencent's Hunyuan Hy3, Grok 4, Claude Opus 5 and Sonnet 5, and Gemini 3.6 and 3.7. It knows what each of them is capable of, so images, videos, tool usage, and reasoning are available right away instead of staying hidden.
- Added the IONOS AI Model Hub as a provider for chats and embeddings. It runs open-source models in Germany, is subject to the GDPR, and IONOS states that your data is not used for training.
- Improved the safety of plugin symbols: AI Studio now shows the symbol of a plugin in isolation, so nothing inside a symbol can reach the rest of the app.
- Improved how much memory AI Studio needs. Working with large documents used to grow the app to several gigabytes, and on macOS that memory was never handed back. AI Studio now stays at a fraction of that and returns memory to your system. This matters most on devices with little memory, such as a Raspberry Pi.
- Improved the preview for large documents. It now shows you the beginning of your document instead of loading all of it, so the dialog opens right away. Your complete document still goes to the AI.

View File

@ -4,7 +4,7 @@ All provider icons are shipped with AI Studio and loaded locally. No icon trigge
## Sources
- `alibaba-cloud.svg`, `anthropic*.svg`, `deepseek.svg`, `hetzner.svg`, `mistral.svg`, `perplexity.svg`, and `x*.svg` use paths and brand colors from [Simple Icons 16.21.0](https://github.com/simple-icons/simple-icons/tree/16.21.0), licensed under [CC0-1.0](https://github.com/simple-icons/simple-icons/blob/16.21.0/LICENSE.md).
- `alibaba-cloud.svg`, `anthropic*.svg`, `deepseek.svg`, `hetzner.svg`, `ionos.svg`, `mistral.svg`, `perplexity.svg`, and `x*.svg` use paths and brand colors from [Simple Icons 16.21.0](https://github.com/simple-icons/simple-icons/tree/16.21.0), licensed under [CC0-1.0](https://github.com/simple-icons/simple-icons/blob/16.21.0/LICENSE.md).
- `hugging-face.svg` was taken from their official website (https://huggingface.co/brand)
- `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).

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#003D8F" d="M22.182 15.499a1.7634 1.7634 0 0 1-.3536-.0358 1.8319 1.8319 0 0 1-1.2086-.8632l-.0066-.012c-.1308-.2616-.0448-.5693.2-.7162a.5236.5236 0 0 1 .2689-.0743.5327.5327 0 0 1 .4632.272.558.558 0 0 0 .018.0299l.0564.0766.0757.0765a.7385.7385 0 0 0 .3453.167.7596.7596 0 0 0 .5954-.136.7206.7206 0 0 0 .299-.5386.7441.7441 0 0 0-.207-.578s-1.5994-1.6612-1.6712-1.7382a1.9604 1.9604 0 0 1-.1929-.229c-.2256-.3198-.2962-.697-.2614-1.0702a1.7361 1.7361 0 0 1 .2253-.7397s.0732-.124.0948-.1554c.0847-.1236.2515-.2834.3658-.3726a1.8813 1.8813 0 0 1 .3069-.1844 1.7494 1.7494 0 0 1 2.2815.7041.5186.5186 0 0 1 .0553.402.5298.5298 0 0 1-.2528.3263.5082.5082 0 0 1-.2593.0705.5283.5283 0 0 1-.46-.2706.6953.6953 0 0 0-.4656-.329.721.721 0 0 0-.5645.1269.688.688 0 0 0-.267.498.6998.6998 0 0 0 .1905.5398l1.6351 1.6862a1.8284 1.8284 0 0 1 .5071 1.3931c-.0465.5243-.3071.9994-.7152 1.3053a1.8425 1.8425 0 0 1-1.0984.369m-5.175-.0006a1.7608 1.7608 0 0 0 1.7585-1.7589v-3.4787a1.7587 1.7587 0 0 0-3.5173 0v3.4786a1.7608 1.7608 0 0 0 1.7587 1.7589m0-5.9342c.3777 0 .6968.319.6968.6967v3.4786a.6977.6977 0 0 1-.6968.6968.6894.6894 0 0 1-.6968-.6968v-3.4786a.6976.6976 0 0 1 .697-.6967m-7.8986.3224v5.0804a.5309.5309 0 0 0 1.0618 0V12.09Zm2.8802 2.0255V9.0328a.531.531 0 0 1 1.0619 0v5.0828zm1.053 2.9406v.1134a.533.533 0 0 1-1.0067.2408L9.1153 9.154v-.1212a.5261.5261 0 0 1 .3028-.4772.5253.5253 0 0 1 .7088.2475Zm-7.9067.6457a1.761 1.761 0 0 0 1.7586-1.7589v-3.4786a1.7587 1.7587 0 0 0-3.5173 0v3.4786a1.7609 1.7609 0 0 0 1.7587 1.7589m0-5.9342a.707.707 0 0 1 .6968.6967v3.4786a.6968.6968 0 1 1-1.3936 0v-3.4786a.6976.6976 0 0 1 .6968-.6967M0 14.9111a.5791.5791 0 1 0 1.1581 0V9.0803a.5791.5791 0 0 0-1.1582 0z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -31,8 +31,8 @@
<li>
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, Hugging Face, Groq, Fireworks, Helmholtz, GWDG,
and self-hosted models.
DeepSeek, Alibaba Cloud, OpenRouter, Hetzner, IONOS, Hugging Face, Groq, Fireworks,
Helmholtz, GWDG, and self-hosted models.
</li>
<li>
Assistants: Use ready-made assistants for common business and other tasks without writing prompts yourself.