Port the OpenAI families and add the model registry

This commit is contained in:
Thorsten Sommer committed 2026-09-11 20:01:45 +02:00
1 parent 31b655cf3b
commit 730bd1c7c4
12 files changed
+809

No files matched your search

@@ -0,0 +1,33 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-3.5, which answers with text and does nothing else.
/// </summary>
public sealed class Gpt35Family : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/models", new DateOnly(2026, 9, 11), "Ported unchanged from the rules in ProviderExtensions.OpenAI.cs: text in, text out, no tools and no images.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("gpt-3.5").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(CHAT_COMPLETION_API);
//
// The odd one out, and kept odd on purpose: the previous rules put this one model on the
// Responses API and every other GPT-3.5 on the chat completion API. It reads like an
// oversight, but what the app answers today is what the snapshot pins, and correcting it is
// a decision of its own rather than something to slip into a port.
//
builder.Rule("gpt-3.5-turbo").AsExact()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(RESPONSES_API);
}
}
@@ -0,0 +1,31 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-4 and GPT-4 Turbo.
/// </summary>
/// <remarks>
/// GPT-4o is not one of these, which the name hides and the matching does not: a rule bound to the
/// start of a name only answers where a name part ends, and in "gpt-4o" the part goes on. The
/// previous rules had to say that twice, once as an exact comparison and once as a prefix.
/// </remarks>
public sealed class Gpt4Family : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/models", new DateOnly(2026, 9, 11), "Ported unchanged from the rules in ProviderExtensions.OpenAI.cs: GPT-4 is text only, Turbo adds images and tool calling.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("gpt-4").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(RESPONSES_API);
builder.Rule("gpt-4-turbo").AsPrefix().Inherits()
.Capabilities(MULTIPLE_IMAGE_INPUT | FUNCTION_CALLING);
}
}
@@ -0,0 +1,43 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-4o, including its mini and its audio preview.
/// </summary>
/// <remarks>
/// The previous rules never named this family. Its models reached the last line of the OpenAI
/// function, the one which answers for everything nobody wrote a rule for, and that line happened
/// to describe GPT-4o exactly. Writing it down changes no answer and takes the family out of the
/// fallback, where a wrong answer looks like no answer.
/// </remarks>
public sealed class Gpt4oFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/models/gpt-4o", new DateOnly(2026, 9, 11), "The answer the previous rules gave these models through their fallback: images, tool calling, and web search on the Responses API.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("gpt-4o").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API);
//
// The search previews are the same generation and almost nothing like it: they search the
// web and do nothing else, no images and no tools, and they answer only through the chat
// completion API. Stated in full rather than inherited, because there is barely anything of
// the family left in them.
//
builder.Rule("gpt-4o-search-preview").AsExact()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | WEB_SEARCH)
.Apis(CHAT_COMPLETION_API);
builder.Rule("gpt-4o-mini-search-preview").AsExact()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | WEB_SEARCH)
.Apis(CHAT_COMPLETION_API);
}
}
@@ -0,0 +1,58 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// The whole GPT-5 line, from GPT-5 to GPT-5.6.
/// </summary>
/// <remarks>
/// One family rather than six, because the generations differ in one sentence each and stating that
/// sentence is the entire content: GPT-5 reasons always and answers only through the Responses API,
/// GPT-5.1 reasons on request and answers through both, GPT-5.5 reasons unless told not to.
///
/// The dot is what keeps the generations apart. A rule bound to the start of a name ends at a name
/// part, and a dot does not end one, so "gpt-5" does not answer for "gpt-5.1" -- which is exactly
/// what the previous rules spelled out one comparison at a time.
///
/// None of these models writes images itself. They can ask for one through the image generation
/// tool, which is a tool call producing a picture from a separate model, and reporting that as an
/// output modality would have the chat offer to receive images which never arrive.
/// </remarks>
public sealed class Gpt5Family : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/models", new DateOnly(2026, 9, 11), "Ported unchanged from the rules in ProviderExtensions.OpenAI.cs, one rule per generation, except that the chat alias no longer inherits the reasoning it is named for not having.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("gpt-5").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS);
//
// The alias for the model of this generation which does not reason. The previous rules had
// it swallowed by the prefix above and told it that it always reasons, which is the one
// thing its name rules out. Here the longer pattern simply wins.
//
builder.Rule("gpt-5-chat").AsPrefix().Inherits()
.Reasoning(ReasoningSupport.NONE);
builder.Rule("gpt-5.1").AsPrefix().InheritsFrom("gpt-5")
.Apis(CHAT_COMPLETION_API)
.Reasoning(ReasoningSupport.OPTIONAL);
builder.Rule("gpt-5.2").AsPrefix().InheritsFrom("gpt-5.1");
builder.Rule("gpt-5.3").AsPrefix().InheritsFrom("gpt-5.1");
builder.Rule("gpt-5.4").AsPrefix().InheritsFrom("gpt-5.1");
builder.Rule("gpt-5.5").AsPrefix().InheritsFrom("gpt-5.1")
.Reasoning(ReasoningSupport.ON_BY_DEFAULT);
builder.Rule("gpt-5.6").AsPrefix().InheritsFrom("gpt-5.5");
}
}
@@ -0,0 +1,26 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-6 Astra.
/// </summary>
/// <remarks>
/// Unlike the 5.5 and 5.6 models it reasons on every request: the effort reaches from low to max,
/// and there is no setting which switches thinking off.
/// </remarks>
public sealed class Gpt6AstraFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/models", new DateOnly(2026, 9, 11), "Ported unchanged from the rules in ProviderExtensions.OpenAI.cs: reasons on every request, both APIs.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder) =>
builder.Rule("gpt-6-astra").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API | CHAT_COMPLETION_API)
.Reasoning(ReasoningSupport.ALWAYS);
}
@@ -0,0 +1,51 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// The o-series: o1, o3, o4 and their minis, the models which reason before they answer.
/// </summary>
/// <remarks>
/// Every one of them always reasons; what differs is how much else they can do, and the minis are
/// consistently the ones which can do less. That the mini is not simply a smaller version of its
/// generation is why each of them is stated in full: o1-mini has neither images nor tools and
/// answers only through the chat completion API, while o3-mini has tools but no images.
///
/// The minis need no ordering: their patterns are longer, so they win over the generation they
/// belong to without anybody saying which rule to try first.
/// </remarks>
public sealed class OSeriesFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/models", new DateOnly(2026, 9, 11), "Ported unchanged from the rules in ProviderExtensions.OpenAI.cs, one rule per generation and one per mini.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("o1").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS);
builder.Rule("o1-mini").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(CHAT_COMPLETION_API)
.Reasoning(ReasoningSupport.ALWAYS);
builder.Rule("o3").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS);
builder.Rule("o3-mini").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS);
// The one mini which is not cut down: it is the o3 generation under another number.
builder.Rule("o4-mini").AsPrefix().InheritsFrom("o3");
}
}
@@ -0,0 +1,35 @@
using AIStudio.Provider;
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// OpenAI's embedding models, which turn text into a vector and answer nothing.
/// </summary>
/// <remarks>
/// The previous rules had no idea these existed. They fell through to the OpenAI fallback and were
/// told they see images, call functions, and search the web -- an answer with nothing right about
/// it, for models the app asks for through a separate method of its own.
///
/// The generation is named rather than the prefix "text-embedding": Google and Alibaba Cloud name
/// their own embedding models the same way, and those are their models, not these.
/// </remarks>
public sealed class OpenAIEmbeddingFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/guides/embeddings", new DateOnly(2026, 9, 11), "The app lists these under IProvider.GetEmbeddingModels, which is where the statement that they embed comes from.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("text-embedding-3").AsPrefix()
.Capabilities(TEXT_INPUT | EMBEDDING)
.Kind(ModelKind.EMBEDDING);
builder.Rule("text-embedding-ada").AsPrefix().Inherits();
}
}
@@ -0,0 +1,29 @@
using AIStudio.Provider;
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// Whisper, which listens and writes down what it heard.
/// </summary>
/// <remarks>
/// OpenAI built it and released the weights, so it turns up far beyond OpenAI's own API: Fireworks,
/// the GWDG, and Groq all serve a Whisper. This family is bound to no provider for that reason --
/// it is the same model wherever it runs, and the previous rules answered for it at every one of
/// those places with the global fallback, tool calling included.
/// </remarks>
public sealed class WhisperFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/guides/speech-to-text", new DateOnly(2026, 9, 11), "The app lists these under IProvider.GetTranscriptionModels, which is where the statement that they transcribe comes from.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder) =>
builder.Rule("whisper").AsSegment()
.Capabilities(SPEECH_INPUT | TEXT_OUTPUT)
.Kind(ModelKind.TRANSCRIPTION);
}