mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-13 20:33:49 +00:00
Read the context window from four more model lists
This commit is contained in:
16 files changed
+415
-28
No files matched your search
@@ -6,6 +6,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Models.Live;
|
||||
using AIStudio.Provider.Anthropic;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Provider.SelfHosted;
|
||||
@@ -191,6 +192,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
Action<HttpRequestMessage, string>? requestConfigurator = null,
|
||||
JsonSerializerOptions? jsonSerializerOptions = null,
|
||||
bool isTryingSecret = false,
|
||||
Func<TResponse, IEnumerable<ModelListing>>? listingFactory = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var secretKey = await this.GetModelLoadingSecretKey(storeType, apiKeyProvisional, isTryingSecret);
|
||||
@@ -220,6 +222,16 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
if (parsedResponse is null)
|
||||
return FailedModelLoadResult(ModelLoadFailureReason.INVALID_RESPONSE, "Model list response could not be deserialized.");
|
||||
|
||||
//
|
||||
// What the list stated about the models, read before anything is filtered out of
|
||||
// it: a model left out below as an embedding model is still a model somebody may
|
||||
// have configured this instance with, and a list like this one is the only place
|
||||
// its window is ever stated. Only pass a whole list in here -- reporting a part of
|
||||
// one would tell the app that everything left out has stopped existing.
|
||||
//
|
||||
if (listingFactory is not null)
|
||||
ListedModels.Shared.Report(this.ConfiguredProviderId, listingFactory(parsedResponse));
|
||||
|
||||
return SuccessfulModelLoadResult(modelFactory(parsedResponse));
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Provider.Groq;
|
||||
|
||||
/// <summary>
|
||||
/// One model as Groq lists it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Groq says more about a model than the shared OpenAI-compatible list does, which is why this
|
||||
/// provider brings a data model of its own instead of using that one: the shared record is read by
|
||||
/// a dozen providers, and a field only one of them sends has no business in it.
|
||||
/// </remarks>
|
||||
/// <param name="Id">The model's ID.</param>
|
||||
/// <param name="ContextWindowTokens">How much the model reads and writes in one conversation, in tokens.</param>
|
||||
public readonly record struct GroqModel(string Id, [property: JsonPropertyName("context_window")] int? ContextWindowTokens);
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace AIStudio.Provider.Groq;
|
||||
|
||||
/// <summary>
|
||||
/// A data model for the response from the Groq models endpoint.
|
||||
/// </summary>
|
||||
/// <param name="Data">The models Groq serves.</param>
|
||||
public readonly record struct GroqModelsResponse(IList<GroqModel> Data);
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Models.Live;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
|
||||
@@ -113,10 +114,12 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, new Uri("https://a
|
||||
|
||||
private Task<ModelLoadResult> LoadModels(SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
return this.LoadModelsResponse<ModelsResponse>(
|
||||
return this.LoadModelsResponse<GroqModelsResponse>(
|
||||
storeType,
|
||||
"models",
|
||||
modelResponse => modelResponse.Data,
|
||||
apiKeyProvisional, token: token);
|
||||
modelResponse => modelResponse.Data.Select(n => new Model(n.Id, null)),
|
||||
apiKeyProvisional,
|
||||
listingFactory: modelResponse => modelResponse.Data.Select(n => ModelListing.For(n.Id, n.ContextWindowTokens)),
|
||||
token: token);
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,30 @@ namespace AIStudio.Provider.HuggingFace;
|
||||
/// </summary>
|
||||
/// <param name="Id">The ID of the model, written as "org/model".</param>
|
||||
/// <param name="Providers">The inference providers serving this model.</param>
|
||||
public readonly record struct HFModel(string Id, IList<HFModelProvider>? Providers);
|
||||
public readonly record struct HFModel(string Id, IList<HFModelProvider>? Providers)
|
||||
{
|
||||
/// <summary>
|
||||
/// The window this model has when it is reached the way this user set things up.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A window belongs to an inference provider here, not to the model: the same weights run
|
||||
/// behind several of them, each configured by somebody else. Where the user named one, its
|
||||
/// number is the answer. Where they let the router choose, the smallest window among the
|
||||
/// providers currently serving the model is -- nobody knows which one the router will take, and
|
||||
/// a number promising more than the chosen provider delivers would walk a conversation into an
|
||||
/// error the user could not see coming.
|
||||
/// </remarks>
|
||||
/// <param name="providerSlug">The inference provider the user chose, or empty when the router chooses.</param>
|
||||
/// <returns>The window in tokens, or null where nobody stated one.</returns>
|
||||
public int? ContextWindowTokens(string providerSlug)
|
||||
{
|
||||
if (this.Providers is null)
|
||||
return null;
|
||||
|
||||
var serving = this.Providers.Where(provider => provider.IsLive);
|
||||
if (!string.IsNullOrEmpty(providerSlug))
|
||||
serving = serving.Where(provider => string.Equals(provider.Provider, providerSlug, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return serving.Where(provider => provider.ContextWindowTokens is > 0).Min(provider => provider.ContextWindowTokens);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Provider.HuggingFace;
|
||||
|
||||
/// <summary>
|
||||
@@ -5,4 +7,13 @@ namespace AIStudio.Provider.HuggingFace;
|
||||
/// </summary>
|
||||
/// <param name="Provider">The slug of the inference provider, e.g. "novita".</param>
|
||||
/// <param name="Status">Whether the provider currently serves the model. Known value: "live".</param>
|
||||
public readonly record struct HFModelProvider(string Provider, string Status);
|
||||
/// <param name="ContextWindowTokens">How much this provider reads and writes in one conversation, in tokens.</param>
|
||||
public readonly record struct HFModelProvider(string Provider, string Status, [property: JsonPropertyName("context_length")] int? ContextWindowTokens)
|
||||
{
|
||||
private const string LIVE = "live";
|
||||
|
||||
/// <summary>
|
||||
/// Whether this provider serves the model right now.
|
||||
/// </summary>
|
||||
public bool IsLive => string.Equals(this.Status, LIVE, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Models.Live;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
@@ -221,7 +222,23 @@ public sealed class ProviderHuggingFace : BaseProvider
|
||||
/// <inheritdoc />
|
||||
public override Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModelsResponse<ModelsResponse>(SecretStoreType.LLM_PROVIDER, "models", this.SelectChatModels, apiKeyProvisional, token: token);
|
||||
return this.LoadModelsResponse<ModelsResponse>(SecretStoreType.LLM_PROVIDER, "models", this.SelectChatModels, apiKeyProvisional, listingFactory: this.ListingsOf, token: token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What the router stated about the models it knows.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every model the router reports, not only the ones offered for chatting below: which models
|
||||
/// are offered depends on the chosen inference provider, while a window belongs to whoever is
|
||||
/// configured here, and both questions are asked of the same list.
|
||||
/// </remarks>
|
||||
/// <param name="response">The response of the model endpoint.</param>
|
||||
/// <returns>One listing per model, which says nothing for the models nobody stated a window for.</returns>
|
||||
private IEnumerable<ModelListing> ListingsOf(ModelsResponse response)
|
||||
{
|
||||
var providerSlug = this.hfProvider.EndpointsId();
|
||||
return response.Data.Select(hfModel => ModelListing.For(hfModel.Id, hfModel.ContextWindowTokens(providerSlug)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -253,8 +270,8 @@ public sealed class ProviderHuggingFace : BaseProvider
|
||||
return false;
|
||||
|
||||
return hfModel.Providers.Any(provider =>
|
||||
string.Equals(provider.Provider, providerSlug, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(provider.Status, "live", StringComparison.OrdinalIgnoreCase));
|
||||
provider.IsLive &&
|
||||
string.Equals(provider.Provider, providerSlug, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Provider.Mistral;
|
||||
|
||||
public readonly record struct Model(string Id, string Object, int Created, string OwnedBy);
|
||||
/// <summary>
|
||||
/// One model as Mistral lists it.
|
||||
/// </summary>
|
||||
/// <param name="Id">The model's ID.</param>
|
||||
/// <param name="Object">What kind of thing the entry is. Known value: "model".</param>
|
||||
/// <param name="Created">When the model was published, as seconds since the epoch.</param>
|
||||
/// <param name="OwnedBy">Who Mistral names as the owner of the model.</param>
|
||||
/// <param name="ContextWindowTokens">How much the model reads and writes in one conversation, in tokens.</param>
|
||||
public readonly record struct Model(string Id, string Object, int Created, string OwnedBy, [property: JsonPropertyName("max_context_length")] int? ContextWindowTokens);
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Models.Live;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
|
||||
@@ -139,6 +140,8 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
|
||||
storeType,
|
||||
"models",
|
||||
modelResponse => modelResponse.Data.Select(n => new Provider.Model(n.Id, null)),
|
||||
apiKeyProvisional, token: token);
|
||||
apiKeyProvisional,
|
||||
listingFactory: modelResponse => modelResponse.Data.Select(n => ModelListing.For(n.Id, n.ContextWindowTokens)),
|
||||
token: token);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Provider.OpenRouter;
|
||||
|
||||
/// <summary>
|
||||
/// A data model for an OpenRouter model from the API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The window is the model's, not that of any one provider behind it. OpenRouter also states a
|
||||
/// window per provider it currently prefers, but it picks one per request, so a number taken from
|
||||
/// there would describe a choice nobody has made yet.
|
||||
/// </remarks>
|
||||
/// <param name="Id">The model's ID.</param>
|
||||
/// <param name="Name">The model's human-readable display name.</param>
|
||||
public readonly record struct OpenRouterModel(string Id, string? Name);
|
||||
/// <param name="ContextWindowTokens">How much the model reads and writes in one conversation, in tokens.</param>
|
||||
public readonly record struct OpenRouterModel(string Id, string? Name, [property: JsonPropertyName("context_length")] int? ContextWindowTokens);
|
||||
@@ -2,6 +2,7 @@ using System.Net.Http.Headers;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Models.Live;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
|
||||
@@ -125,9 +126,21 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
request.Headers.Add("HTTP-Referer", PROJECT_WEBSITE);
|
||||
request.Headers.Add("X-Title", PROJECT_NAME);
|
||||
},
|
||||
listingFactory: modelResponse => modelResponse.Data.Select(n => ModelListing.For(n.Id, n.ContextWindowTokens)),
|
||||
token: token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the models OpenRouter offers for embedding, which live on a route of their own.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Nothing is reported from here: this route answers with the embedding models alone, and what
|
||||
/// is reported replaces everything an instance said before. The windows of the chat models
|
||||
/// would go missing the moment somebody opens the embedding settings.
|
||||
/// </remarks>
|
||||
/// <param name="apiKeyProvisional">An API key which is not stored yet.</param>
|
||||
/// <param name="token">The cancellation token to use.</param>
|
||||
/// <returns>The embedding models.</returns>
|
||||
private Task<ModelLoadResult> LoadEmbeddingModels(string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
return this.LoadModelsResponse<OpenRouterModelsResponse>(
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Models;
|
||||
using AIStudio.Models.Live;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
@@ -322,23 +321,9 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
/// <summary>
|
||||
/// What an engine stated about the models it serves.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A window of zero or less is dropped rather than repaired. An engine answering that way is
|
||||
/// telling us something we cannot interpret, and falling back to what the rules say about the
|
||||
/// weights is the one answer nobody has to invent.
|
||||
/// </remarks>
|
||||
/// <param name="models">The models exactly as the engine listed them.</param>
|
||||
/// <returns>One listing per model the engine said something usable about.</returns>
|
||||
private static IEnumerable<ModelListing> ListingsOf(IEnumerable<Model> models)
|
||||
{
|
||||
foreach (var model in models)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(model.Id) || model.ContextWindowTokens is not > 0)
|
||||
continue;
|
||||
|
||||
yield return new(model.Id, ContextWindow.Of(model.ContextWindowTokens.Value));
|
||||
}
|
||||
}
|
||||
/// <returns>One listing per model, which says nothing for the models the engine was silent about.</returns>
|
||||
private static IEnumerable<ModelListing> ListingsOf(IEnumerable<Model> models) => models.Select(model => ModelListing.For(model.Id, model.ContextWindowTokens));
|
||||
|
||||
private static bool IsMatchingLlamaCppTextModel(Model model, string[] ignorePhrases, string[] filterPhrases)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user