Added model availability check and error handling for unavailable models

This commit is contained in:
Peer Schütt 2026-03-30 15:29:48 +02:00
parent 6146446fa9
commit eb80a0df95
4 changed files with 78 additions and 1 deletions

View File

@ -1732,6 +1732,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, kee
-- Export Chat to Microsoft Word
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word"
-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."
-- The local image file does not exist. Skipping the image.
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "The local image file does not exist. Skipping the image."

View File

@ -3,6 +3,8 @@ using System.Text.Json.Serialization;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.RAG.RAGProcesses;
namespace AIStudio.Chat;
@ -13,6 +15,7 @@ namespace AIStudio.Chat;
public sealed class ContentText : IContent
{
private static readonly ILogger<ContentText> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ContentText>();
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ContentText).Namespace, nameof(ContentText));
/// <summary>
/// The minimum time between two streaming events, when the user
@ -48,11 +51,21 @@ public sealed class ContentText : IContent
public async Task<ChatThread> CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastUserPrompt, ChatThread? chatThread, CancellationToken token = default)
{
if(chatThread is null)
{
await this.CompleteWithoutStreaming();
return new();
}
if(!chatThread.IsLLMProviderAllowed(provider))
{
LOGGER.LogError("The provider is not allowed for this chat thread due to data security reasons. Skipping the AI process.");
await this.CompleteWithoutStreaming();
return chatThread;
}
if(!await this.CheckSelectedModelAvailability(provider, chatModel, token))
{
await this.CompleteWithoutStreaming();
return chatThread;
}
@ -137,6 +150,61 @@ public sealed class ContentText : IContent
return chatThread;
}
private async Task CompleteWithoutStreaming()
{
this.InitialRemoteWait = false;
this.IsStreaming = false;
await this.StreamingDone();
}
private static bool ModelsMatch(Model modelA, Model modelB)
{
var idA = modelA.Id.Trim();
var idB = modelB.Id.Trim();
return string.Equals(idA, idB, StringComparison.OrdinalIgnoreCase);
}
private async Task<bool> CheckSelectedModelAvailability(IProvider provider, Model chatModel, CancellationToken token = default)
{
if(chatModel.IsSystemModel)
return true;
if(string.IsNullOrWhiteSpace(chatModel.Id))
return true;
IEnumerable<Model> loadedModels;
try
{
loadedModels = await provider.GetTextModels(token: token);
}
catch (OperationCanceledException)
{
return false;
}
catch (Exception e)
{
LOGGER.LogWarning(e, "Skipping selected model availability check for '{ProviderInstanceName}' (provider={ProviderType}) because the model list could not be loaded.", provider.InstanceName, provider.Provider);
return true;
}
var availableModels = loadedModels.Where(model => !string.IsNullOrWhiteSpace(model.Id)).ToList();
if (availableModels.Count == 0)
return true;
if(availableModels.Any(model => ModelsMatch(model, chatModel)))
return true;
var message = string.Format(
TB("The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."),
chatModel.Id,
provider.InstanceName,
provider.Provider);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, message));
LOGGER.LogWarning("Skipping AI request because model '{ModelId}' is not available from '{ProviderInstanceName}' (provider={ProviderType}).", chatModel.Id, provider.InstanceName, provider.Provider);
return false;
}
/// <inheritdoc />
public IContent DeepClone() => new ContentText
{
@ -214,4 +282,4 @@ public sealed class ContentText : IContent
/// The text content.
/// </summary>
public string Text { get; set; } = string.Empty;
}
}

View File

@ -1734,6 +1734,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "Nein, b
-- Export Chat to Microsoft Word
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Chat in Microsoft Word exportieren"
-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "Das ausgewählte Modell '{0}' ist bei '{1}' (Anbieter={2}) nicht mehr verfügbar. Bitte passen Sie Ihre Anbietereinstellungen an."
-- The local image file does not exist. Skipping the image.
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "Die lokale Bilddatei existiert nicht. Das Bild wird übersprungen."

View File

@ -1734,6 +1734,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, kee
-- Export Chat to Microsoft Word
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word"
-- The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTTEXT::T3267850764"] = "The selected model '{0}' is no longer available from '{1}' (provider={2}). Please adapt your provider settings."
-- The local image file does not exist. Skipping the image.
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "The local image file does not exist. Skipping the image."