mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 00:53:37 +00:00
Normalize model IDs before matching capabilities
This commit is contained in:
parent
e64c51bd57
commit
954012936f
@ -6,7 +6,7 @@ public static partial class ProviderExtensions
|
||||
{
|
||||
private static List<Capability> GetModelCapabilitiesAlibaba(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
// Qwen models:
|
||||
if (modelName.StartsWith("qwen"))
|
||||
|
||||
@ -6,7 +6,7 @@ public static partial class ProviderExtensions
|
||||
{
|
||||
private static List<Capability> GetModelCapabilitiesAnthropic(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
// Claude Fable 5 and Mythos 5 always use adaptive thinking:
|
||||
if(modelName.StartsWith("claude-fable-5") || modelName.StartsWith("claude-mythos-5"))
|
||||
|
||||
@ -6,7 +6,7 @@ public static partial class ProviderExtensions
|
||||
{
|
||||
private static List<Capability> GetModelCapabilitiesDeepSeek(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
// The reasoner alias points to the thinking mode of the current flash model:
|
||||
if(modelName.IndexOf("reasoner") is not -1)
|
||||
|
||||
@ -32,7 +32,7 @@ public static partial class ProviderExtensions
|
||||
var separatorIndex = model.Id.IndexOf('/');
|
||||
var vendor = separatorIndex is -1 ? string.Empty : model.Id[..separatorIndex].ToLowerInvariant();
|
||||
var bareModel = separatorIndex is -1 ? model : model with { Id = model.Id[(separatorIndex + 1)..] };
|
||||
var bareModelName = bareModel.Id.ToLowerInvariant().AsSpan();
|
||||
var bareModelName = NormalizeModelId(bareModel.Id).AsSpan();
|
||||
|
||||
var capabilities = vendor switch
|
||||
{
|
||||
|
||||
@ -6,7 +6,7 @@ public static partial class ProviderExtensions
|
||||
{
|
||||
private static List<Capability> GetModelCapabilitiesGoogle(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
if (modelName.IndexOf("gemini-") is not -1)
|
||||
{
|
||||
|
||||
@ -64,7 +64,7 @@ public static partial class ProviderExtensions
|
||||
|
||||
private static List<Capability> GetModelCapabilitiesMistral(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
// Pixtral models are able to do process images:
|
||||
if (modelName.IndexOf("pixtral") is not -1)
|
||||
|
||||
@ -6,7 +6,7 @@ public static partial class ProviderExtensions
|
||||
{
|
||||
private static List<Capability> GetModelCapabilitiesOpenAI(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
if (modelName is "gpt-4o-search-preview")
|
||||
return
|
||||
|
||||
@ -6,7 +6,7 @@ public static partial class ProviderExtensions
|
||||
{
|
||||
private static List<Capability> GetModelCapabilitiesOpenSource(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
//
|
||||
// Checking for names in the case of open source models is a hard task.
|
||||
@ -19,6 +19,13 @@ public static partial class ProviderExtensions
|
||||
// - LM Studio: llama-3.1-405b-instruct
|
||||
// - Helmholtz Blablador: 1 - Llama3 405 the best general model
|
||||
// - GWDG: Llama 3.1 405B Instruct
|
||||
// - Ollama: llama3.1:405b
|
||||
//
|
||||
// The name arrives here already normalized by NormalizeModelId: lowercase, with every
|
||||
// separator written as a single hyphen. That is why the checks below no longer carry a
|
||||
// variant with a space or a colon. What normalization cannot do is insert a separator
|
||||
// where a provider left it out, or remove one where it added it, so a family which is
|
||||
// written both as "llama3" and as "llama-3" still needs both spellings.
|
||||
//
|
||||
|
||||
//
|
||||
@ -27,7 +34,6 @@ public static partial class ProviderExtensions
|
||||
if (modelName.IndexOf("llama") is not -1)
|
||||
{
|
||||
if (modelName.IndexOf("llama4") is not -1 ||
|
||||
modelName.IndexOf("llama 4") is not -1 ||
|
||||
modelName.IndexOf("llama-4") is not -1 ||
|
||||
modelName.IndexOf("llama-v4") is not -1)
|
||||
return
|
||||
@ -52,7 +58,6 @@ public static partial class ProviderExtensions
|
||||
// All models >= 3.1 are able to do function calling:
|
||||
//
|
||||
if (modelName.IndexOf("llama3.") is not -1 ||
|
||||
modelName.IndexOf("llama 3.") is not -1 ||
|
||||
modelName.IndexOf("llama-3.") is not -1 ||
|
||||
modelName.IndexOf("llama-v3p") is not -1)
|
||||
return
|
||||
@ -101,8 +106,7 @@ public static partial class ProviderExtensions
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
if(modelName.IndexOf("deepseek-r1") is not -1 ||
|
||||
modelName.IndexOf("deepseek r1") is not -1)
|
||||
if(modelName.IndexOf("deepseek-r1") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.TEXT_OUTPUT,
|
||||
Capability.ALWAYS_REASONING,
|
||||
@ -150,8 +154,15 @@ public static partial class ProviderExtensions
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// Check for the multimodal Qwen 3.8 27B checkpoint:
|
||||
if(modelName.IndexOf("qwen3.8-27b") is not -1)
|
||||
//
|
||||
// Check for the multimodal Qwen 3.8 27B checkpoint. Blablador writes this one in two
|
||||
// further ways, which no normalization can turn into the canonical name: it separates
|
||||
// the family from the version ("Qwen 3.8-27B with DFlash on haicluster"), and its short
|
||||
// alias drops the dot ("alias-qwen38-27b").
|
||||
//
|
||||
if(modelName.IndexOf("qwen3.8-27b") is not -1 ||
|
||||
modelName.IndexOf("qwen-3.8-27b") is not -1 ||
|
||||
modelName.IndexOf("qwen38-27b") is not -1)
|
||||
return
|
||||
[
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT, Capability.VIDEO_INPUT,
|
||||
@ -475,8 +486,7 @@ public static partial class ProviderExtensions
|
||||
// template keeps the thinking channel closed by default.
|
||||
//
|
||||
if (modelName.IndexOf("gemma-4") is not -1 ||
|
||||
modelName.IndexOf("gemma4") is not -1 ||
|
||||
modelName.IndexOf("gemma 4") is not -1)
|
||||
modelName.IndexOf("gemma4") is not -1)
|
||||
{
|
||||
if (modelName.IndexOf("e2b") is not -1 ||
|
||||
modelName.IndexOf("e4b") is not -1 ||
|
||||
@ -508,8 +518,7 @@ public static partial class ProviderExtensions
|
||||
// than "1b", so that a name such as gemma-3-31b does not match it.
|
||||
//
|
||||
if (modelName.IndexOf("gemma-3") is not -1 ||
|
||||
modelName.IndexOf("gemma3") is not -1 ||
|
||||
modelName.IndexOf("gemma 3") is not -1)
|
||||
modelName.IndexOf("gemma3") is not -1)
|
||||
{
|
||||
if (modelName.IndexOf("-1b") is not -1)
|
||||
return
|
||||
|
||||
@ -6,7 +6,7 @@ public static partial class ProviderExtensions
|
||||
{
|
||||
private static List<Capability> GetModelCapabilitiesPerplexity(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
var modelName = NormalizeModelId(model.Id).AsSpan();
|
||||
|
||||
if(modelName.IndexOf("reasoning") is not -1 ||
|
||||
modelName.IndexOf("deep-research") is not -1)
|
||||
|
||||
@ -5,6 +5,70 @@ namespace AIStudio.Settings;
|
||||
|
||||
public static partial class ProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The longest model ID we normalize without going to the heap.
|
||||
/// </summary>
|
||||
private const int MAX_STACK_ALLOCATED_MODEL_ID_LENGTH = 256;
|
||||
|
||||
/// <summary>
|
||||
/// Brings a model ID into the form the capability rules are written in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every provider names the same model differently, and the difference is rarely in the words:
|
||||
/// it is in what sits between them. Ollama separates the variant with a colon
|
||||
/// ("qwen3.8:27b-mlx"), Blablador answers with a whole sentence ("10 - Muse Glimmer 30b - the
|
||||
/// newest META model"), Fireworks puts a path in front
|
||||
/// ("accounts/fireworks/models/llama-v3p1-405b-instruct"), and the hubs use hyphens. Without
|
||||
/// this, every rule would have to spell out each of those writings, which is what the Llama
|
||||
/// block used to do with four variants of one check.
|
||||
///
|
||||
/// The dots stay. They carry the version boundary: llama3 and llama3.1 are different models,
|
||||
/// and only the latter calls functions. Dropping them would merge the two.
|
||||
///
|
||||
/// The patterns in the rules are written in this normalized form already, which is why they
|
||||
/// use lowercase and hyphens throughout.
|
||||
/// </remarks>
|
||||
/// <param name="modelId">The model ID as the provider reports it.</param>
|
||||
/// <returns>The model ID in lowercase, with every separator written as a single hyphen.</returns>
|
||||
private static string NormalizeModelId(string modelId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modelId))
|
||||
return string.Empty;
|
||||
|
||||
//
|
||||
// Normalizing never makes a name longer, so the original length is always enough room.
|
||||
// Model IDs are short, which is why the buffer lives on the stack: the longest ones we
|
||||
// know of are the descriptive names Blablador answers with, at around 75 characters. A
|
||||
// provider reporting something longer still gets a correct answer, just from the heap.
|
||||
//
|
||||
Span<char> normalized = modelId.Length <= MAX_STACK_ALLOCATED_MODEL_ID_LENGTH
|
||||
? stackalloc char[modelId.Length]
|
||||
: new char[modelId.Length];
|
||||
|
||||
var length = 0;
|
||||
foreach (var character in modelId)
|
||||
{
|
||||
if (char.IsAsciiLetterOrDigit(character) || character is '.')
|
||||
{
|
||||
normalized[length++] = char.ToLowerInvariant(character);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Anything else separates two parts of the name. A leading separator, and a repeated
|
||||
// one, say nothing and would only get in the way of the patterns:
|
||||
if (length is 0 || normalized[length - 1] is '-')
|
||||
continue;
|
||||
|
||||
normalized[length++] = '-';
|
||||
}
|
||||
|
||||
// A trailing separator carries no meaning either:
|
||||
if (length > 0 && normalized[length - 1] is '-')
|
||||
length--;
|
||||
|
||||
return new string(normalized[..length]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the capabilities of the model used by the configured provider.
|
||||
/// </summary>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user