From d58f0931249f6ad76ae926ef728710528a8caaad Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 11 Jun 2026 15:40:53 +0200 Subject: [PATCH] Added support for filtering --- .../Provider/SelfHosted/ModelsResponse.cs | 4 +- .../Provider/SelfHosted/ProviderSelfHosted.cs | 54 ++++++++++++++++--- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ModelsResponse.cs b/app/MindWork AI Studio/Provider/SelfHosted/ModelsResponse.cs index fcb23c61..545c9939 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ModelsResponse.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ModelsResponse.cs @@ -2,4 +2,6 @@ namespace AIStudio.Provider.SelfHosted; public readonly record struct ModelsResponse(string? Object, Model[]? Data); -public readonly record struct Model(string Id, string? Object, string? OwnedBy); \ No newline at end of file +public readonly record struct Model(string Id, string? Object, string? OwnedBy, ModelArchitecture? Architecture); + +public readonly record struct ModelArchitecture(string[]? InputModalities, string[]? OutputModalities); \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index af0c56a4..723a5ab3 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -95,7 +95,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide switch (host) { case Host.LLAMA_CPP: - return await this.LoadLlamaCppTextModels(token, apiKeyProvisional); + return await this.LoadLlamaCppTextModels(["embed"], [], token, apiKeyProvisional); case Host.LM_STUDIO: case Host.OLLAMA: @@ -208,7 +208,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide if (host is not Host.LLAMA_CPP || !chatModel.IsSystemModel) return chatModel; - var modelLoadResult = await this.LoadLlamaCppTextModels(token); + var modelLoadResult = await this.LoadLlamaCppTextModels(["embed"], [], token); if (!modelLoadResult.Success) return chatModel; @@ -216,20 +216,36 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide .Where(model => !model.IsSystemModel && !string.IsNullOrWhiteSpace(model.Id)) .ToList(); + if (modelLoadResult.Models.All(model => !model.IsSystemModel) && availableModels.Count is 0) + { + LOGGER.LogError("The llama.cpp provider '{ProviderInstanceName}' does not offer a usable text model. Please check your provider settings.", this.InstanceName); + throw new ProviderRequestException( + ProviderRequestFailureReason.NONE, + string.Format( + TB("The llama.cpp provider '{0}' does not offer a usable text model. Please check your provider settings."), + this.InstanceName)); + } + if (availableModels.Count is 1) return availableModels[0]; if (availableModels.Count > 1) + { + LOGGER.LogError( + "The llama.cpp provider '{ProviderInstanceName}' offers {ModelCount} models, but the configured model is the legacy system placeholder. The provider settings must be updated to select a specific model.", + this.InstanceName, + availableModels.Count); throw new ProviderRequestException( ProviderRequestFailureReason.NONE, string.Format( TB("The llama.cpp provider '{0}' offers multiple models. Please open the provider settings and select the model to use."), this.InstanceName)); + } return chatModel; } - private async Task LoadLlamaCppTextModels(CancellationToken token, string? apiKeyProvisional = null) + private async Task LoadLlamaCppTextModels(string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null) { var secretKey = await this.GetModelLoadingSecretKey(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, true); @@ -253,12 +269,19 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide try { var modelResponse = JsonSerializer.Deserialize(responseBody, JSON_SERIALIZER_OPTIONS); - var models = modelResponse.Data? + var responseModels = modelResponse.Data? .Where(model => !string.IsNullOrWhiteSpace(model.Id)) - .Select(model => new Provider.Model(model.Id, null)) .ToList() ?? []; - return models.Count is 0 ? LlamaCppLegacyModelResult() : SuccessfulModelLoadResult(models); + if (responseModels.Count is 0) + return LlamaCppLegacyModelResult(); + + var models = responseModels + .Where(model => IsMatchingLlamaCppTextModel(model, ignorePhrases, filterPhrases)) + .Select(model => new Provider.Model(model.Id, null)) + .ToList(); + + return SuccessfulModelLoadResult(models); } catch (JsonException e) { @@ -279,6 +302,25 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide } } + private static bool IsMatchingLlamaCppTextModel(Model model, string[] ignorePhrases, string[] filterPhrases) + { + if (string.IsNullOrWhiteSpace(model.Id)) + return false; + + if (ignorePhrases.Any(ignorePhrase => model.Id.Contains(ignorePhrase, StringComparison.InvariantCultureIgnoreCase))) + return false; + + if (!filterPhrases.All(filter => model.Id.Contains(filter, StringComparison.InvariantCultureIgnoreCase))) + return false; + + var outputModalities = model.Architecture?.OutputModalities; + if (outputModalities is { Length: > 0 } && + !outputModalities.Any(modality => string.Equals(modality, "text", StringComparison.OrdinalIgnoreCase))) + return false; + + return true; + } + private static ModelLoadResult LlamaCppLegacyModelResult() { return ModelLoadResult.FromModels([ AIStudio.Provider.Model.SYSTEM_MODEL ]);