mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-06 14:49:40 +00:00
Check image counts against the limits vendors state
This commit is contained in:
10 files changed
+561
-283
No files matched your search
@@ -1,3 +1,5 @@
|
||||
using AIStudio.Models.Matching;
|
||||
|
||||
using static AIStudio.Provider.Capability;
|
||||
|
||||
namespace AIStudio.Models.Anthropic;
|
||||
@@ -14,15 +16,62 @@ namespace AIStudio.Models.Anthropic;
|
||||
/// Claude nobody has written a rule for yet is still a Claude, and every one of them so far reads
|
||||
/// images and calls tools. It answers for whole name parts, so every rule bound to the start of a
|
||||
/// name beats it, whatever their lengths -- which is what lets it sit first and mean "unless".
|
||||
///
|
||||
/// The one thing no rule below states is how many images a Claude takes. Anthropic ties that number
|
||||
/// to the context window instead of to the model, so it is worked out afterwards rather than written
|
||||
/// on every line which sets a window.
|
||||
/// </remarks>
|
||||
public sealed class ClaudeFamily : ModelFamily
|
||||
{
|
||||
/// <summary>
|
||||
/// The window every Claude has unless its own rule states the larger one.
|
||||
/// </summary>
|
||||
private const int STANDARD_WINDOW = 200_000;
|
||||
|
||||
/// <summary>
|
||||
/// The window of the Claude models which read a million tokens.
|
||||
/// </summary>
|
||||
private const int LARGE_WINDOW = 1_000_000;
|
||||
|
||||
/// <summary>
|
||||
/// How many images one request may carry when the model has the standard window.
|
||||
/// </summary>
|
||||
private const int IMAGES_PER_REQUEST_STANDARD_WINDOW = 100;
|
||||
|
||||
/// <summary>
|
||||
/// How many images one request may carry for every other Claude.
|
||||
/// </summary>
|
||||
private const int IMAGES_PER_REQUEST_OTHERWISE = 600;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ModelVendor Vendor => ModelVendor.ANTHROPIC;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ModelSource Source => new("https://platform.claude.com/docs/en/build-with-claude/context-windows", new DateOnly(2026, 9, 12), "Capabilities ported unchanged from ProviderExtensions.Anthropic.cs: one shape for all of Claude, and one sentence per generation about how it thinks. The context window page names the models with a 1M window and says every other Claude has 200k.");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<ModelSource> FurtherSources =>
|
||||
[
|
||||
new("https://platform.claude.com/docs/en/build-with-claude/vision", new DateOnly(2026, 9, 12), "The vision page gives the image limit as a rule rather than as a number per model: 100 images per request on the API for models with a 200k-token context window, 600 per request for all other models. The 20 it also names belongs to claude.ai, not to the API.")
|
||||
];
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Anthropic states no image limit per model. They state a rule which reads off the context
|
||||
/// window, and this is that rule -- written once rather than repeated as a number on every line
|
||||
/// which sets a window. Two statements of one fact drift apart, and the way they drift here is
|
||||
/// silent: the next Claude with the larger window would quietly keep the smaller limit because
|
||||
/// somebody wrote one number and not the other.
|
||||
/// </remarks>
|
||||
public override ModelProfile Refine(in ModelId id, in ModelProfile selected)
|
||||
{
|
||||
if (!selected.Context.IsKnown)
|
||||
return selected;
|
||||
|
||||
var perRequest = selected.Context.DefaultTokens is STANDARD_WINDOW ? IMAGES_PER_REQUEST_STANDARD_WINDOW : IMAGES_PER_REQUEST_OTHERWISE;
|
||||
return selected with { Images = new ImageLimits(null, perRequest) };
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Declare(ModelFamilyBuilder builder)
|
||||
{
|
||||
@@ -35,7 +84,7 @@ public sealed class ClaudeFamily : ModelFamily
|
||||
builder.Rule("claude").AsSegment()
|
||||
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
|
||||
.Apis(CHAT_COMPLETION_API)
|
||||
.ContextWindow(200_000);
|
||||
.ContextWindow(STANDARD_WINDOW);
|
||||
|
||||
//
|
||||
// The 3.x models say nothing beyond the shape above, so nothing is written for them: the
|
||||
@@ -57,22 +106,22 @@ public sealed class ClaudeFamily : ModelFamily
|
||||
// through 4.8 and Sonnet 4.6 have the 1M window, while the 4.0, 4.1 and 4.5 models of the
|
||||
// same prefixes keep the 200k they were released with.
|
||||
//
|
||||
builder.Rule("claude-opus-4-6").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(1_000_000);
|
||||
builder.Rule("claude-opus-4-7").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(1_000_000);
|
||||
builder.Rule("claude-opus-4-8").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(1_000_000);
|
||||
builder.Rule("claude-sonnet-4-6").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(1_000_000);
|
||||
builder.Rule("claude-opus-4-6").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(LARGE_WINDOW);
|
||||
builder.Rule("claude-opus-4-7").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(LARGE_WINDOW);
|
||||
builder.Rule("claude-opus-4-8").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(LARGE_WINDOW);
|
||||
builder.Rule("claude-sonnet-4-6").AsPrefix().InheritsFrom("claude-opus-4").ContextWindow(LARGE_WINDOW);
|
||||
|
||||
// Opus 5 and Sonnet 5 think adaptively unless thinking is turned off:
|
||||
builder.Rule("claude-opus-5").AsPrefix().InheritsFrom("claude")
|
||||
.Reasoning(ReasoningSupport.ON_BY_DEFAULT)
|
||||
.ContextWindow(1_000_000);
|
||||
.ContextWindow(LARGE_WINDOW);
|
||||
|
||||
builder.Rule("claude-sonnet-5").AsPrefix().InheritsFrom("claude-opus-5");
|
||||
|
||||
// Fable 5 and Mythos 5 always think, and there is no switch for it:
|
||||
builder.Rule("claude-fable-5").AsPrefix().InheritsFrom("claude")
|
||||
.Reasoning(ReasoningSupport.ALWAYS)
|
||||
.ContextWindow(1_000_000);
|
||||
.ContextWindow(LARGE_WINDOW);
|
||||
|
||||
builder.Rule("claude-mythos-5").AsPrefix().InheritsFrom("claude-fable-5");
|
||||
}
|
||||
|
||||
@@ -26,12 +26,25 @@ public sealed class GeminiFamily : ModelFamily
|
||||
/// <inheritdoc />
|
||||
public override ModelSource Source => new("https://ai.google.dev/gemini-api/docs/gemini-3", new DateOnly(2026, 9, 12), "Capabilities ported unchanged from ProviderExtensions.Google.cs: one shape for all of Gemini, one sentence per generation about thinking. The Gemini 3 guide states a one million token input window; the 2.5 model pages state their input limit as 1,048,576, and both numbers are written here as their page gives them.");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<ModelSource> FurtherSources =>
|
||||
[
|
||||
new("https://ai.google.dev/gemini-api/docs/image-understanding", new DateOnly(2026, 9, 12), "States one number for the whole family: \"Gemini models support a maximum of 3,600 image files per request.\" The 20 MB it also names is a limit on the request body rather than on the number of images.")
|
||||
];
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Declare(ModelFamilyBuilder builder)
|
||||
{
|
||||
//
|
||||
// Google states the image limit once, for all of Gemini, so it sits on the fallback and
|
||||
// every generation inherits it. The two rules below which do not inherit from here say
|
||||
// nothing about it: the live model looks at no still images at all, and for the 1.0 vision
|
||||
// model Google's current pages state no number any more.
|
||||
//
|
||||
builder.Rule("gemini").AsSegment()
|
||||
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | AUDIO_INPUT | SPEECH_INPUT | VIDEO_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
|
||||
.Apis(CHAT_COMPLETION_API);
|
||||
.Apis(CHAT_COMPLETION_API)
|
||||
.Images(maxPerRequest: 3_600);
|
||||
|
||||
// The one Gemini which only ever read text and images:
|
||||
builder.Rule("gemini-1.0-pro-vision").AsPrefix()
|
||||
|
||||
@@ -35,4 +35,21 @@ public readonly record struct ImageLimits(int? MaxPerMessage, int? MaxPerRequest
|
||||
/// Whether either of the two numbers is known.
|
||||
/// </summary>
|
||||
public bool IsKnown => this.MaxPerMessage.HasValue || this.MaxPerRequest.HasValue;
|
||||
|
||||
/// <summary>
|
||||
/// How many images may travel in one message, as far as anybody has said.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A message is part of a request, so a message cannot carry more than a whole request may --
|
||||
/// whichever of the two numbers is smaller decides, and a number nobody stated does not decide
|
||||
/// anything. Null means nobody stated either, which is a gap and never a limit of zero.
|
||||
/// </remarks>
|
||||
public int? MaxInOneMessage => (this.MaxPerMessage, this.MaxPerRequest) switch
|
||||
{
|
||||
({ } perMessage, { } perRequest) => Math.Min(perMessage, perRequest),
|
||||
({ } perMessage, null) => perMessage,
|
||||
(null, { } perRequest) => perRequest,
|
||||
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
@@ -29,6 +29,17 @@ public abstract class ModelFamily
|
||||
/// </summary>
|
||||
public abstract ModelSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The other pages this family was read from, where one was not enough.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A vendor keeps what a model can do, how much it reads and how many images it takes on three
|
||||
/// different pages often enough. The source above stays the one to start from; these are the
|
||||
/// rest, and the same is asked of them -- a page and a day, so that every number in the family
|
||||
/// leads back to something somebody can open.
|
||||
/// </remarks>
|
||||
public virtual IReadOnlyList<ModelSource> FurtherSources => [];
|
||||
|
||||
/// <summary>
|
||||
/// What this family is called, which is what its rules name as their origin.
|
||||
/// </summary>
|
||||
|
||||
Reference in new issue
Block a user