mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-13 05:53:47 +00:00
Rebuilt how AI Studio knows what a model can do (#960)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
1 parent
d21e09dd1e
commit
d85b4e71b6
287 files changed
+18341
-3677
No files matched your search
@@ -0,0 +1,221 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Models;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// Renders the corpus and its capabilities as one text, and says where that text is kept.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The snapshot is compared as text rather than parsed back into entries. A model ID may be
|
||||
/// anything a provider chooses to answer with, empty strings and separator characters included, and
|
||||
/// a parser would have to be right about all of it to be worth anything. Comparing the rendered text
|
||||
/// cannot be wrong about a name, and a diff of it reads the same in the test output as in the IDE.
|
||||
/// </remarks>
|
||||
public static class CapabilitySnapshot
|
||||
{
|
||||
/// <summary>
|
||||
/// What a model with no capabilities at all is written as.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An empty column would be an invisible statement. Providers do answer with nothing: an empty
|
||||
/// model ID and the "no provider" entry both do, and both are in the corpus.
|
||||
/// </remarks>
|
||||
private const string NOTHING = "(nothing)";
|
||||
|
||||
/// <summary>
|
||||
/// What a model nobody stated a context window for is written as.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Deliberately not a zero. A window nobody has looked up is a different statement from a
|
||||
/// window of no tokens, and reading the two as the same is the mistake this whole rebuild set
|
||||
/// out to stop making.
|
||||
/// </remarks>
|
||||
private const string NO_WINDOW = "(unknown)";
|
||||
|
||||
/// <summary>
|
||||
/// What a model nobody stated an image limit for is written as.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same reasoning as the window, and the same warning against reading it as a zero: a model
|
||||
/// whose vendor says nothing takes as many images as it takes, and the app treats it that way.
|
||||
/// </remarks>
|
||||
private const string NO_IMAGE_LIMIT = "(unknown)";
|
||||
|
||||
/// <summary>
|
||||
/// What a model nobody named a tokenizer for is written as.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Which is what the app already does for all of them: it counts with the tokenizer it ships
|
||||
/// and says that the number is an estimate. Naming one changes nothing about the counting yet;
|
||||
/// it tells a person which file to look for, and for two vendors that there is none.
|
||||
/// </remarks>
|
||||
private const string NO_TOKENIZER = "(unknown)";
|
||||
|
||||
private const string HEADER =
|
||||
"""
|
||||
# What the rules answer, for every model of the corpus.
|
||||
#
|
||||
# Generated. Do not edit by hand: run the SnapshotWriter test to write it anew, then read
|
||||
# the diff. Every line of it is a statement about a model which somebody has to agree with.
|
||||
#
|
||||
# Columns are provider, model ID as the provider reports it, the capabilities sorted by
|
||||
# name, what the model is made for, its context window in tokens, how many images it
|
||||
# accepts, and which tokenizer it uses. The ID stands here unchanged, so a line may well
|
||||
# carry leading or trailing spaces.
|
||||
#
|
||||
# A window or an image limit written as "(unknown)" is one nobody has stated a source for.
|
||||
# That is a gap, not a claim: the app then shows a person how many tokens their conversation
|
||||
# uses without telling them what it may grow to, and it stops nobody from attaching a
|
||||
# hundred pictures to a model which may well take them.
|
||||
#
|
||||
# Every model of the corpus stands here, the ones the audit found a wrong answer for
|
||||
# included. While the old rules still stood those were kept out, so that a known-wrong
|
||||
# answer could not be frozen into this file. The old rules are gone and their answers are
|
||||
# corrected, so keeping them out only hid four of their columns: ExpectedChanges.cs states
|
||||
# what each of them must answer, but it states capabilities alone.
|
||||
#
|
||||
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// The directory this source file lives in, filled in by the compiler.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The snapshot is read from the source tree, not from the build output. It is a file somebody
|
||||
/// reviews and commits, so the test has to fail against the file in the working copy rather
|
||||
/// than against a stale copy next to the assembly.
|
||||
/// </remarks>
|
||||
private static readonly string DIRECTORY = ResolveDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// Where the snapshot is kept.
|
||||
/// </summary>
|
||||
public static readonly string FILE_PATH = Path.Combine(DIRECTORY, "CapabilitySnapshot.txt");
|
||||
|
||||
/// <summary>
|
||||
/// Where a mismatching snapshot is written for comparison in the IDE.
|
||||
/// </summary>
|
||||
public static readonly string ACTUAL_FILE_PATH = Path.Combine(DIRECTORY, "CapabilitySnapshot.actual.txt");
|
||||
|
||||
/// <summary>
|
||||
/// Renders the given entries and the capabilities the current rules answer with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The text ends with the last model rather than with a line break, which is how this repository
|
||||
/// keeps its files. A generator disagreeing with that by one byte makes the test fail the next
|
||||
/// time an editor tidies the file up, and the failure says that nothing changed -- which is both
|
||||
/// true and useless.
|
||||
/// </remarks>
|
||||
/// <param name="entries">The entries to render.</param>
|
||||
/// <returns>The snapshot text, without a trailing newline and without carriage returns.</returns>
|
||||
public static string Render(IEnumerable<CorpusEntry> entries)
|
||||
{
|
||||
var lines = entries
|
||||
.OrderBy(entry => entry.Provider.ToString(), StringComparer.Ordinal)
|
||||
.ThenBy(entry => entry.ModelId, StringComparer.Ordinal)
|
||||
.Select(Line);
|
||||
|
||||
return new StringBuilder(HEADER).AppendJoin('\n', lines).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes one corpus entry as a snapshot line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The kind stands next to the capabilities rather than among them: the two answer different
|
||||
/// questions, and a model changing from a chat model into an embedding one is a different kind
|
||||
/// of news than a model gaining image input.
|
||||
/// </remarks>
|
||||
/// <param name="entry">The entry to write.</param>
|
||||
/// <returns>The line.</returns>
|
||||
private static string Line(CorpusEntry entry)
|
||||
{
|
||||
var profile = entry.Provider.GetModelProfile(new Model(entry.ModelId, null));
|
||||
return $"{entry.Provider} | {entry.ModelId} | {Describe(RebuiltRules.AsCapabilities(profile))} | {profile.Kind} | {Describe(profile.Context)} | {Describe(profile.Images)} | {Describe(profile.Tokenizer)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a tokenizer reference the way a snapshot line does.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The kind travels with the name, because the name alone would be a riddle: "o200k_base" is
|
||||
/// not a repository somebody can open, and "/v1/messages/count_tokens" is not a file somebody
|
||||
/// can download. What sort of thing it is decides what a person can do with it.
|
||||
/// </remarks>
|
||||
/// <param name="tokenizer">The reference to write.</param>
|
||||
/// <returns>The reference, or a marker when nobody named one.</returns>
|
||||
public static string Describe(TokenizerRef tokenizer) => tokenizer.IsKnown ? $"{tokenizer.Kind} {tokenizer.Id}" : NO_TOKENIZER;
|
||||
|
||||
/// <summary>
|
||||
/// Writes an image limit the way a snapshot line does.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Both numbers are named where both are known, because they answer different questions and a
|
||||
/// vendor may state either alone. Naming the one which happens to be smaller would turn two
|
||||
/// statements into one and lose which of them was actually read from a page.
|
||||
/// </remarks>
|
||||
/// <param name="limits">The limits to write.</param>
|
||||
/// <returns>The limits, or a marker when nobody stated any.</returns>
|
||||
public static string Describe(ImageLimits limits)
|
||||
{
|
||||
if (!limits.IsKnown)
|
||||
return NO_IMAGE_LIMIT;
|
||||
|
||||
var parts = new List<string>(2);
|
||||
if (limits.MaxPerMessage is { } perMessage)
|
||||
parts.Add($"{perMessage.ToString(CultureInfo.InvariantCulture)} per message");
|
||||
|
||||
if (limits.MaxPerRequest is { } perRequest)
|
||||
parts.Add($"{perRequest.ToString(CultureInfo.InvariantCulture)} per request");
|
||||
|
||||
return string.Join(", ", parts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a context window the way a snapshot line does.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Plain digits rather than thousands separators: the number is read by whoever reviews the
|
||||
/// diff, and a separator would make the file depend on which machine generated it.
|
||||
/// </remarks>
|
||||
/// <param name="window">The window to write.</param>
|
||||
/// <returns>The window, or a marker when nobody stated one.</returns>
|
||||
public static string Describe(ContextWindow window)
|
||||
{
|
||||
if (!window.IsKnown)
|
||||
return NO_WINDOW;
|
||||
|
||||
return window.RaisableToTokens is { } raisable
|
||||
? $"{window.DefaultTokens} up to {raisable}"
|
||||
: window.DefaultTokens.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes capabilities the way a snapshot line does.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sorted by name, and duplicates are kept rather than folded away: a capability appearing twice
|
||||
/// is something to see, not something to hide.
|
||||
/// </remarks>
|
||||
/// <param name="capabilities">The capabilities to write.</param>
|
||||
/// <returns>The capability names, or a marker when there are none.</returns>
|
||||
public static string Describe(IEnumerable<Capability> capabilities)
|
||||
{
|
||||
var names = capabilities.Select(capability => capability.ToString()).Order(StringComparer.Ordinal).ToList();
|
||||
return names.Count is 0 ? NOTHING : string.Join(", ", names);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the snapshot as it stands in the source tree.
|
||||
/// </summary>
|
||||
/// <returns>The snapshot text with its line endings normalized, or null when there is none yet.</returns>
|
||||
public static string? Read() => File.Exists(FILE_PATH) ? File.ReadAllText(FILE_PATH).Replace("\r\n", "\n") : null;
|
||||
|
||||
private static string ResolveDirectory([CallerFilePath] string sourceFilePath = "") => Path.GetDirectoryName(sourceFilePath)!;
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
# What the rules answer, for every model of the corpus.
|
||||
#
|
||||
# Generated. Do not edit by hand: run the SnapshotWriter test to write it anew, then read
|
||||
# the diff. Every line of it is a statement about a model which somebody has to agree with.
|
||||
#
|
||||
# Columns are provider, model ID as the provider reports it, the capabilities sorted by
|
||||
# name, what the model is made for, its context window in tokens, how many images it
|
||||
# accepts, and which tokenizer it uses. The ID stands here unchanged, so a line may well
|
||||
# carry leading or trailing spaces.
|
||||
#
|
||||
# A window or an image limit written as "(unknown)" is one nobody has stated a source for.
|
||||
# That is a gap, not a claim: the app then shows a person how many tokens their conversation
|
||||
# uses without telling them what it may grow to, and it stops nobody from attaching a
|
||||
# hundred pictures to a model which may well take them.
|
||||
#
|
||||
# Every model of the corpus stands here, the ones the audit found a wrong answer for
|
||||
# included. While the old rules still stood those were kept out, so that a known-wrong
|
||||
# answer could not be frozen into this file. The old rules are gone and their answers are
|
||||
# corrected, so keeping them out only hid four of their columns: ExpectedChanges.cs states
|
||||
# what each of them must answer, but it states capabilities alone.
|
||||
#
|
||||
ALIBABA_CLOUD | qvq-max | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen-max-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen-mt-plus | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen-plus-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen-turbo-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen-vl-max | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen2.5-14b-instruct-1m | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen2.5-72b-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen2.5-omni-7b | AUDIO_INPUT, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, SPEECH_OUTPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen2.5-vl-72b-instruct | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3-235b-a22b | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3-omni-flash | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, SPEECH_OUTPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3-vl-plus | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.5-plus | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.6-max | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.7-max | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.7-max-2026-05-17 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.7-max-2026-06-08 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.7-max-preview | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.8-27b | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.8-flash | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwen3.8-max | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1000000 | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwq-32b | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | qwq-plus | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
ALIBABA_CLOUD | text-embedding-v3 | EMBEDDING, TEXT_INPUT | EMBEDDING | (unknown) | (unknown) | (unknown)
|
||||
ANTHROPIC | claude-3-5-haiku-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-3-5-sonnet-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-3-7-sonnet-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-3-opus-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-7-sonnet | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-fable-5-1 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | 600 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-haiku-4-5-20251001 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-mythos-5 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | 600 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-opus-4-0 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
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
|
||||
ANTHROPIC | claude-sonnet-4-0 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | 100 per request | PROVIDER_API /v1/messages/count_tokens
|
||||
ANTHROPIC | claude-sonnet-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
|
||||
DEEP_SEEK | deepseek-chat | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
DEEP_SEEK | deepseek-reasoner | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
DEEP_SEEK | deepseek-v3.2-exp | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
DEEP_SEEK | deepseek-v4 | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | (unknown) | (unknown)
|
||||
DEEP_SEEK | deepseek-v4-vision | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | (unknown) | (unknown)
|
||||
FIREWORKS | accounts/fireworks/models/deepseek-v3 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
FIREWORKS | accounts/fireworks/models/llama-v3p1-405b-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 131072 | (unknown) | (unknown)
|
||||
FIREWORKS | accounts/fireworks/models/qwen3-235b-a22b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
FIREWORKS | whisper-v3 | SPEECH_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
||||
GOOGLE | gemini-1.0-pro-vision | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GOOGLE | gemini-2.0-flash | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | gemini-2.0-flash-live-001 | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, SPEECH_INPUT, SPEECH_OUTPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GOOGLE | gemini-2.5-flash | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1048576 | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | gemini-2.5-flash-image | CHAT_COMPLETION_API, IMAGE_OUTPUT, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | IMAGE_GENERATION | (unknown) | (unknown) | (unknown)
|
||||
GOOGLE | gemini-2.5-flash-lite | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1048576 | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | gemini-2.5-pro | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1048576 | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | gemini-3-flash | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1000000 | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | gemini-3-pro | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1000000 | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | gemini-3-pro-image | ALWAYS_REASONING, CHAT_COMPLETION_API, IMAGE_OUTPUT, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | IMAGE_GENERATION | (unknown) | (unknown) | (unknown)
|
||||
GOOGLE | gemini-3.1-flash-image | ALWAYS_REASONING, CHAT_COMPLETION_API, IMAGE_OUTPUT, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | IMAGE_GENERATION | (unknown) | (unknown) | (unknown)
|
||||
GOOGLE | gemini-flash-latest | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1000000 | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | gemini-pro-latest | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1000000 | 3600 per request | PROVIDER_API countTokens
|
||||
GOOGLE | imagen-4.0-generate-001 | IMAGE_OUTPUT, TEXT_INPUT | IMAGE_GENERATION | (unknown) | (unknown) | (unknown)
|
||||
GOOGLE | text-embedding-004 | EMBEDDING, TEXT_INPUT | EMBEDDING | (unknown) | (unknown) | (unknown)
|
||||
GROQ | llama-3.3-70b-versatile | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 131072 | (unknown) | (unknown)
|
||||
GROQ | moonshotai/kimi-k2-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GROQ | openai/gpt-oss-120b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 131072 | (unknown) | (unknown)
|
||||
GROQ | qwen/qwen3-32b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GROQ | whisper-large-v3-turbo | SPEECH_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
||||
GWDG | claude-sonnet-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
|
||||
GWDG | deepseek-r1 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GWDG | e5-mistral-7b-instruct | EMBEDDING, TEXT_INPUT | EMBEDDING | (unknown) | (unknown) | (unknown)
|
||||
GWDG | gemma-3-27b-it | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GWDG | gpt-5.5 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||
GWDG | internvl2.5-8b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GWDG | meta-llama-3.1-8b-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 131072 | (unknown) | (unknown)
|
||||
GWDG | qwen3-235b-a22b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
GWDG | whisper-large-v2 | SPEECH_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
||||
HELMHOLTZ | 01 - GPT-5.5 - great overall performance | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||
HELMHOLTZ | 1 - Llama3 405 the best general model | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HELMHOLTZ | 10 - Muse Glimmer 30b - the newest META model | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HELMHOLTZ | Qwen 3.8-27B with DFlash on haicluster | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HELMHOLTZ | alias-qwen38-27b | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HETZNER | gpt-oss-120b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 131072 | (unknown) | (unknown)
|
||||
HETZNER | qwen3-coder-30b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HUGGINGFACE | HuggingFaceTB/SmolLM3-3B | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HUGGINGFACE | Qwen/Qwen3.8-27B | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HUGGINGFACE | deepseek-ai/DeepSeek-R1-Distill-Qwen-32B | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HUGGINGFACE | google/gemma-4-31B-it:novita | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HUGGINGFACE | meta-llama/Llama-4-Scout-17B-16E-Instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HUGGINGFACE | meta-llama/Meta-Llama-3.1-405B-Instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 131072 | (unknown) | (unknown)
|
||||
HUGGINGFACE | mistralai/Magistral-Small-2509 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
HUGGINGFACE | openai/gpt-oss-120b:fireworks-ai | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 131072 | (unknown) | (unknown)
|
||||
IONOS | meta-llama/Llama-3.3-70B-Instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 131072 | (unknown) | (unknown)
|
||||
IONOS | mistralai/Mistral-Small-24B-Instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
LITE_LLM | anthropic/claude-sonnet-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
|
||||
LITE_LLM | azure/gpt-5.6 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||
LITE_LLM | bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
LITE_LLM | the-fast-one | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | codestral-2508 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | magistral-medium-2506 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | ministral-14b-2512 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | ministral-3b-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | ministral-8b-2410 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | mistral-large-2411 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-large-2512 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-large-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-medium-2505 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-medium-2508 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-medium-2604 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-medium-3-5 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-medium-3.5 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-medium-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
MISTRAL | mistral-saba-2502 | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | mistral-small-2501 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | mistral-small-2503 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | mistral-small-2603 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | mistral-small-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | open-mistral-nemo | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
MISTRAL | pixtral-12b-2409 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 128000 | (unknown) | (unknown)
|
||||
MISTRAL | pixtral-large-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 128000 | (unknown) | (unknown)
|
||||
MISTRAL | voxtral-small-2507 | CHAT_COMPLETION_API, FUNCTION_CALLING, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
||||
NONE | gpt-5.6 | (nothing) | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_AI | | (nothing) | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_AI | gpt-3.5-turbo | RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | TIKTOKEN cl100k_base
|
||||
OPEN_AI | gpt-3.5-turbo-16k | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | TIKTOKEN cl100k_base
|
||||
OPEN_AI | gpt-4 | RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | 8192 | (unknown) | TIKTOKEN cl100k_base
|
||||
OPEN_AI | gpt-4-0613 | RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | 8192 | (unknown) | TIKTOKEN cl100k_base
|
||||
OPEN_AI | gpt-4-turbo | FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | 128000 | (unknown) | TIKTOKEN cl100k_base
|
||||
OPEN_AI | gpt-4o | FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 128000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-4o-audio-preview | FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | SPEECH_SYNTHESIS | 128000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-4o-mini | FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 128000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-4o-mini-search-preview | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 128000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-4o-search-preview | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 128000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5 | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 400000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5-chat-latest | FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 400000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5-mini | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 400000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5-nano | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 400000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5.1 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 400000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5.1-codex | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 400000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5.2 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 400000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5.3 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5.4 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5.5 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-5.6 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | gpt-6-astra | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | (unknown)
|
||||
OPEN_AI | gpt-6-astra-mini | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | (unknown)
|
||||
OPEN_AI | o1 | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | o1-mini | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | o1-pro | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | 200000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | o3 | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 200000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | o3-mini | ALWAYS_REASONING, FUNCTION_CALLING, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | o3-pro | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 200000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | o4-mini | ALWAYS_REASONING, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, RESPONSES_API, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 200000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_AI | text-embedding-3-large | EMBEDDING, TEXT_INPUT | EMBEDDING | (unknown) | (unknown) | TIKTOKEN cl100k_base
|
||||
OPEN_AI | whisper-1 | SPEECH_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | 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
|
||||
OPEN_ROUTER | deepseek/deepseek-chat-v3.1 | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | deepseek/deepseek-r1-distill-llama-70b | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | google/gemini-3.7-flash | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1000000 | 3600 per request | PROVIDER_API countTokens
|
||||
OPEN_ROUTER | google/gemma-4-31b-it | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | meta-llama/llama-4-maverick | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | minimax/minimax-m2 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | mistralai/mistral-large-3 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
OPEN_ROUTER | moonshotai/kimi-k2-thinking | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | nvidia/nemotron-3-49b | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | openai/gpt-5.6 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||
OPEN_ROUTER | openai/gpt-oss-120b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 131072 | (unknown) | (unknown)
|
||||
OPEN_ROUTER | perplexity/sonar-reasoning | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | qwen/qwen3.8-flash-next | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
OPEN_ROUTER | z-ai/glm-5.3 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
PERPLEXITY | sonar | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | (unknown)
|
||||
PERPLEXITY | sonar-deep-research | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | (unknown)
|
||||
PERPLEXITY | sonar-pro | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (unknown) | (unknown) | (unknown)
|
||||
PERPLEXITY | sonar-reasoning | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | (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)
|
||||
SELF_HOSTED | | (nothing) | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | --- | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | 01-ai/yi-large | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | a-model-nobody-has-heard-of | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | apertus-1.5-8b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | apriel-1.5-15b-thinker | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | apriel-1.6-15b-thinker | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | aya-expanse:8b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | aya-vision:8b | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | command-a-plus | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | command-a-reasoning | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | command-a-vision | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | command-a:111b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | command-r7b:7b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | deepseek-r1-distill-llama-70b | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | deepseek-r1:32b | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | deepseek-v2.5 | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | deepseek-v3.1:671b | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | ernie-4.5-21b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | ernie-4.5-vl-28b | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | ernie-x1.1-thinking | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | eurollm-9b-instruct | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | falcon-h1-1.5b-tool-calling | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | falcon-h1:7b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | falcon3:10b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | gemma2:9b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | gemma3:1b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | gemma3:27b | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | gemma3n:e4b | AUDIO_INPUT, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | gemma4:31b | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | gemma4:e2b | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | glm-4-9b-chat | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | glm-4.5v | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | glm-4.6:latest | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | glm-5-2 | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | glm-5.3-flash-nvfp4 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | gpt-oss:20b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 131072 | (unknown) | (unknown)
|
||||
SELF_HOSTED | granite-embedding:278m | EMBEDDING, TEXT_INPUT | EMBEDDING | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | granite3.2-vision:2b | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | granite3.3:8b | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | granite4.2:8b | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | hunyuan:7b | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | inclusionai/ling-mini-2.0 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | internlm3:8b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | internvl3-8b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | kimi-k2.7-code | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | kimi-k2:1t | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | kimi-k3:latest | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | kimi-vl:16b | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | ling-1t | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | llama-3.1-405b-base | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | 131072 | (unknown) | (unknown)
|
||||
SELF_HOSTED | llama-3.3-nemotron-super-49b | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | llama2:13b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | llama3.2-vision:11b | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | llama3.2:3b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | 131072 | (unknown) | (unknown)
|
||||
SELF_HOSTED | magistral:24b | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | minimax-m2:latest | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | minimax-text-01 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | ministral-8b-instruct-2410 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | mistral-nemo:12b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | mistral-small-3.1-24b-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | mistral-small3.2:24b | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | muse-glimmer-30b | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | nemotron-3-49b | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | nomic-embed-text:latest | EMBEDDING, TEXT_INPUT | EMBEDDING | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | nvidia-nemotron-3.5-lightning-30b-a3b-nvfp4 | CHAT_COMPLETION_API, FUNCTION_CALLING, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | occiglot-7b-eu5 | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | olmo-3-32b-think | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | olmo2:13b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | olmo3:7b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | phi-4-mini-reasoning | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | phi-4-multimodal-instruct | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | phi-4-reasoning-vision | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | phi3:14b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | phi4-mini:latest | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | phi4:14b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen2.5-vl-7b-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen3-coder:30b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen3.5:32b | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen3.6:32b | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen3.8-2.4t-a95b | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen3.8:27b | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen3.8:27b-mlx | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwen3.8:latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | qwq:32b | ALWAYS_REASONING, CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | ring-1t | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | salamandra-7b-instruct | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | salamandra-7b-instruct-tools | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | seed-oss:36b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | smollm2:1.7b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | smollm3:3b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | starling-lm:7b | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | tencent/hy3 | CHAT_COMPLETION_API, FUNCTION_CALLING, OPTIONAL_REASONING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | teuken-7b-instruct | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | voxtral-mini-3b | CHAT_COMPLETION_API, FUNCTION_CALLING, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
||||
SELF_HOSTED | yi-1.5:9b | CHAT_COMPLETION_API, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
X | grok-2-vision-1212 | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
X | grok-3 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
X | grok-3-mini | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
X | grok-4 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
X | grok-4-fast-reasoning | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
X | grok-4.20 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | (unknown) | (unknown)
|
||||
X | grok-4.20-non-reasoning | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 1000000 | (unknown) | (unknown)
|
||||
X | grok-5 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||
X | grok-build-0.1 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | 256000 | (unknown) | (unknown)
|
||||
@@ -0,0 +1,11 @@
|
||||
using AIStudio.Provider;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// One model of the corpus, written the way one provider writes it.
|
||||
/// </summary>
|
||||
/// <param name="Provider">The provider the model is reached through.</param>
|
||||
/// <param name="ModelId">The model ID exactly as that provider reports it, before any normalization.</param>
|
||||
/// <param name="Origin">Where this spelling comes from.</param>
|
||||
public sealed record CorpusEntry(LLMProviders Provider, string ModelId, CorpusOrigin Origin);
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// Says where a spelling in the corpus comes from.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A corpus is only worth as much as the names in it. Anybody can invent a model ID which makes a
|
||||
/// rule look right, so every entry has to say who writes the name that way. The values below are
|
||||
/// ordered by how easy the claim is to check: the first three point at something in this repository,
|
||||
/// the last one does not and is the reason the fallback needs testing at all.
|
||||
/// </remarks>
|
||||
public enum CorpusOrigin
|
||||
{
|
||||
/// <summary>
|
||||
/// A rule in the current capability code names this spelling literally.
|
||||
/// </summary>
|
||||
NAMED_BY_A_RULE,
|
||||
|
||||
/// <summary>
|
||||
/// The app carries this model in a built-in list, such as the one Alibaba Cloud models are
|
||||
/// picked from when the provider serves no catalog.
|
||||
/// </summary>
|
||||
BUILT_INTO_THE_APP,
|
||||
|
||||
/// <summary>
|
||||
/// A comment in the current capability code quotes this spelling as an example of how some
|
||||
/// host writes model names: an Ollama tag, a Fireworks path, a hub prefix, a Blablador
|
||||
/// sentence.
|
||||
/// </summary>
|
||||
QUOTED_AS_A_NAME_SHAPE,
|
||||
|
||||
/// <summary>
|
||||
/// The manual verification list of the rebuild plan asks for this model.
|
||||
/// </summary>
|
||||
ON_THE_MANUAL_TEST_LIST,
|
||||
|
||||
/// <summary>
|
||||
/// A name the provider serves which no rule literal mentions. These are the entries which say
|
||||
/// what happens to everything the rules were not written for.
|
||||
/// </summary>
|
||||
NAMED_BY_NO_RULE,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using AIStudio.Provider;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// A corpus entry whose current answer the audit showed to be wrong.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// While the old rules still stood, these were kept out of the snapshot: it says "this must not
|
||||
/// change", and writing a known-wrong answer into it would have turned the rebuild into a copy of
|
||||
/// the mistake. That has been over since the old rules were deleted, and keeping them out had a
|
||||
/// price nobody had counted -- an entry here states capabilities and nothing else, so the kind, the
|
||||
/// context window, the image limit and the tokenizer of these models were reviewed nowhere at all.
|
||||
/// Five embedding models sat in that blind spot. They are in the snapshot now like everything else,
|
||||
/// and what this file still does is the part no snapshot can: saying what the answer has to be,
|
||||
/// rather than only noticing that it changed.
|
||||
/// </remarks>
|
||||
/// <param name="Provider">The provider the model is reached through.</param>
|
||||
/// <param name="ModelId">The model ID, exactly as it appears in the corpus.</param>
|
||||
/// <param name="AnswerToday">What the rules being replaced answered. History now: the code that produced it is gone, so nothing checks this any more. It stays because an entry saying only what is right leaves the reader wondering what was wrong.</param>
|
||||
/// <param name="AnswerWanted">What the rebuilt rules have to answer.</param>
|
||||
/// <param name="Reason">Why the current answer is wrong, in one sentence.</param>
|
||||
/// <param name="Source">Where that can be checked.</param>
|
||||
public sealed record ExpectedChange(LLMProviders Provider, string ModelId, IReadOnlyList<Capability> AnswerToday, IReadOnlyList<Capability> AnswerWanted, string Reason, string Source);
|
||||
@@ -0,0 +1,181 @@
|
||||
using static AIStudio.Provider.Capability;
|
||||
using static AIStudio.Provider.LLMProviders;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// The answers the rebuild has to change, one entry per model.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every entry here was found by running the corpus against the rules as they stand and reading
|
||||
/// what came back. They are kept out of the snapshot so that the rebuild does not copy them: a
|
||||
/// snapshot says "do not change this", and a wrong answer is the one thing that must change.
|
||||
///
|
||||
/// Three kinds of mistake are collected below, and they are the three the new architecture is meant
|
||||
/// to make impossible rather than fix one by one:
|
||||
///
|
||||
/// - A model which is not a chat model at all is answered as if it were one. The app already knows
|
||||
/// better: it asks its providers for embedding and transcription models through methods of their
|
||||
/// own. The capability rules never hear about that and hand out tool calling and image input.
|
||||
/// - The same model gets two different answers depending on which spelling it arrives in. That is
|
||||
/// the routing graph leaking into the rules, and it is what the explicit hosts are for.
|
||||
/// - A prefix rule swallows a variant whose name says the opposite. That is priority written by
|
||||
/// hand, and it is what computed specificity is for.
|
||||
/// </remarks>
|
||||
public static class ExpectedChanges
|
||||
{
|
||||
/// <summary>
|
||||
/// Where the app itself states that a model is not a chat model.
|
||||
/// </summary>
|
||||
private const string THE_APP_LISTS_IT_AS_AN_EMBEDDING_MODEL = "The app asks every provider for its embedding models separately, through IProvider.GetEmbeddingModels.";
|
||||
|
||||
/// <summary>
|
||||
/// Every model whose answer has to change.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<ExpectedChange> ENTRIES =
|
||||
[
|
||||
//
|
||||
// Embedding models. They turn text into a vector; there is nothing for them to call a
|
||||
// function with and no image for them to look at. What they need stated is that they embed,
|
||||
// which the capability vocabulary has a word for and the rules never use.
|
||||
//
|
||||
new(OPEN_AI, "text-embedding-3-large",
|
||||
AnswerToday: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, RESPONSES_API, WEB_SEARCH],
|
||||
AnswerWanted: [TEXT_INPUT, EMBEDDING],
|
||||
Reason: "An embedding model is answered with the OpenAI chat default, tool calling and image input included.",
|
||||
Source: THE_APP_LISTS_IT_AS_AN_EMBEDDING_MODEL),
|
||||
|
||||
new(GOOGLE, "text-embedding-004",
|
||||
AnswerToday: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, EMBEDDING],
|
||||
Reason: "An embedding model is answered with the Google default for everything which is not a Gemini.",
|
||||
Source: THE_APP_LISTS_IT_AS_AN_EMBEDDING_MODEL),
|
||||
|
||||
new(ALIBABA_CLOUD, "text-embedding-v3",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, EMBEDDING],
|
||||
Reason: "An embedding model is answered with the Alibaba default, because its name starts with none of the Qwen prefixes.",
|
||||
Source: "Provider/AlibabaCloud/ProviderAlibabaCloud.cs adds it in GetEmbeddingModels and filters the catalog by the prefix \"text-embedding-\"."),
|
||||
|
||||
new(SELF_HOSTED, "nomic-embed-text:latest",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, EMBEDDING],
|
||||
Reason: "An embedding model reaches the global fallback, which assumes an instruction-tuned model that calls functions.",
|
||||
Source: THE_APP_LISTS_IT_AS_AN_EMBEDDING_MODEL),
|
||||
|
||||
new(SELF_HOSTED, "granite-embedding:278m",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, EMBEDDING],
|
||||
Reason: "The Granite block answers about a checkpoint which embeds, and it hands out tool calling for it.",
|
||||
Source: THE_APP_LISTS_IT_AS_AN_EMBEDDING_MODEL),
|
||||
|
||||
new(GWDG, "e5-mistral-7b-instruct",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, EMBEDDING],
|
||||
Reason: "An embedding model is judged by the Mistral rules, because its name carries the word.",
|
||||
Source: THE_APP_LISTS_IT_AS_AN_EMBEDDING_MODEL),
|
||||
|
||||
//
|
||||
// Transcription models. They take speech and write it down. Three of the four are in the
|
||||
// app's own list of transcription models, with the provider's documentation next to them.
|
||||
//
|
||||
new(OPEN_AI, "whisper-1",
|
||||
AnswerToday: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, RESPONSES_API, WEB_SEARCH],
|
||||
AnswerWanted: [SPEECH_INPUT, TEXT_OUTPUT],
|
||||
Reason: "A transcription model is answered with the OpenAI chat default, web search and image input included.",
|
||||
Source: "The app asks every provider for its transcription models separately, through IProvider.GetTranscriptionModels."),
|
||||
|
||||
new(FIREWORKS, "whisper-v3",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [SPEECH_INPUT, TEXT_OUTPUT],
|
||||
Reason: "A transcription model reaches the global fallback and is told it calls functions.",
|
||||
Source: "Provider/Fireworks/ProviderFireworks.cs returns it from GetTranscriptionModels."),
|
||||
|
||||
new(GWDG, "whisper-large-v2",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [SPEECH_INPUT, TEXT_OUTPUT],
|
||||
Reason: "A transcription model reaches the global fallback and is told it calls functions.",
|
||||
Source: "Provider/GWDG/ProviderGWDG.cs returns it from GetTranscriptionModels."),
|
||||
|
||||
new(GROQ, "whisper-large-v3-turbo",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [SPEECH_INPUT, TEXT_OUTPUT],
|
||||
Reason: "A transcription model reaches the global fallback and is told it calls functions.",
|
||||
Source: "Same model family as the Whisper entries the app lists for Fireworks and GWDG."),
|
||||
|
||||
//
|
||||
// An image generation model. It draws a picture from a description; there is no
|
||||
// conversation in it and nothing to call a function with.
|
||||
//
|
||||
new(GOOGLE, "imagen-4.0-generate-001",
|
||||
AnswerToday: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, IMAGE_OUTPUT],
|
||||
Reason: "An image generation model is answered with the Google default for everything which is not a Gemini: it is told it reads images, writes text, and calls functions, and the one thing it does is not said at all.",
|
||||
Source: "Provider/Google/ProviderGoogle.cs keeps only names beginning with \"gemini-\" in its chat model list, so this model is never a chat model to begin with; Provider/ModelKindExtensions.cs classifies image generation separately."),
|
||||
|
||||
//
|
||||
// One model, two spellings, two answers.
|
||||
//
|
||||
new(LITE_LLM, "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
Reason: "Claude 3.5 Sonnet loses its image input when it arrives under the Bedrock spelling: the vendor sits behind a dot rather than a slash, so neither the gateway detection nor the reseller check finds it.",
|
||||
Source: "The same model as \"anthropic/claude-sonnet-4-0\" and the other Claude entries of this corpus, which all report image input."),
|
||||
|
||||
new(SELF_HOSTED, "mistral-small-3.1-24b-instruct",
|
||||
AnswerToday: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, OPTIONAL_REASONING, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
Reason: "Mistral Small 3.1 is told that it thinks, and the very same model is told the opposite when it arrives through Mistral's own API. The rules for the open weights answer for the whole 3 and 4 range in one line, and reasoning arrived with 4.",
|
||||
Source: "The corpus entry \"mistral-small-2503\" is this model at Mistral and reports no reasoning; Mistral names Magistral as the thinking model of that generation."),
|
||||
|
||||
new(HELMHOLTZ, "01 - GPT-5.5 - great overall performance",
|
||||
AnswerToday: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, WEB_SEARCH, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, REASONING_BY_DEFAULT, WEB_SEARCH, CHAT_COMPLETION_API],
|
||||
Reason: "The descriptive name is recognized as a GPT model and then placed nowhere: every version rule matches the beginning of the name, which here is the list number. The model loses the reasoning it is known for.",
|
||||
Source: "The GWDG entry \"gpt-5.5\" of this corpus is the same model and does report reasoning by default."),
|
||||
|
||||
//
|
||||
// A rule written for one spelling of a name, while the engine people actually run writes
|
||||
// another. The rule is right about the model and never fires.
|
||||
//
|
||||
new(SELF_HOSTED, "granite4.2:8b",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, TEXT_OUTPUT, REASONING_BY_DEFAULT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
Reason: "Granite 4.2 thinks unless the request says otherwise, and there is a rule which says so -- for \"granite-4.2\". Ollama glues the version to the family name, so the rule never sees the models anybody runs locally.",
|
||||
Source: "IBM documents thinking on by default from Granite 4.2; the Ollama library lists the same checkpoint as \"granite4.2\"."),
|
||||
|
||||
new(SELF_HOSTED, "granite3.3:8b",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, TEXT_OUTPUT, OPTIONAL_REASONING, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
Reason: "The same spelling problem one generation earlier: Granite 3.3 has a thinking toggle, and the rule for it is written as \"granite-3.3\".",
|
||||
Source: "IBM documents the thinking toggle for Granite 3.2 and 3.3; the Ollama library lists the checkpoint as \"granite3.3\"."),
|
||||
|
||||
//
|
||||
// One vendor's block swallowing another vendor's model, for no reason but where the two
|
||||
// blocks stand in the file.
|
||||
//
|
||||
new(SELF_HOSTED, "llama-3.3-nemotron-super-49b",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, TEXT_OUTPUT, OPTIONAL_REASONING, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
Reason: "An NVIDIA model is answered by the Llama rules because it carries the name of the weights it was built from, and the Llama block stands above the Nemotron one. It loses the thinking switch, which is one of the two things NVIDIA changed about those weights.",
|
||||
Source: "The corpus entry \"nemotron-3-49b\" is the generation after it and does report thinking; NVIDIA documents the detailed thinking switch for the Llama-Nemotron models."),
|
||||
|
||||
//
|
||||
// A prefix rule swallowing the variant which says the opposite.
|
||||
//
|
||||
new(OPEN_AI, "gpt-5-chat-latest",
|
||||
AnswerToday: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, ALWAYS_REASONING, WEB_SEARCH, RESPONSES_API],
|
||||
AnswerWanted: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, WEB_SEARCH, RESPONSES_API],
|
||||
Reason: "The alias for the non-reasoning GPT-5 is claimed by the \"gpt-5-\" prefix rule and is told it always reasons, which is the one thing its name rules out.",
|
||||
Source: "OpenAI names this alias as the non-reasoning model of the GPT-5 line; the corpus entry \"gpt-5\" next to it is the reasoning one."),
|
||||
|
||||
//
|
||||
// A model nobody had written a rule for yet, found while testing the switch-over.
|
||||
//
|
||||
new(X, "grok-build-0.1",
|
||||
AnswerToday: [TEXT_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
AnswerWanted: [TEXT_INPUT, MULTIPLE_IMAGE_INPUT, TEXT_OUTPUT, FUNCTION_CALLING, CHAT_COMPLETION_API],
|
||||
Reason: "The agentic coding model of the Grok line reads pictures, and the family fallback it reaches says text only.",
|
||||
Source: "https://x.ai/news/grok-build-0-1 states text and image input, tool calling, and a 256K context window."),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using static AIStudio.Provider.LLMProviders;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// The models of the corpus no rule answers for, and why each of them is all right that way.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Hugging Face carries more than a hundred thousand models. Writing a rule for each is not a goal
|
||||
/// anybody could reach, so the question was never whether models fall through to the default but
|
||||
/// which ones may. This list is that decision, written down: every model here was looked at once,
|
||||
/// and leaving it to the default was the answer.
|
||||
///
|
||||
/// It exists because the alternative is silence. A family nobody got round to and a family nobody
|
||||
/// wanted look exactly the same from the outside -- both are simply missing -- and the difference
|
||||
/// only survives if somebody writes it down. The verification run reads this list, and a test holds
|
||||
/// it against the rules from both sides: nothing falls through unlisted, and nothing stays listed
|
||||
/// once a rule does answer for it.
|
||||
///
|
||||
/// What the default says is that a model reads and writes text, speaks the chat completion API, and
|
||||
/// calls functions. The last part is a guess, and the one that matters: the models where it goes
|
||||
/// the wrong way are named in WithoutToolCallingFamily instead of being left here.
|
||||
/// </remarks>
|
||||
public static class LeftToTheDefault
|
||||
{
|
||||
/// <summary>
|
||||
/// A model whose answer the default already gets right, word for word.
|
||||
/// </summary>
|
||||
private const string THE_DEFAULT_SAYS_THE_SAME = "The default answers exactly what the rules for it answer today: text in, text out, and tool calling.";
|
||||
|
||||
/// <summary>
|
||||
/// A model which keeps what it needs and loses what was extra.
|
||||
/// </summary>
|
||||
private const string THE_DEFAULT_KEEPS_WHAT_MATTERS = "A family we decided not to write down. The default keeps the chat and the tool calling; what it drops is the thinking, which a person turns back on in the expert settings and an organization states in a model plugin.";
|
||||
|
||||
/// <summary>
|
||||
/// A model which reads more than text, and is told it does not.
|
||||
/// </summary>
|
||||
private const string THE_DEFAULT_DROPS_THE_MODALITIES = "A family we decided not to write down. The default cannot know what it reads besides text, so images have to be turned on by hand -- in the expert settings, or for everybody through a model plugin.";
|
||||
|
||||
/// <summary>
|
||||
/// Something a provider answered with which was never the name of a model.
|
||||
/// </summary>
|
||||
private const string NOT_A_MODEL_AT_ALL = "Not a model name. It is in the corpus because providers really answer with it, and the rules have to stay quiet rather than invent something.";
|
||||
|
||||
/// <summary>
|
||||
/// Every model which reaches the global default on purpose.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<ModelLeftToTheDefault> ENTRIES =
|
||||
[
|
||||
//
|
||||
// Families whose answer the default already is. Writing them down would add a file and
|
||||
// change nothing about a single answer.
|
||||
//
|
||||
new(SELF_HOSTED, "olmo3:7b", THE_DEFAULT_SAYS_THE_SAME),
|
||||
new(SELF_HOSTED, "falcon3:10b", THE_DEFAULT_SAYS_THE_SAME),
|
||||
new(SELF_HOSTED, "falcon-h1-1.5b-tool-calling", THE_DEFAULT_SAYS_THE_SAME),
|
||||
new(SELF_HOSTED, "salamandra-7b-instruct-tools", THE_DEFAULT_SAYS_THE_SAME),
|
||||
new(SELF_HOSTED, "ling-1t", THE_DEFAULT_SAYS_THE_SAME),
|
||||
new(SELF_HOSTED, "inclusionai/ling-mini-2.0", THE_DEFAULT_SAYS_THE_SAME),
|
||||
new(SELF_HOSTED, "starling-lm:7b", THE_DEFAULT_SAYS_THE_SAME),
|
||||
new(SELF_HOSTED, "phi3:14b", "The Phi rules were written for the fourth generation and the ones before it already reached the default, which answers them the same as it does today."),
|
||||
|
||||
//
|
||||
// Families which lose their thinking to the default. It is the ability a person misses
|
||||
// least: the model still answers, and the answer still carries the thinking -- it is only
|
||||
// not announced, so the thinking settings stay hidden.
|
||||
//
|
||||
new(SELF_HOSTED, "olmo-3-32b-think", THE_DEFAULT_KEEPS_WHAT_MATTERS),
|
||||
new(SELF_HOSTED, "seed-oss:36b", THE_DEFAULT_KEEPS_WHAT_MATTERS),
|
||||
new(SELF_HOSTED, "ring-1t", THE_DEFAULT_KEEPS_WHAT_MATTERS),
|
||||
new(SELF_HOSTED, "smollm3:3b", THE_DEFAULT_KEEPS_WHAT_MATTERS),
|
||||
new(HUGGINGFACE, "HuggingFaceTB/SmolLM3-3B", THE_DEFAULT_KEEPS_WHAT_MATTERS),
|
||||
new(SELF_HOSTED, "internlm3:8b", THE_DEFAULT_KEEPS_WHAT_MATTERS),
|
||||
|
||||
//
|
||||
// Families which read more than text. These are the ones the decision costs something:
|
||||
// until somebody says otherwise, the chat will not offer to send them a picture.
|
||||
//
|
||||
new(SELF_HOSTED, "internvl3-8b", THE_DEFAULT_DROPS_THE_MODALITIES),
|
||||
new(GWDG, "internvl2.5-8b", THE_DEFAULT_DROPS_THE_MODALITIES),
|
||||
new(SELF_HOSTED, "apertus-1.5-8b", "A family we decided not to write down, and the one which loses the most by it: it reads images and listens to audio, and the default knows about neither."),
|
||||
|
||||
//
|
||||
// Names which were never models.
|
||||
//
|
||||
new(OPEN_AI, "", NOT_A_MODEL_AT_ALL),
|
||||
new(SELF_HOSTED, " ", NOT_A_MODEL_AT_ALL),
|
||||
new(SELF_HOSTED, "---", NOT_A_MODEL_AT_ALL),
|
||||
new(NONE, "gpt-5.6", "A model without a provider. There is no way to reach it, so there is nothing to say about how it could be used."),
|
||||
new(LITE_LLM, "the-fast-one", "A freely chosen LiteLLM alias. Nothing in the name says what is behind it, which is what the default exists for."),
|
||||
new(SELF_HOSTED, "a-model-nobody-has-heard-of", "The corpus entry for the default itself. It has to reach it, or the default would never be measured."),
|
||||
|
||||
//
|
||||
// Still to do rather than decided.
|
||||
//
|
||||
new(LITE_LLM, "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0", "Not a decision: the LiteLLM host cannot take the Bedrock spelling apart yet, because the vendor sits behind a dot rather than a slash. ExpectedChanges holds the answer it has to arrive at."),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
using static AIStudio.Provider.LLMProviders;
|
||||
using static AIStudio.Tests.Models.Corpus.CorpusOrigin;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// The model IDs the capability rules are measured against.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This list is the ruler for rebuilding the capability system. Every entry is a name some provider
|
||||
/// really answers with, together with the provider it arrives from, because the same model gets a
|
||||
/// different answer depending on who serves it: an ID travels through the rules of its host before
|
||||
/// it reaches the rules of its family.
|
||||
///
|
||||
/// Two things make an entry worth having. Either it is the only name that reaches a particular
|
||||
/// rule, so removing it would let that rule rot unnoticed. Or it is a name no rule was written for,
|
||||
/// which is what the fallback exists for and what nobody looks at otherwise. Names that merely vary
|
||||
/// a size or a date are left out; they exercise the same rule twice and only make the snapshot
|
||||
/// longer.
|
||||
///
|
||||
/// Sizes, dates, and quantization suffixes appear where they change the answer, and only there.
|
||||
/// </remarks>
|
||||
public static class ModelCorpus
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenAI, reached directly. Its rules are the only ones that hand out the Responses API.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] OPEN_AI_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "gpt-6-astra", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-6-astra-mini", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5.6", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5.5", ON_THE_MANUAL_TEST_LIST),
|
||||
new(OPEN_AI, "gpt-5.4", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5.3", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5.2", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5.1", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5.1-codex", NAMED_BY_NO_RULE),
|
||||
new(OPEN_AI, "gpt-5", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5-mini", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5-nano", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-5-chat-latest", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-4o", NAMED_BY_NO_RULE),
|
||||
new(OPEN_AI, "gpt-4o-mini", NAMED_BY_NO_RULE),
|
||||
new(OPEN_AI, "gpt-4o-search-preview", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-4o-mini-search-preview", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-4o-audio-preview", NAMED_BY_NO_RULE),
|
||||
new(OPEN_AI, "gpt-4-turbo", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-4", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-4-0613", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-3.5-turbo", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "gpt-3.5-turbo-16k", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "o1", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "o1-pro", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "o1-mini", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "o3", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "o3-pro", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "o3-mini", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "o4-mini", NAMED_BY_A_RULE),
|
||||
new(OPEN_AI, "text-embedding-3-large", NAMED_BY_NO_RULE),
|
||||
new(OPEN_AI, "whisper-1", NAMED_BY_NO_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Anthropic, reached directly. The six dated aliases come from the list the app falls back to.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] ANTHROPIC_ENTRIES =
|
||||
[
|
||||
new(ANTHROPIC, "claude-mythos-5", NAMED_BY_A_RULE),
|
||||
new(ANTHROPIC, "claude-fable-5-1", NAMED_BY_A_RULE),
|
||||
new(ANTHROPIC, "claude-opus-5", NAMED_BY_A_RULE),
|
||||
new(ANTHROPIC, "claude-sonnet-5", ON_THE_MANUAL_TEST_LIST),
|
||||
new(ANTHROPIC, "claude-haiku-4-5-20251001", NAMED_BY_A_RULE),
|
||||
new(ANTHROPIC, "claude-opus-4-0", BUILT_INTO_THE_APP),
|
||||
new(ANTHROPIC, "claude-sonnet-4-0", BUILT_INTO_THE_APP),
|
||||
new(ANTHROPIC, "claude-3-7-sonnet-latest", BUILT_INTO_THE_APP),
|
||||
new(ANTHROPIC, "claude-3-5-sonnet-latest", BUILT_INTO_THE_APP),
|
||||
new(ANTHROPIC, "claude-3-5-haiku-latest", BUILT_INTO_THE_APP),
|
||||
new(ANTHROPIC, "claude-3-opus-latest", BUILT_INTO_THE_APP),
|
||||
new(ANTHROPIC, "claude-7-sonnet", NAMED_BY_NO_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Google, reached directly. Everything hangs on whether the name carries "gemini-" at all.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] GOOGLE_ENTRIES =
|
||||
[
|
||||
new(GOOGLE, "gemini-3-pro", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-3-flash", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-3-pro-image", ON_THE_MANUAL_TEST_LIST),
|
||||
new(GOOGLE, "gemini-3.1-flash-image", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-flash-latest", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-pro-latest", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-2.5-pro", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-2.5-flash", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-2.5-flash-lite", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-2.5-flash-image", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-2.0-flash", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-2.0-flash-live-001", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "gemini-1.0-pro-vision", NAMED_BY_A_RULE),
|
||||
new(GOOGLE, "text-embedding-004", NAMED_BY_NO_RULE),
|
||||
new(GOOGLE, "imagen-4.0-generate-001", NAMED_BY_NO_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Mistral, reached directly. The family is versioned by release date, so the dated names are
|
||||
/// what the rules really read; the marketing names are a table on the side.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] MISTRAL_ENTRIES =
|
||||
[
|
||||
new(MISTRAL, "mistral-large-latest", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-large-2512", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-large-2411", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-medium-latest", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-medium-2604", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-medium-2508", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-medium-2505", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-medium-3.5", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-medium-3-5", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-small-latest", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-small-2603", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-small-2503", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-small-2501", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "ministral-3b-latest", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "ministral-8b-2410", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "ministral-14b-2512", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(MISTRAL, "pixtral-large-latest", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "pixtral-12b-2409", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "mistral-saba-2502", NAMED_BY_A_RULE),
|
||||
new(MISTRAL, "magistral-medium-2506", NAMED_BY_NO_RULE),
|
||||
new(MISTRAL, "voxtral-small-2507", NAMED_BY_NO_RULE),
|
||||
new(MISTRAL, "codestral-2508", NAMED_BY_NO_RULE),
|
||||
new(MISTRAL, "open-mistral-nemo", NAMED_BY_NO_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Alibaba Cloud. Everything below the two dozen models the app carries is one Qwen tier per
|
||||
/// entry, because each tier answers differently about thinking and vision.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] ALIBABA_ENTRIES =
|
||||
[
|
||||
new(ALIBABA_CLOUD, "qwq-plus", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen-max-latest", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen-plus-latest", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen-turbo-latest", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qvq-max", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen-vl-max", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen-mt-plus", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen2.5-72b-instruct", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen2.5-14b-instruct-1m", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen2.5-omni-7b", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen2.5-vl-72b-instruct", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "text-embedding-v3", BUILT_INTO_THE_APP),
|
||||
new(ALIBABA_CLOUD, "qwen3-omni-flash", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3-vl-plus", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3-235b-a22b", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.5-plus", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.6-max", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.7-max", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.7-max-preview", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.7-max-2026-05-17", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.7-max-2026-06-08", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.8-flash", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.8-max", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwen3.8-27b", NAMED_BY_A_RULE),
|
||||
new(ALIBABA_CLOUD, "qwq-32b", ON_THE_MANUAL_TEST_LIST),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The DeepSeek platform. Two of its names are aliases of their own; everything else is the open
|
||||
/// weights under their published name, which is why the rules hand those on.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] DEEP_SEEK_ENTRIES =
|
||||
[
|
||||
new(DEEP_SEEK, "deepseek-chat", NAMED_BY_A_RULE),
|
||||
new(DEEP_SEEK, "deepseek-reasoner", NAMED_BY_A_RULE),
|
||||
new(DEEP_SEEK, "deepseek-v3.2-exp", NAMED_BY_A_RULE),
|
||||
new(DEEP_SEEK, "deepseek-v4", NAMED_BY_A_RULE),
|
||||
new(DEEP_SEEK, "deepseek-v4-vision", NAMED_BY_A_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Perplexity. One rule separates the thinking Sonar models from the rest.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] PERPLEXITY_ENTRIES =
|
||||
[
|
||||
new(PERPLEXITY, "sonar", NAMED_BY_NO_RULE),
|
||||
new(PERPLEXITY, "sonar-pro", NAMED_BY_NO_RULE),
|
||||
new(PERPLEXITY, "sonar-reasoning", NAMED_BY_A_RULE),
|
||||
new(PERPLEXITY, "sonar-reasoning-pro", NAMED_BY_A_RULE),
|
||||
new(PERPLEXITY, "sonar-deep-research", NAMED_BY_A_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// xAI. It is served by the rules for open weights, which is where the Grok block lives.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] XAI_ENTRIES =
|
||||
[
|
||||
new(X, "grok-4", NAMED_BY_A_RULE),
|
||||
new(X, "grok-4-fast-reasoning", NAMED_BY_A_RULE),
|
||||
new(X, "grok-4.20", NAMED_BY_A_RULE),
|
||||
new(X, "grok-4.20-non-reasoning", NAMED_BY_A_RULE),
|
||||
new(X, "grok-3", NAMED_BY_A_RULE),
|
||||
new(X, "grok-3-mini", NAMED_BY_A_RULE),
|
||||
new(X, "grok-2-vision-1212", NAMED_BY_A_RULE),
|
||||
new(X, "grok-5", NAMED_BY_NO_RULE),
|
||||
new(X, "grok-build-0.1", NAMED_BY_A_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The gateways, which name a model "vendor/model" and serve everything through the chat
|
||||
/// completion API. Each entry picks a different branch of the vendor detection.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] GATEWAY_ENTRIES =
|
||||
[
|
||||
new(OPEN_ROUTER, "openai/gpt-5.6", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(OPEN_ROUTER, "openai/gpt-oss-120b", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "anthropic/claude-opus-5", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(OPEN_ROUTER, "google/gemini-3.7-flash", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(OPEN_ROUTER, "google/gemma-4-31b-it", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "mistralai/mistral-large-3", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "perplexity/sonar-reasoning", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "qwen/qwen3.8-flash-next", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(OPEN_ROUTER, "deepseek/deepseek-r1-distill-llama-70b", ON_THE_MANUAL_TEST_LIST),
|
||||
new(OPEN_ROUTER, "deepseek/deepseek-chat-v3.1", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "moonshotai/kimi-k2-thinking", ON_THE_MANUAL_TEST_LIST),
|
||||
new(OPEN_ROUTER, "z-ai/glm-5.3", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "meta-llama/llama-4-maverick", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "nvidia/nemotron-3-49b", NAMED_BY_A_RULE),
|
||||
new(OPEN_ROUTER, "minimax/minimax-m2", NAMED_BY_A_RULE),
|
||||
new(LITE_LLM, "anthropic/claude-sonnet-5", QUOTED_AS_A_NAME_SHAPE),
|
||||
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),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Hugging Face. Two things have to come off before a name says anything: the routing suffix,
|
||||
/// which names the inference provider, and the organization in front of the slash.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] HUGGING_FACE_ENTRIES =
|
||||
[
|
||||
new(HUGGINGFACE, "google/gemma-4-31B-it:novita", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(HUGGINGFACE, "meta-llama/Llama-4-Scout-17B-16E-Instruct", NAMED_BY_A_RULE),
|
||||
new(HUGGINGFACE, "meta-llama/Meta-Llama-3.1-405B-Instruct", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(HUGGINGFACE, "Qwen/Qwen3.8-27B", NAMED_BY_A_RULE),
|
||||
new(HUGGINGFACE, "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", NAMED_BY_A_RULE),
|
||||
new(HUGGINGFACE, "openai/gpt-oss-120b:fireworks-ai", NAMED_BY_A_RULE),
|
||||
new(HUGGINGFACE, "mistralai/Magistral-Small-2509", NAMED_BY_A_RULE),
|
||||
new(HUGGINGFACE, "HuggingFaceTB/SmolLM3-3B", NAMED_BY_A_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Providers that serve other vendors' models under their plain names, without a prefix. GWDG
|
||||
/// is the case that brought this up: next to open weights it resells Claude and GPT models.
|
||||
/// Blablador answers with a whole sentence instead of an ID.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] RESELLER_ENTRIES =
|
||||
[
|
||||
new(GWDG, "claude-sonnet-5", ON_THE_MANUAL_TEST_LIST),
|
||||
new(GWDG, "gpt-5.5", ON_THE_MANUAL_TEST_LIST),
|
||||
new(GWDG, "meta-llama-3.1-8b-instruct", NAMED_BY_A_RULE),
|
||||
new(GWDG, "qwen3-235b-a22b", NAMED_BY_A_RULE),
|
||||
new(GWDG, "deepseek-r1", NAMED_BY_A_RULE),
|
||||
new(GWDG, "gemma-3-27b-it", NAMED_BY_A_RULE),
|
||||
new(GWDG, "internvl2.5-8b", NAMED_BY_A_RULE),
|
||||
new(GWDG, "e5-mistral-7b-instruct", NAMED_BY_NO_RULE),
|
||||
new(GWDG, "whisper-large-v2", BUILT_INTO_THE_APP),
|
||||
new(HELMHOLTZ, "1 - Llama3 405 the best general model", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(HELMHOLTZ, "01 - GPT-5.5 - great overall performance", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(HELMHOLTZ, "10 - Muse Glimmer 30b - the newest META model", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(HELMHOLTZ, "Qwen 3.8-27B with DFlash on haicluster", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(HELMHOLTZ, "alias-qwen38-27b", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(GROQ, "llama-3.3-70b-versatile", NAMED_BY_A_RULE),
|
||||
new(GROQ, "openai/gpt-oss-120b", NAMED_BY_A_RULE),
|
||||
new(GROQ, "moonshotai/kimi-k2-instruct", NAMED_BY_A_RULE),
|
||||
new(GROQ, "qwen/qwen3-32b", NAMED_BY_A_RULE),
|
||||
new(GROQ, "whisper-large-v3-turbo", NAMED_BY_NO_RULE),
|
||||
new(FIREWORKS, "accounts/fireworks/models/llama-v3p1-405b-instruct", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(FIREWORKS, "accounts/fireworks/models/deepseek-v3", NAMED_BY_A_RULE),
|
||||
new(FIREWORKS, "accounts/fireworks/models/qwen3-235b-a22b", NAMED_BY_A_RULE),
|
||||
new(FIREWORKS, "whisper-v3", BUILT_INTO_THE_APP),
|
||||
new(HETZNER, "gpt-oss-120b", NAMED_BY_A_RULE),
|
||||
new(HETZNER, "qwen3-coder-30b", NAMED_BY_A_RULE),
|
||||
new(IONOS, "meta-llama/Llama-3.3-70B-Instruct", NAMED_BY_A_RULE),
|
||||
new(IONOS, "mistralai/Mistral-Small-24B-Instruct", NAMED_BY_A_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Self-hosted engines. Ollama writes the variant behind a colon, which normalization turns
|
||||
/// into a hyphen, so a rolling tag such as "qwen3.8:latest" carries no size at all. This is the
|
||||
/// longest section on purpose: it is where the open-weight families arrive.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] SELF_HOSTED_ENTRIES =
|
||||
[
|
||||
new(SELF_HOSTED, "qwen3.8:latest", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(SELF_HOSTED, "qwen3.8-2.4t-a95b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "qwen3.8:27b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "qwen3.5:32b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "qwen3.6:32b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "qwen3-coder:30b", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "qwen2.5-vl-7b-instruct", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "qwen3.8:27b-mlx", ON_THE_MANUAL_TEST_LIST),
|
||||
new(SELF_HOSTED, "qwq:32b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "deepseek-r1:32b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "deepseek-r1-distill-llama-70b", ON_THE_MANUAL_TEST_LIST),
|
||||
new(SELF_HOSTED, "deepseek-v3.1:671b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "deepseek-v2.5", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "llama3.2:3b", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(SELF_HOSTED, "llama3.2-vision:11b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "llama2:13b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "llama-3.1-405b-base", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "muse-glimmer-30b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "gemma4:e2b", ON_THE_MANUAL_TEST_LIST),
|
||||
new(SELF_HOSTED, "gemma4:31b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "gemma3:1b", ON_THE_MANUAL_TEST_LIST),
|
||||
new(SELF_HOSTED, "gemma3:27b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "gemma3n:e4b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "gemma2:9b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "gpt-oss:20b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "mistral-small3.2:24b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "mistral-small-3.1-24b-instruct", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "mistral-nemo:12b", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "magistral:24b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "voxtral-mini-3b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "ministral-8b-instruct-2410", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "glm-5.3-flash-nvfp4", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(SELF_HOSTED, "glm-5-2", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(SELF_HOSTED, "glm-4.5v", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "glm-4-9b-chat", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "glm-4.6:latest", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "kimi-k3:latest", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "kimi-k2.7-code", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "kimi-vl:16b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "kimi-k2:1t", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "hunyuan:7b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "tencent/hy3", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(SELF_HOSTED, "nemotron-3-49b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "nvidia-nemotron-3.5-lightning-30b-a3b-nvfp4", QUOTED_AS_A_NAME_SHAPE),
|
||||
new(SELF_HOSTED, "llama-3.3-nemotron-super-49b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "granite4.2:8b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "granite3.3:8b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "granite3.2-vision:2b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "granite-embedding:278m", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "command-a:111b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "command-a-plus", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "command-a-vision", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "command-a-reasoning", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "command-r7b:7b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "aya-expanse:8b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "aya-vision:8b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "olmo3:7b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "olmo-3-32b-think", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "olmo2:13b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "seed-oss:36b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "falcon-h1:7b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "falcon-h1-1.5b-tool-calling", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "falcon3:10b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "ling-1t", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "ring-1t", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "inclusionai/ling-mini-2.0", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "starling-lm:7b", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "ernie-4.5-vl-28b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "ernie-x1.1-thinking", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "ernie-4.5-21b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "smollm3:3b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "smollm2:1.7b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "apriel-1.5-15b-thinker", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "apriel-1.6-15b-thinker", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "internvl3-8b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "internlm3:8b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "apertus-1.5-8b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "phi4-mini:latest", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "phi-4-multimodal-instruct", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "phi-4-mini-reasoning", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "phi-4-reasoning-vision", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "phi4:14b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "phi3:14b", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "minimax-m2:latest", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "minimax-text-01", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "teuken-7b-instruct", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "eurollm-9b-instruct", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "occiglot-7b-eu5", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "salamandra-7b-instruct", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "salamandra-7b-instruct-tools", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "yi-1.5:9b", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "01-ai/yi-large", NAMED_BY_A_RULE),
|
||||
new(SELF_HOSTED, "nomic-embed-text:latest", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "a-model-nobody-has-heard-of", NAMED_BY_NO_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Names that say nothing, and one provider which answers about nothing. They are here because
|
||||
/// a rebuild is exactly where a fresh crash on an empty string gets introduced.
|
||||
/// </summary>
|
||||
private static readonly CorpusEntry[] EDGE_CASE_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, " ", NAMED_BY_NO_RULE),
|
||||
new(SELF_HOSTED, "---", NAMED_BY_NO_RULE),
|
||||
new(NONE, "gpt-5.6", NAMED_BY_NO_RULE),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Every entry of the corpus, in the order the sections above are written.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This has to stand below the sections it reads: static fields are initialized top to bottom,
|
||||
/// and a field which is not initialized yet is null rather than an error.
|
||||
/// </remarks>
|
||||
public static readonly IReadOnlyList<CorpusEntry> ENTRIES =
|
||||
[
|
||||
..OPEN_AI_ENTRIES,
|
||||
..ANTHROPIC_ENTRIES,
|
||||
..GOOGLE_ENTRIES,
|
||||
..MISTRAL_ENTRIES,
|
||||
..ALIBABA_ENTRIES,
|
||||
..DEEP_SEEK_ENTRIES,
|
||||
..PERPLEXITY_ENTRIES,
|
||||
..XAI_ENTRIES,
|
||||
..GATEWAY_ENTRIES,
|
||||
..HUGGING_FACE_ENTRIES,
|
||||
..RESELLER_ENTRIES,
|
||||
..SELF_HOSTED_ENTRIES,
|
||||
..EDGE_CASE_ENTRIES,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using static AIStudio.Provider.LLMProviders;
|
||||
using static AIStudio.Provider.ModelKind;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// The names which say what a model is made for.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A list of its own, next to the corpus the capability rules are measured against. The two answer
|
||||
/// different questions and are made of different names: the capability corpus is full of chat models,
|
||||
/// because everything else has no capabilities worth stating, while every name here is one nobody
|
||||
/// should be able to start a conversation with.
|
||||
///
|
||||
/// Each entry says what the model is for. Where the markers being replaced answer something else,
|
||||
/// the entry says that too, with the reason -- so that the port can be held to changing nothing
|
||||
/// except where somebody decided it should.
|
||||
/// </remarks>
|
||||
public static class ModelKindCorpus
|
||||
{
|
||||
/// <summary>
|
||||
/// The models which turn text into a vector.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] EMBEDDING_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "text-embedding-3-small", EMBEDDING),
|
||||
new(SELF_HOSTED, "mxbai-embed-large:latest", EMBEDDING),
|
||||
new(SELF_HOSTED, "bge-m3:567m", EMBEDDING),
|
||||
new(SELF_HOSTED, "multilingual-e5-large", EMBEDDING),
|
||||
new(SELF_HOSTED, "gte-multilingual-base", EMBEDDING),
|
||||
new(SELF_HOSTED, "paraphrase-multilingual-mpnet-base-v2", EMBEDDING),
|
||||
new(SELF_HOSTED, "gritlm-7b", EMBEDDING),
|
||||
|
||||
// The one name whose only marker used to be the organization it was published under:
|
||||
new(SELF_HOSTED, "sentence-transformers/all-MiniLM-L6-v2", EMBEDDING),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which put search results back into order, each named after an embedding model.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] RERANKING_ENTRIES =
|
||||
[
|
||||
new(SELF_HOSTED, "bge-reranker-v2-m3", RERANKING),
|
||||
new(SELF_HOSTED, "gte-multilingual-reranker-base", RERANKING),
|
||||
new(SELF_HOSTED, "qwen3-reranker-8b", RERANKING),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which draw.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] IMAGE_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "gpt-image-1", IMAGE_GENERATION),
|
||||
new(OPEN_AI, "dall-e-3", IMAGE_GENERATION),
|
||||
new(SELF_HOSTED, "flux.1-schnell", IMAGE_GENERATION),
|
||||
new(SELF_HOSTED, "stable-diffusion-3.5-large", IMAGE_GENERATION),
|
||||
new(GOOGLE, "gemini-3-pro-image", IMAGE_GENERATION),
|
||||
|
||||
new(GOOGLE, "imagen-4.0-generate-001", IMAGE_GENERATION, AnsweredTodayAs: CHAT, Reason: "The markers never knew the name; the family ported in the Google step states it. Nobody noticed because the Google provider shows only names beginning with gemini."),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which make video.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] VIDEO_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "sora-2", VIDEO_GENERATION),
|
||||
new(GOOGLE, "veo-3.0-generate-001", VIDEO_GENERATION),
|
||||
new(SELF_HOSTED, "kling-video-v2", VIDEO_GENERATION),
|
||||
|
||||
new(X, "grok-imagine-video", VIDEO_GENERATION, AnsweredTodayAs: CHAT, Reason: "Found in the chat list while testing. No marker knew the name, and the xAI provider only kept models whose name lacks \"-image\" -- which \"-imagine\" does."),
|
||||
new(X, "grok-imagine-video-1.5", VIDEO_GENERATION, AnsweredTodayAs: CHAT, Reason: "The same, one version on."),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which listen and write down what they heard.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] TRANSCRIPTION_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "gpt-4o-transcribe", TRANSCRIPTION),
|
||||
new(SELF_HOSTED, "faster-whisper-large-v3", TRANSCRIPTION),
|
||||
new(SELF_HOSTED, "parakeet-tdt-0.6b-v2", TRANSCRIPTION),
|
||||
new(SELF_HOSTED, "wav2vec2-large-xlsr-53", TRANSCRIPTION),
|
||||
new(MISTRAL, "voxtral-mini-latest", TRANSCRIPTION),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which speak, and the ones which answer in audio.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] SPEECH_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "tts-1-hd", SPEECH_SYNTHESIS),
|
||||
new(OPEN_AI, "gpt-4o-mini-tts", SPEECH_SYNTHESIS),
|
||||
new(OPEN_AI, "gpt-audio", SPEECH_SYNTHESIS),
|
||||
new(OPEN_AI, "gpt-4o-audio-preview", SPEECH_SYNTHESIS),
|
||||
|
||||
// The one name which glues the word to something else, and the reason the three words are
|
||||
// not loosened into substrings:
|
||||
new(SELF_HOSTED, "xtts-v2", SPEECH_SYNTHESIS),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which want a connection of their own.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] REALTIME_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "gpt-realtime", REALTIME),
|
||||
new(OPEN_AI, "gpt-4o-realtime-preview", REALTIME),
|
||||
|
||||
// The name the marker file names as the reason for asking this question before the others:
|
||||
new(OPEN_AI, "gpt-realtime-whisper", REALTIME),
|
||||
|
||||
new(OPEN_AI, "gpt-live-1", REALTIME, AnsweredTodayAs: CHAT, Reason: "Found in the chat list while testing. The line which succeeds the realtime models dropped the word, and it is even less of a chat partner: it listens and speaks at once and leaves the thinking to a text model behind it."),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which work a screen.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] COMPUTER_USE_ENTRIES =
|
||||
[
|
||||
new(GOOGLE, "gemini-2.5-computer-use-preview-10-2025", COMPUTER_USE, AnsweredTodayAs: CHAT, Reason: "Found in the chat list while testing. Its API refuses every request which does not carry the computer use tool, so a conversation with it cannot even begin."),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models from before chat completions existed.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] TEXT_COMPLETION_ENTRIES =
|
||||
[
|
||||
new(HELMHOLTZ, "text-davinci-003", TEXT_COMPLETION),
|
||||
new(OPEN_AI, "babbage-002", TEXT_COMPLETION),
|
||||
new(OPEN_AI, "gpt-3.5-turbo-instruct", TEXT_COMPLETION),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which read text off a page.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] OCR_ENTRIES =
|
||||
[
|
||||
new(MISTRAL, "mistral-ocr-latest", OCR),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The models which judge content instead of writing it.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] MODERATION_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "omni-moderation-latest", MODERATION),
|
||||
new(SELF_HOSTED, "llama-guard-3-8b", MODERATION),
|
||||
|
||||
// Written without a separator, which is why the word is looked for as a plain substring:
|
||||
new(SELF_HOSTED, "Qwen3Guard-Gen-8B", MODERATION),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The entries which are no models at all.
|
||||
/// </summary>
|
||||
private static readonly ModelKindExample[] NOT_A_MODEL_ENTRIES =
|
||||
[
|
||||
new(OPEN_AI, "container", OTHER),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The names which carry a word of one of the kinds above without being one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These are the reason several of the words are looked for as whole name parts. A model sorted
|
||||
/// into the wrong kind disappears from the user's list, and a fine-tune losing its place because
|
||||
/// somebody named it after Star Trek is exactly the kind of defect nobody goes looking for.
|
||||
/// </remarks>
|
||||
private static readonly ModelKindExample[] STILL_CHAT_MODELS =
|
||||
[
|
||||
new(SELF_HOSTED, "llama-2-7b-chat-klingon", CHAT),
|
||||
new(SELF_HOSTED, "llama3.3:70b", CHAT),
|
||||
new(OPEN_AI, "gpt-5.1", CHAT),
|
||||
|
||||
//
|
||||
// Three which were questioned while testing and stay all the same. Grok Build is the coding
|
||||
// model behind the xAI CLI and answers like any other Grok. The Groq compound systems are
|
||||
// models with tools already built in, reached through the ordinary chat completion API. And
|
||||
// Gemini Robotics ER answers in text; it is built for pointing at things in a picture rather
|
||||
// than for conversation, but a conversation with it works, and a model which works belongs
|
||||
// in the list.
|
||||
//
|
||||
new(X, "grok-build-0.1", CHAT),
|
||||
new(GROQ, "groq/compound", CHAT),
|
||||
new(GROQ, "groq/compound-mini", CHAT),
|
||||
new(GOOGLE, "gemini-robotics-er-1.5-preview", CHAT),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Every example, in the order the kinds are written above.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<ModelKindExample> ENTRIES =
|
||||
[
|
||||
..EMBEDDING_ENTRIES,
|
||||
..RERANKING_ENTRIES,
|
||||
..IMAGE_ENTRIES,
|
||||
..VIDEO_ENTRIES,
|
||||
..TRANSCRIPTION_ENTRIES,
|
||||
..SPEECH_ENTRIES,
|
||||
..REALTIME_ENTRIES,
|
||||
..COMPUTER_USE_ENTRIES,
|
||||
..TEXT_COMPLETION_ENTRIES,
|
||||
..OCR_ENTRIES,
|
||||
..MODERATION_ENTRIES,
|
||||
..NOT_A_MODEL_ENTRIES,
|
||||
..STILL_CHAT_MODELS,
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using AIStudio.Provider;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// One model name together with what the app has to make of it.
|
||||
/// </summary>
|
||||
/// <param name="Provider">The provider the model is reached through.</param>
|
||||
/// <param name="ModelId">The model ID exactly as that provider reports it, before any normalization.</param>
|
||||
/// <param name="Kind">What the model is made for.</param>
|
||||
/// <param name="AnsweredTodayAs">What the markers that used to answer this question said, where they said something else. History now: the code that said it is gone, so nothing checks this any more. It stays because a decision without the thing it decided against reads like an arbitrary statement.</param>
|
||||
/// <param name="Reason">Why the two differ, which is only filled in when they do.</param>
|
||||
public sealed record ModelKindExample(LLMProviders Provider, string ModelId, ModelKind Kind, ModelKind? AnsweredTodayAs = null, string Reason = "");
|
||||
@@ -0,0 +1,11 @@
|
||||
using AIStudio.Provider;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// One model of the corpus which no rule answers for, together with why that is all right.
|
||||
/// </summary>
|
||||
/// <param name="Provider">The provider the model is reached through.</param>
|
||||
/// <param name="ModelId">The model ID exactly as that provider reports it.</param>
|
||||
/// <param name="Reason">Why this model is left to the global default.</param>
|
||||
public sealed record ModelLeftToTheDefault(LLMProviders Provider, string ModelId, string Reason);
|
||||
@@ -0,0 +1,60 @@
|
||||
using AIStudio.Models;
|
||||
using AIStudio.Models.Registry;
|
||||
using AIStudio.Provider;
|
||||
|
||||
namespace AIStudio.Tests.Models.Corpus;
|
||||
|
||||
/// <summary>
|
||||
/// Asks the rebuilt rules about a corpus entry, in the words the old ones answered in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The two systems say the same things in different shapes: the old one hands out a list of
|
||||
/// capabilities, the new one a profile whose reasoning is a field of its own rather than one of
|
||||
/// three flags. Comparing them at all needs one of the two translated, and translating the new one
|
||||
/// into the old vocabulary is the direction which loses nothing -- the profile knows more, and
|
||||
/// everything the old answer could say has a place in it.
|
||||
/// </remarks>
|
||||
public static class RebuiltRules
|
||||
{
|
||||
/// <summary>
|
||||
/// Asks the rebuilt rules about one corpus entry.
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry to ask about.</param>
|
||||
/// <returns>The capabilities, in the vocabulary the old rules answered in.</returns>
|
||||
public static IReadOnlyList<Capability> Ask(CorpusEntry entry) => AsCapabilities(ModelRegistry.Shared.Profile(entry.Provider, entry.ModelId));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a profile as the list of capabilities the old rules would have answered with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The reasoning field turns back into the flag which stands for it. That mapping is the whole
|
||||
/// reason the flags stay in the vocabulary: a person writing an override still says
|
||||
/// ALWAYS_REASONING, and the expert dialog still shows those five choices.
|
||||
/// </remarks>
|
||||
/// <param name="profile">The profile to write out.</param>
|
||||
/// <returns>The capabilities.</returns>
|
||||
public static IReadOnlyList<Capability> AsCapabilities(in ModelProfile profile)
|
||||
{
|
||||
// A profile handed in by reference cannot be reached from inside a query, and copying one
|
||||
// costs nothing:
|
||||
var answered = profile;
|
||||
var stated = Enum.GetValues<Capability>()
|
||||
.Where(capability => capability is not Capability.NONE && answered.Has(capability))
|
||||
.ToList();
|
||||
|
||||
var reasoning = ReasoningAsCapability(profile.Reasoning);
|
||||
if (reasoning is not Capability.NONE)
|
||||
stated.Add(reasoning);
|
||||
|
||||
return stated;
|
||||
}
|
||||
|
||||
private static Capability ReasoningAsCapability(ReasoningSupport reasoning) => reasoning switch
|
||||
{
|
||||
ReasoningSupport.OPTIONAL => Capability.OPTIONAL_REASONING,
|
||||
ReasoningSupport.ON_BY_DEFAULT => Capability.REASONING_BY_DEFAULT,
|
||||
ReasoningSupport.ALWAYS => Capability.ALWAYS_REASONING,
|
||||
|
||||
_ => Capability.NONE,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user