mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-14 22:43:38 +00:00
Some checks are pending
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
193 lines
7.6 KiB
C#
193 lines
7.6 KiB
C#
using AIStudio.Models;
|
|
using AIStudio.Models.Hosting;
|
|
using AIStudio.Models.Matching;
|
|
using AIStudio.Models.Registry;
|
|
using AIStudio.Provider;
|
|
|
|
namespace AIStudio.Tests.Models.Hosting;
|
|
|
|
/// <summary>
|
|
/// Checks the hosts the app actually ships, against the names the providers actually answer with.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The names in here are the ones from the corpus, which came out of the provider lists and the
|
|
/// audit rather than out of somebody's head. What is being asked is the routing question only --
|
|
/// what is left of a name once the way it arrived has been accounted for, and which APIs survive
|
|
/// the trip. Which model it then is remains a question for the rules.
|
|
/// </remarks>
|
|
[TestFixture]
|
|
public sealed class ModelHostTests
|
|
{
|
|
/// <summary>
|
|
/// The hosts as the app has them, found by the generator rather than listed here.
|
|
/// </summary>
|
|
private static readonly ModelHostIndex INDEX = ModelHostIndex.Build(ModelRegistrations.CreateHosts());
|
|
|
|
[Test]
|
|
public void EveryProviderAPersonCanConfigureHasAHost()
|
|
{
|
|
//
|
|
// This is the one which fails when somebody adds a provider to the app and stops there. It
|
|
// is not a runtime error -- names would simply be taken as they arrive -- so nothing else
|
|
// would ever point it out.
|
|
//
|
|
Assert.That(INDEX.ProvidersWithoutAHost, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void EveryHostSaysWhereItsBehaviourCanBeCheckedAndWhen()
|
|
{
|
|
var unstated = INDEX.Hosts.Where(host => !host.Source.IsStated).Select(host => host.GetType().Name);
|
|
|
|
Assert.That(unstated, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void AGatewayNameFallsApartIntoTheModelAndWhoBuiltIt()
|
|
{
|
|
var unwrapped = Unwrap(LLMProviders.OPEN_ROUTER, "anthropic/claude-opus-5", out var vendor);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5"));
|
|
Assert.That(vendor, Is.EqualTo(ModelVendor.ANTHROPIC));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void TheHuggingFaceRouterTakesOffTheRouteFirstAndTheOrganizationSecond()
|
|
{
|
|
var unwrapped = Unwrap(LLMProviders.HUGGINGFACE, "openai/gpt-oss-120b:fireworks-ai", out var vendor);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(unwrapped.Original, Is.EqualTo("gpt-oss-120b"));
|
|
Assert.That(vendor, Is.EqualTo(ModelVendor.OPEN_AI), "OpenAI published the weights, whoever is serving them today.");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void AHuggingFaceNameWithoutARouteIsStillTakenApart()
|
|
{
|
|
var unwrapped = Unwrap(LLMProviders.HUGGINGFACE, "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", out var vendor);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(unwrapped.Original, Is.EqualTo("DeepSeek-R1-Distill-Qwen-32B"));
|
|
Assert.That(vendor, Is.EqualTo(ModelVendor.DEEP_SEEK));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void TheFireworksAccountPathComesOffWholeWithoutAnybodyCountingItsSegments()
|
|
{
|
|
var unwrapped = Unwrap(LLMProviders.FIREWORKS, "accounts/fireworks/models/llama-v3p1-405b-instruct", out var vendor);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(unwrapped.Original, Is.EqualTo("llama-v3p1-405b-instruct"));
|
|
Assert.That(vendor, Is.Null, "None of the three path segments names a vendor.");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void BlabladorLosesItsPlaceInTheMenu()
|
|
{
|
|
var unwrapped = Unwrap(LLMProviders.HELMHOLTZ, "1 - Llama3 405 the best general model", out _);
|
|
|
|
Assert.That(unwrapped.Original, Is.EqualTo("Llama3 405 the best general model"));
|
|
}
|
|
|
|
[Test]
|
|
public void AnEngineServingAHubRepositoryHasItReadAsOne()
|
|
{
|
|
var unwrapped = Unwrap(LLMProviders.SELF_HOSTED, "meta-llama/Llama-3.3-70B-Instruct", out var vendor);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(unwrapped.Original, Is.EqualTo("Llama-3.3-70B-Instruct"));
|
|
Assert.That(vendor, Is.EqualTo(ModelVendor.META));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void TheVariantOllamaWritesAfterAColonSurvives()
|
|
{
|
|
//
|
|
// The colon means two different things at two different hosts. On the router it says where
|
|
// the request goes; on Ollama it says which build is running, and taking it off would leave
|
|
// a name which no longer identifies the model.
|
|
//
|
|
var unwrapped = Unwrap(LLMProviders.SELF_HOSTED, "qwen3.8:27b-mlx", out _);
|
|
|
|
Assert.That(unwrapped.Original, Is.EqualTo("qwen3.8:27b-mlx"));
|
|
}
|
|
|
|
[Test]
|
|
public void AResellerLeavesTheNameAloneAndOnlyTakesTheApiAway()
|
|
{
|
|
//
|
|
// This is the GWDG case: it offers Claude and GPT under the names their vendors use, so the
|
|
// rules recognize them and answer with everything those models can do. Everything except
|
|
// the API -- the request goes to Göttingen, and the Responses API is not served there.
|
|
//
|
|
var atItsVendor = new ModelProfile { Capabilities = Capability.TEXT_INPUT | Capability.FUNCTION_CALLING | Capability.RESPONSES_API };
|
|
var throughTheReseller = Transport(LLMProviders.GWDG, atItsVendor);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(Unwrap(LLMProviders.GWDG, "claude-sonnet-5", out _).Original, Is.EqualTo("claude-sonnet-5"));
|
|
Assert.That(throughTheReseller.Has(Capability.FUNCTION_CALLING), Is.True);
|
|
Assert.That(throughTheReseller.Has(Capability.RESPONSES_API), Is.False);
|
|
Assert.That(throughTheReseller.Has(Capability.CHAT_COMPLETION_API), Is.True);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void OnlyOpenAIsOwnCloudKeepsTheResponsesApi()
|
|
{
|
|
var withBothApis = new ModelProfile { Capabilities = Capability.RESPONSES_API | Capability.CHAT_COMPLETION_API };
|
|
var elsewhere = INDEX.Hosts
|
|
.Where(host => host.Provider is not LLMProviders.OPEN_AI)
|
|
.Where(host => host.ApplyTransport(withBothApis).Has(Capability.RESPONSES_API))
|
|
.Select(host => host.GetType().Name);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(Transport(LLMProviders.OPEN_AI, withBothApis).Has(Capability.RESPONSES_API), Is.True);
|
|
Assert.That(elsewhere, Is.Empty, "The app sends a Responses API request from exactly one place.");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void AModelReachedThroughNeitherApiIsNotGivenOne()
|
|
{
|
|
//
|
|
// An embedding model is reached through neither of the two. Answering that it speaks the
|
|
// chat completion API would be a claim nobody made.
|
|
//
|
|
var embedding = new ModelProfile { Capabilities = Capability.EMBEDDING };
|
|
var throughAGateway = Transport(LLMProviders.OPEN_ROUTER, embedding);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(throughAGateway.Has(Capability.EMBEDDING), Is.True);
|
|
Assert.That(throughAGateway.HasAny(Capability.CHAT_COMPLETION_API | Capability.RESPONSES_API), Is.False);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void ANameWithoutAWrappingComesBackAsItWas()
|
|
{
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(Unwrap(LLMProviders.OPEN_AI, "gpt-5.6", out _).Original, Is.EqualTo("gpt-5.6"));
|
|
Assert.That(Unwrap(LLMProviders.GROQ, "llama-3.3-70b-versatile", out _).Original, Is.EqualTo("llama-3.3-70b-versatile"));
|
|
Assert.That(Unwrap(LLMProviders.LITE_LLM, "the-fast-one", out _).Original, Is.EqualTo("the-fast-one"));
|
|
});
|
|
}
|
|
|
|
private static ModelId Unwrap(LLMProviders provider, string modelId, out ModelVendor? declaredVendor) => INDEX.Unwrap(new ModelId(modelId), provider, out declaredVendor);
|
|
|
|
private static ModelProfile Transport(LLMProviders provider, in ModelProfile profile) => INDEX.ApplyTransport(profile, provider);
|
|
} |