Read the context window from a provider's model list

This commit is contained in:
Thorsten Sommer committed 2026-09-13 10:28:10 +02:00
1 parent d8a30c4e60
commit 1c03a46e2f
8 files changed
+467 -15

No files matched your search

+154
View File
@@ -0,0 +1,154 @@
using AIStudio.Models;
using AIStudio.Models.Live;
using AIStudio.Provider;
namespace AIStudio.Tests.Models.Live;
/// <summary>
/// Checks what a running installation is allowed to say about the models it serves.
/// </summary>
/// <remarks>
/// Every test here builds its own store rather than using the shared one. What a provider reported
/// is state which outlives a single question, and a test leaving some of it behind would decide
/// what the next test sees.
/// </remarks>
[TestFixture]
public sealed class ListedModelsTests
{
private const string ONE_MACHINE = "11111111-1111-1111-1111-111111111111";
private const string ANOTHER_MACHINE = "22222222-2222-2222-2222-222222222222";
private const string MODEL = "qwen3-32b";
/// <summary>
/// A model the rules have something to say about, so that a report has something to contradict.
/// </summary>
private static readonly ModelProfile WHAT_THE_RULES_SAY = new()
{
Capabilities = Capability.TEXT_INPUT | Capability.TEXT_OUTPUT | Capability.MULTIPLE_IMAGE_INPUT,
Kind = ModelKind.CHAT,
Context = ContextWindow.Of(131_072, 262_144),
Images = new(null, 20),
};
[Test]
public void AMachineWhichWasNeverAskedSaysNothing()
{
var listed = new ListedModels();
Assert.Multiple(() =>
{
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING));
Assert.That(listed.Of(ONE_MACHINE, MODEL).IsKnown, Is.False);
});
}
[Test]
public void WhatOneMachineSaysIsNotWhatAnotherSays()
{
//
// The same weights behind two engines, each started by somebody who decided for themselves.
// This is the whole reason these numbers are kept per configured instance.
//
var listed = new ListedModels();
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(32_768))]);
listed.Report(ANOTHER_MACHINE, [new(MODEL, ContextWindow.Of(8_192))]);
Assert.Multiple(() =>
{
Assert.That(listed.Of(ONE_MACHINE, MODEL).Context.DefaultTokens, Is.EqualTo(32_768));
Assert.That(listed.Of(ANOTHER_MACHINE, MODEL).Context.DefaultTokens, Is.EqualTo(8_192));
});
}
[Test]
public void WhatAMachineNoLongerServesStopsAnswering()
{
var listed = new ListedModels();
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(32_768)), new("gemma3-27b", ContextWindow.Of(16_384))]);
listed.Report(ONE_MACHINE, [new("gemma3-27b", ContextWindow.Of(16_384))]);
Assert.Multiple(() =>
{
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING), "The engine was restarted without it, so nothing is known about it any more.");
Assert.That(listed.Of(ONE_MACHINE, "gemma3-27b").Context.DefaultTokens, Is.EqualTo(16_384));
});
}
[Test]
public void AMachineWhichHalvedItsWindowIsBelievedTheSecondTimeToo()
{
var listed = new ListedModels();
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(32_768))]);
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(16_384))]);
Assert.That(listed.Of(ONE_MACHINE, MODEL).Context.DefaultTokens, Is.EqualTo(16_384));
}
[TestCase("Qwen3-32B")]
[TestCase("qwen3-32b")]
public void AModelSomebodyTypedIsStillTheSameModel(string asConfigured)
{
//
// An organization writes the model of a provider into its configuration plugin by hand,
// and the availability check already treats such a name as the same model whatever case it
// was typed in. Being stricter here would leave exactly those people without the numbers.
//
var listed = new ListedModels();
listed.Report(ONE_MACHINE, [new("qwen3-32b", ContextWindow.Of(32_768))]);
Assert.That(listed.Of(ONE_MACHINE, asConfigured).Context.DefaultTokens, Is.EqualTo(32_768));
}
[Test]
public void AnInstanceWithoutAnIdIsNothingToRemember()
{
var listed = new ListedModels();
listed.Report(string.Empty, [new(MODEL, ContextWindow.Of(32_768))]);
Assert.Multiple(() =>
{
Assert.That(listed.Of(string.Empty, MODEL), Is.EqualTo(ModelListing.NOTHING));
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING), "One nameless report does not become every machine's answer.");
});
}
[Test]
public void AModelTheMachineSaidNothingAboutIsNotStored()
{
var listed = new ListedModels();
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.UNKNOWN), new(string.Empty, ContextWindow.Of(32_768))]);
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING));
}
[Test]
public void AReportedWindowReplacesTheWholeWindow()
{
var after = new ModelListing(MODEL, ContextWindow.Of(32_768)).ApplyTo(WHAT_THE_RULES_SAY);
Assert.Multiple(() =>
{
Assert.That(after.Context.DefaultTokens, Is.EqualTo(32_768));
Assert.That(after.Context.RaisableToTokens, Is.Null, "What the weights could be raised to is not a number anybody reaches without restarting this engine.");
});
}
[Test]
public void AWindowSaysNothingAboutAnythingElse()
{
var after = new ModelListing(MODEL, ContextWindow.Of(32_768)).ApplyTo(WHAT_THE_RULES_SAY);
Assert.Multiple(() =>
{
Assert.That(after.Capabilities, Is.EqualTo(WHAT_THE_RULES_SAY.Capabilities));
Assert.That(after.Images, Is.EqualTo(WHAT_THE_RULES_SAY.Images));
Assert.That(after.Kind, Is.EqualTo(WHAT_THE_RULES_SAY.Kind));
});
}
[Test]
public void SayingNothingKeepsEverythingTheRulesWorkedOut()
{
Assert.That(ModelListing.NOTHING.ApplyTo(WHAT_THE_RULES_SAY), Is.EqualTo(WHAT_THE_RULES_SAY));
}
}
+89 -4
View File
@@ -1,4 +1,5 @@
using AIStudio.Models;
using AIStudio.Models.Live;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tests.Models.Corpus;
@@ -10,14 +11,31 @@ namespace AIStudio.Tests.Settings;
/// </summary>
/// <remarks>
/// Behind it stand the links of the chain in the order they win: what a person said about their own
/// installation, then what the rules worked out, then what the app assumes. The rules themselves are
/// measured elsewhere, against the whole corpus. What is measured here is the last link -- the one
/// nothing held to account until now, because a model falling through looked exactly like a model
/// nobody had asked about.
/// installation, then what that installation reported about itself, then what the rules worked out,
/// then what the app assumes. The rules themselves are measured elsewhere, against the whole corpus.
/// What is measured here is everything around them -- the last link, which nothing held to account
/// until now because a model falling through looked exactly like a model nobody had asked about,
/// and the order of the three links above it, each of which can speak about the same number.
///
/// What a provider reported lands in the store the app shares, so these tests must not run next to
/// anything else touching it.
/// </remarks>
[TestFixture]
[NonParallelizable]
public sealed class ModelProfileChainTests
{
private const string MACHINE = "33333333-3333-3333-3333-333333333333";
/// <summary>
/// A window no rule would ever state, so that finding it proves where the answer came from.
/// </summary>
private const int WHAT_THE_MACHINE_REPORTS = 33_333;
private static readonly Model MODEL = new("qwen3-32b", null);
[SetUp]
public void ForgetWhatTheMachineSaidBefore() => ListedModels.Shared.Report(MACHINE, []);
[Test]
public void EveryModelLeftToTheDefaultIsAnsweredByTheAssumption()
{
@@ -100,4 +118,71 @@ public sealed class ModelProfileChainTests
Assert.That(profile.Has(Capability.TEXT_INPUT), Is.True, "Everything nobody said anything about stays as the rules had it.");
});
}
[Test]
public void WhatThePersonTypedBeatsWhatTheMachineReported()
{
//
// Somebody who types a window has a reason for it, and the app is not in a position to know
// it better -- they may be working around an engine reporting nonsense.
//
ListedModels.Shared.Report(MACHINE, [new(MODEL.Id, ContextWindow.Of(WHAT_THE_MACHINE_REPORTS))]);
var configured = ProviderWith(new() { ContextWindowTokens = 8_192 });
Assert.That(configured.GetModelProfile().Context.DefaultTokens, Is.EqualTo(8_192));
}
[Test]
public void WhatTheMachineReportedBeatsWhatTheRulesWorkedOut()
{
ListedModels.Shared.Report(MACHINE, [new(MODEL.Id, ContextWindow.Of(WHAT_THE_MACHINE_REPORTS))]);
var configured = ProviderWith(null);
Assert.That(configured.GetModelProfile().Context.DefaultTokens, Is.EqualTo(WHAT_THE_MACHINE_REPORTS));
}
[Test]
public void ASilentMachineLeavesTheRulesStanding()
{
var configured = ProviderWith(null);
Assert.That(configured.GetModelProfile().Context, Is.EqualTo(LLMProviders.SELF_HOSTED.GetModelProfile(MODEL).Context));
}
[Test]
public void TheAutomaticAnswerIsWhatHappensWithoutTheSwitches()
{
//
// This is the number the expert dialog offers as its placeholder. Showing the rules there
// while the chat goes by the reported window would tell a person that emptying the field
// gets them something it does not.
//
ListedModels.Shared.Report(MACHINE, [new(MODEL.Id, ContextWindow.Of(WHAT_THE_MACHINE_REPORTS))]);
var configured = ProviderWith(new() { ContextWindowTokens = 8_192 });
Assert.Multiple(() =>
{
Assert.That(configured.GetAutomaticModelProfile().Context.DefaultTokens, Is.EqualTo(WHAT_THE_MACHINE_REPORTS));
Assert.That(configured.GetModelProfile().Context.DefaultTokens, Is.EqualTo(8_192), "What the person typed is still what counts everywhere else.");
});
}
[Test]
public void WhatOneMachineReportsIsNoAnswerForAnother()
{
ListedModels.Shared.Report(MACHINE, [new(MODEL.Id, ContextWindow.Of(WHAT_THE_MACHINE_REPORTS))]);
var somebodyElse = ProviderWith(null) with { Id = "44444444-4444-4444-4444-444444444444" };
Assert.That(somebodyElse.GetModelProfile().Context, Is.EqualTo(LLMProviders.SELF_HOSTED.GetModelProfile(MODEL).Context));
}
/// <summary>
/// A configured self-hosted provider, the way the settings hold one.
/// </summary>
/// <param name="overrides">What the person switched, or nothing when they switched nothing.</param>
/// <returns>The configured provider.</returns>
private static AIStudio.Settings.Provider ProviderWith(ProviderCapabilityOverrides? overrides) => new(1, MACHINE, "A machine of my own", LLMProviders.SELF_HOSTED, MODEL, IsSelfHosted: true)
{
CapabilityOverrides = overrides,
};
}