Added Requesty as a provider (#1006)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Thibault JaiguandThorsten Sommer authored and GitHub committed 2026-10-13 08:29:06 +02:00
1 parent f912af321f
commit 2bd3d41421
44 files changed
+518 -65

No files matched your search

@@ -9,8 +9,8 @@ namespace AIStudio.Models.Hosting;
/// A wrapping is worked on the name as the provider reported it, never on the normalized one. That
/// is not a detail: normalizing writes every separator as a hyphen, so "meta-llama/Llama-3.3-70B"
/// and "meta-llama-llama-3.3-70b" are the same text afterwards and nobody can say where the
/// organization ended. The slash, the colon, and the spaces are the whole evidence, and they only
/// exist in the original.
/// organization ended. The slash, the colon, the at sign, and the spaces are the whole evidence, and
/// they only exist in the original.
/// </remarks>
public static class HostNaming
{
@@ -20,10 +20,15 @@ public static class HostNaming
private const char ORGANIZATION_SEPARATOR = '/';
/// <summary>
/// What separates the model from the inference provider it should be routed to.
/// What separates the model from how a request for it should be routed.
/// </summary>
private const char ROUTING_SEPARATOR = ':';
/// <summary>
/// What separates the model from the region it should run in.
/// </summary>
private const char REGION_SEPARATOR = '@';
/// <summary>
/// Takes the organization off a hub style name.
/// </summary>
@@ -66,20 +71,46 @@ public static class HostNaming
/// Takes the routing suffix off a name.
/// </summary>
/// <remarks>
/// The suffix says where a request goes, not what the model is: "google/gemma-4-31B-it:novita"
/// is the same model as "google/gemma-4-31B-it". Names on the hub carry no colon of their own,
/// so the last one always starts the suffix. This is not true everywhere -- Ollama writes the
/// variant after a colon, as in "qwen3.8:27b-mlx", and taking that off would throw away which
/// model it is. That is why only the host which has a router asks for this.
/// The suffix says how a request is served, not what the model is: "google/gemma-4-31B-it:novita"
/// is the same model as "google/gemma-4-31B-it". On the Hugging Face router it names the
/// inference provider; Requesty writes the service tier there, as in "openai/gpt-6-sol:flex", or
/// a preset reasoning effort, as in "openai/o4-mini:high". Names at both hosts carry no colon of
/// their own, so the last one always starts the suffix. This is not true everywhere -- Ollama
/// writes the variant after a colon, as in "qwen3.8:27b-mlx", and taking that off would throw
/// away which model it is. That is why only the hosts which put a suffix there ask for this.
/// </remarks>
/// <param name="id">The name as it arrived.</param>
/// <param name="inner">The name without its routing suffix.</param>
/// <returns>True, when there was a suffix to take off.</returns>
public static bool TryStripRoutingSuffix(in ModelId id, out ModelId inner)
public static bool TryStripRoutingSuffix(in ModelId id, out ModelId inner) => TryStripSuffix(id, ROUTING_SEPARATOR, out inner);
/// <summary>
/// Takes the region suffix off a name.
/// </summary>
/// <remarks>
/// The suffix says where a request runs, not what the model is:
/// "bedrock/claude-opus-5@eu-central-1" is the same model as "bedrock/claude-opus-5", served from
/// Frankfurt. Requesty writes names this way, and every suffix it used on the day we checked was a
/// region: "eu", "us", or a data center of AWS, Azure, or Google. No model name carries an at sign
/// of its own, so the last one always starts the suffix.
/// </remarks>
/// <param name="id">The name as it arrived.</param>
/// <param name="inner">The name without its region suffix.</param>
/// <returns>True, when there was a suffix to take off.</returns>
public static bool TryStripRegionSuffix(in ModelId id, out ModelId inner) => TryStripSuffix(id, REGION_SEPARATOR, out inner);
/// <summary>
/// Takes off everything from the last separator on.
/// </summary>
/// <param name="id">The name as it arrived.</param>
/// <param name="separator">The character which starts the suffix.</param>
/// <param name="inner">The name without its suffix.</param>
/// <returns>True, when there was a suffix to take off.</returns>
private static bool TryStripSuffix(in ModelId id, char separator, out ModelId inner)
{
inner = id;
var separatorIndex = id.Original.LastIndexOf(ROUTING_SEPARATOR);
var separatorIndex = id.Original.LastIndexOf(separator);
if (separatorIndex is -1)
return false;
@@ -166,8 +197,8 @@ public static class HostNaming
"cohere" or "coherelabs" or "cohereforai" => ModelVendor.COHERE,
"moonshot" or "moonshotai" => ModelVendor.MOONSHOT_AI,
"tencent" or "tencent-hunyuan" => ModelVendor.TENCENT,
"z-ai" or "zai-org" => ModelVendor.Z_AI,
"minimax" or "minimaxai" => ModelVendor.MINIMAX,
"z-ai" or "zai" or "zai-org" => ModelVendor.Z_AI,
"minimax" or "minimaxai" or "minimaxi" => ModelVendor.MINIMAX,
"ai2" or "allenai" => ModelVendor.AI2,
"bytedance" or "bytedance-seed" => ModelVendor.BYTE_DANCE,
"tii" or "tiiuae" => ModelVendor.TII,
@@ -0,0 +1,50 @@
using AIStudio.Models.Matching;
using AIStudio.Provider;
namespace AIStudio.Models.Hosting.Hosts;
/// <summary>
/// Requesty, a gateway which serves other people's models and says who runs them.
/// </summary>
/// <remarks>
/// A name comes as "provider/model", and the provider is whoever runs the model: the vendor's own
/// API, as in "openai/gpt-6-sol", or a cloud serving somebody else's model, as in
/// "bedrock/claude-opus-5" or "vertex/gemini-3.5-flash". A cloud which serves weights from the hub
/// keeps the hub's organization as a second segment, as in "novita/qwen/qwen3.8-max". The walk
/// takes off one segment at a time, and the innermost one which names a vendor is the one that
/// counts.
///
/// Where the provider is also a vendor, its name states that vendor. That is right whenever a
/// vendor serves its own models, which is nearly always. It is wrong where the cloud of a vendor
/// resells other models, as in "tencent/kimi-k3": on the day we checked, about one in twenty of the
/// stated vendors was wrong this way. The price is small, because no built-in rule insists on a
/// vendor -- only a model plugin which does would miss such a name and leave it to the built-in
/// rules.
///
/// Behind the model, a name may carry one more thing which does not change the model: the region it
/// runs in after an at sign, as in "@eu-central-1" or "@eu", or the service tier or a preset
/// reasoning effort after a colon, as in ":flex" or ":high". These come off first. Managed
/// policies, which Requesty curates and routes across several providers, carry no prefix at all,
/// as in "claude-opus-5@eu", so they only lose their region.
/// </remarks>
public sealed class HostRequesty : ModelHost
{
/// <inheritdoc />
public override LLMProviders Provider => LLMProviders.REQUESTY;
/// <inheritdoc />
public override ModelSource Source => new("https://docs.requesty.ai/features/eu-routing", new DateOnly(2026, 10, 12), "Models are named \"provider/model\", where the provider is whoever runs the model, optionally followed by a region after an at sign; the model list adds a service tier or a reasoning effort after a colon, and managed policies without any prefix. All of them are served through the OpenAI-compatible chat completion API.");
/// <inheritdoc />
public override bool TryUnwrap(in ModelId id, out ModelId inner, out ModelVendor? declaredVendor)
{
declaredVendor = null;
if (HostNaming.TryStripRegionSuffix(id, out inner))
return true;
if (HostNaming.TryStripRoutingSuffix(id, out inner))
return true;
return HostNaming.TrySplitOrganization(id, out inner, out declaredVendor);
}
}