From 142f4807bfef6358f21ac060e5fe060a01157545 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 13 Sep 2026 13:55:32 +0200 Subject: [PATCH] Name the model's window when a text was too long --- .../Assistants/I18N/allTexts.lua | 3 ++ .../Provider/BaseProvider.cs | 38 +++++++++++++++++-- .../Provider/Google/ProviderGoogle.cs | 2 +- .../HuggingFace/ProviderHuggingFace.cs | 5 ++- .../Provider/OpenAI/ProviderOpenAI.cs | 5 ++- .../Tools/PluginSystem/I18N.cs | 27 +++++++++++-- 6 files changed, 68 insertions(+), 12 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index ad8f245d..016bb778 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -10024,6 +10024,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4049517041"] = "We tried to -- The provider '{0}' does not know the selected model. Please select another model. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T411393889"] = "The provider '{0}' does not know the selected model. Please select another model." +-- The text was longer than the selected model accepts, which is {0} tokens. Please select a model which takes longer texts, or reduce the chunk size of the data source. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T479223640"] = "The text was longer than the selected model accepts, which is {0} tokens. Please select a model which takes longer texts, or reduce the chunk size of the data source." + -- The provider '{0}' reported an error: {1} UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T700894460"] = "The provider '{0}' reported an error: {1}" diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 58166675..2c15830c 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using AIStudio.Chat; +using AIStudio.Models; using AIStudio.Models.Live; using AIStudio.Provider.Anthropic; using AIStudio.Provider.OpenAI; @@ -247,13 +248,34 @@ public abstract class BaseProvider : IProvider, ISecretId } } - protected virtual string GetProviderRequestFailureUserMessage(ProviderRequestFailureReason failureReason) => failureReason switch + /// + /// Says what a failed request means for the user. + /// + /// + /// The window is only ever known where the caller knows which model the request was for, which + /// is why it is optional rather than a second required argument: most failures say nothing + /// about a length and need no number to explain themselves. + /// + /// Why the request failed. + /// What the model reads, where that is known. + /// The message to show, or an empty string when we have nothing to say. + protected virtual string GetProviderRequestFailureUserMessage(ProviderRequestFailureReason failureReason, ContextWindow contextWindow = default) => failureReason switch { ProviderRequestFailureReason.TOO_MANY_REQUESTS => TB("The provider rejected the request because too many requests were sent. Please wait a moment and try again."), ProviderRequestFailureReason.INVALID_OR_MISSING_API_KEY => string.Format(TB("The API key for the provider '{0}' is missing or was rejected. Please check the key in the settings."), this.InstanceName), ProviderRequestFailureReason.AUTHENTICATION_OR_PERMISSION_ERROR => string.Format(TB("The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region."), this.InstanceName), ProviderRequestFailureReason.PROVIDER_UNAVAILABLE => string.Format(TB("The provider '{0}' could not be reached. Please check whether it is running and reachable, then try again."), this.InstanceName), ProviderRequestFailureReason.MODEL_NOT_FOUND => string.Format(TB("The provider '{0}' does not know the selected model. Please select another model."), this.InstanceName), + // + // Naming the number is the whole point of knowing it: "too long" leaves the user guessing + // by how much, while the window turns the next step into arithmetic. Where nobody knows the + // window, no number is invented -- the sentence below says the same thing without one. + // + // Written out in full rather than shortened the way the chat shortens it. The sentence ends + // by asking the user to set a chunk size, and 32.77k is not a number anybody types into a + // field. + // + ProviderRequestFailureReason.CONTEXT_LENGTH_EXCEEDED when contextWindow.IsKnown => string.Format(TB("The text was longer than the selected model accepts, which is {0} tokens. Please select a model which takes longer texts, or reduce the chunk size of the data source."), contextWindow.DefaultTokens.ToString("N0", I18N.I.Culture)), ProviderRequestFailureReason.CONTEXT_LENGTH_EXCEEDED => TB("The text was longer than the selected model accepts. Please select a model which takes longer texts, or reduce the chunk size of the data source."), ProviderRequestFailureReason.TOOLS_NOT_SUPPORTED => string.Format(TB("The selected model is not able to use tools. Please select a model which can, or open the settings of the provider '{0}', show its expert settings, and switch the function calling capability off there."), this.InstanceName), ProviderRequestFailureReason.EMBEDDINGS_NOT_SUPPORTED => string.Format(TB("The provider '{0}' cannot create embeddings. Please select a provider which offers an embedding model."), this.InstanceName), @@ -279,10 +301,18 @@ public abstract class BaseProvider : IProvider, ISecretId /// Shared with the providers which talk to an embedding endpoint of their own: what the user /// needs to know does not depend on which route the request took. /// - protected ProviderRequestException CreateEmbeddingRequestException(HttpStatusCode statusCode, string reasonPhrase, string responseBody) + protected ProviderRequestException CreateEmbeddingRequestException(HttpStatusCode statusCode, string reasonPhrase, string responseBody, Model embeddingModel) { + // + // What the rules know about this model, corrected by whatever this installation reported + // about it. That is the same walk a configured chat provider takes, minus the expert + // settings: an embedding provider has none, so there is nothing above the two to ask. + // + var stated = this.Provider.GetModelProfile(embeddingModel); + var contextWindow = ListedModels.Shared.Of(this.ConfiguredProviderId, embeddingModel.Id).ApplyTo(stated).Context; + var failureReason = this.ClassifyEmbeddingRequestFailure(statusCode, responseBody); - var userMessage = this.GetProviderRequestFailureUserMessage(failureReason); + var userMessage = this.GetProviderRequestFailureUserMessage(failureReason, contextWindow); // We know nothing about this failure, so we pass on what the provider said about it: if (string.IsNullOrWhiteSpace(userMessage)) @@ -1551,7 +1581,7 @@ public abstract class BaseProvider : IProvider, ISecretId // thousands being indexed in the background or the one thing the user just asked // for, and only it can decide how often the user should hear about it. // - throw this.CreateEmbeddingRequestException(response.StatusCode, response.ReasonPhrase ?? string.Empty, responseBody); + throw this.CreateEmbeddingRequestException(response.StatusCode, response.ReasonPhrase ?? string.Empty, responseBody, embeddingModel); } var embeddingResponse = JsonSerializer.Deserialize(responseBody, JSON_SERIALIZER_OPTIONS); diff --git a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs index 17363e1d..af4486f3 100644 --- a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs +++ b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs @@ -116,7 +116,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, new Uri("https if (!response.IsSuccessStatusCode) { LOGGER.LogError("Embedding request failed with status code {ResponseStatusCode} and body: '{ResponseBody}'.", response.StatusCode, responseBody); - throw this.CreateEmbeddingRequestException(response.StatusCode, response.ReasonPhrase ?? string.Empty, responseBody); + throw this.CreateEmbeddingRequestException(response.StatusCode, response.ReasonPhrase ?? string.Empty, responseBody, embeddingModel); } var embeddingResponse = JsonSerializer.Deserialize(responseBody, JSON_SERIALIZER_OPTIONS); diff --git a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs index debd89e9..2a225ae8 100644 --- a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs +++ b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using AIStudio.Chat; +using AIStudio.Models; using AIStudio.Models.Live; using AIStudio.Provider.OpenAI; using AIStudio.Settings; @@ -128,10 +129,10 @@ public sealed class ProviderHuggingFace : BaseProvider } /// - protected override string GetProviderRequestFailureUserMessage(ProviderRequestFailureReason failureReason) + protected override string GetProviderRequestFailureUserMessage(ProviderRequestFailureReason failureReason, ContextWindow contextWindow = default) { if (failureReason is not ProviderRequestFailureReason.MODEL_NOT_SUPPORTED_BY_PROVIDER) - return base.GetProviderRequestFailureUserMessage(failureReason); + return base.GetProviderRequestFailureUserMessage(failureReason, contextWindow); // // When Hugging Face chose the provider itself, naming it back to the user would help diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index faa40eb6..7de4e08f 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -5,6 +5,7 @@ using System.Text; using System.Text.Json; using AIStudio.Chat; +using AIStudio.Models; using AIStudio.Settings; using AIStudio.Tools.PluginSystem; using AIStudio.Tools.Rust; @@ -48,10 +49,10 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur return base.ClassifyProviderRequestFailure(errorCode, errorType, errorMessage, responseBody); } - protected override string GetProviderRequestFailureUserMessage(ProviderRequestFailureReason failureReason) => failureReason switch + protected override string GetProviderRequestFailureUserMessage(ProviderRequestFailureReason failureReason, ContextWindow contextWindow = default) => failureReason switch { ProviderRequestFailureReason.INSUFFICIENT_QUOTA => TB("It looks like you do not have any API credits left with OpenAI. Please add credits to your account and try again."), - _ => base.GetProviderRequestFailureUserMessage(failureReason), + _ => base.GetProviderRequestFailureUserMessage(failureReason, contextWindow), }; /// diff --git a/app/MindWork AI Studio/Tools/PluginSystem/I18N.cs b/app/MindWork AI Studio/Tools/PluginSystem/I18N.cs index 134c9587..cb113149 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/I18N.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/I18N.cs @@ -1,17 +1,38 @@ +using System.Globalization; + namespace AIStudio.Tools.PluginSystem; public class I18N : ILang { public static readonly I18N I = new(); private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(); - + private ILanguagePlugin? language; - + private I18N() { } - public static void Init(ILanguagePlugin language) => I.language = language; + /// + /// How the language in use writes its numbers, or the invariant culture while none is loaded. + /// + /// + /// A number standing inside a translated sentence has to be written the way that language + /// writes numbers. AI Studio's language is chosen in its own settings and never moves the + /// thread's culture along with it, so a number formatted from the thread comes out with English + /// separators inside a German sentence. It lives here because it is the same decision as the + /// texts: whoever picked the language picked how its numbers look. + /// + /// Components which already hold the active plugin may keep deriving it themselves. This is for + /// the code which has no plugin to ask -- a provider building an error message, say. + /// + public CultureInfo Culture { get; private set; } = CultureInfo.InvariantCulture; + + public static void Init(ILanguagePlugin language) + { + I.language = language; + I.Culture = CommonTools.DeriveActiveCultureOrInvariant(language.IETFTag); + } #region Implementation of ILang