Added support for filtering

This commit is contained in:
Thorsten Sommer 2026-06-11 15:40:53 +02:00
parent ec7c83adfa
commit d58f093124
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
2 changed files with 51 additions and 7 deletions

View File

@ -2,4 +2,6 @@ namespace AIStudio.Provider.SelfHosted;
public readonly record struct ModelsResponse(string? Object, Model[]? Data); public readonly record struct ModelsResponse(string? Object, Model[]? Data);
public readonly record struct Model(string Id, string? Object, string? OwnedBy); public readonly record struct Model(string Id, string? Object, string? OwnedBy, ModelArchitecture? Architecture);
public readonly record struct ModelArchitecture(string[]? InputModalities, string[]? OutputModalities);

View File

@ -95,7 +95,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
switch (host) switch (host)
{ {
case Host.LLAMA_CPP: case Host.LLAMA_CPP:
return await this.LoadLlamaCppTextModels(token, apiKeyProvisional); return await this.LoadLlamaCppTextModels(["embed"], [], token, apiKeyProvisional);
case Host.LM_STUDIO: case Host.LM_STUDIO:
case Host.OLLAMA: 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) if (host is not Host.LLAMA_CPP || !chatModel.IsSystemModel)
return chatModel; return chatModel;
var modelLoadResult = await this.LoadLlamaCppTextModels(token); var modelLoadResult = await this.LoadLlamaCppTextModels(["embed"], [], token);
if (!modelLoadResult.Success) if (!modelLoadResult.Success)
return chatModel; return chatModel;
@ -216,20 +216,36 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
.Where(model => !model.IsSystemModel && !string.IsNullOrWhiteSpace(model.Id)) .Where(model => !model.IsSystemModel && !string.IsNullOrWhiteSpace(model.Id))
.ToList(); .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) if (availableModels.Count is 1)
return availableModels[0]; return availableModels[0];
if (availableModels.Count > 1) 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( throw new ProviderRequestException(
ProviderRequestFailureReason.NONE, ProviderRequestFailureReason.NONE,
string.Format( string.Format(
TB("The llama.cpp provider '{0}' offers multiple models. Please open the provider settings and select the model to use."), TB("The llama.cpp provider '{0}' offers multiple models. Please open the provider settings and select the model to use."),
this.InstanceName)); this.InstanceName));
}
return chatModel; return chatModel;
} }
private async Task<ModelLoadResult> LoadLlamaCppTextModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<ModelLoadResult> LoadLlamaCppTextModels(string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = await this.GetModelLoadingSecretKey(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, true); var secretKey = await this.GetModelLoadingSecretKey(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, true);
@ -253,12 +269,19 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
try try
{ {
var modelResponse = JsonSerializer.Deserialize<ModelsResponse>(responseBody, JSON_SERIALIZER_OPTIONS); var modelResponse = JsonSerializer.Deserialize<ModelsResponse>(responseBody, JSON_SERIALIZER_OPTIONS);
var models = modelResponse.Data? var responseModels = modelResponse.Data?
.Where(model => !string.IsNullOrWhiteSpace(model.Id)) .Where(model => !string.IsNullOrWhiteSpace(model.Id))
.Select(model => new Provider.Model(model.Id, null))
.ToList() ?? []; .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) 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() private static ModelLoadResult LlamaCppLegacyModelResult()
{ {
return ModelLoadResult.FromModels([ AIStudio.Provider.Model.SYSTEM_MODEL ]); return ModelLoadResult.FromModels([ AIStudio.Provider.Model.SYSTEM_MODEL ]);