mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-18 23:38:00 +00:00
Sorted every model list by what a model is made for (#984)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
1 parent
e42277beba
commit
f9c6075c50
36 files changed
+947
-333
No files matched your search
@@ -76,40 +76,8 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
||||
/// <inheritdoc />
|
||||
public override async Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
var additionalModels = new[]
|
||||
{
|
||||
new Model("qwq-plus", "QwQ plus"), // reasoning model
|
||||
new Model("qwen-max-latest", "Qwen-Max (Latest)"),
|
||||
new Model("qwen-plus-latest", "Qwen-Plus (Latest)"),
|
||||
new Model("qwen-turbo-latest", "Qwen-Turbo (Latest)"),
|
||||
new Model("qvq-max", "QVQ Max"), // visual reasoning model
|
||||
new Model("qvq-max-latest", "QVQ Max (Latest)"), // visual reasoning model
|
||||
new Model("qwen-vl-max", "Qwen-VL Max"), // text generation model that can understand and process images
|
||||
new Model("qwen-vl-plus", "Qwen-VL Plus"), // text generation model that can understand and process images
|
||||
new Model("qwen-mt-plus", "Qwen-MT Plus"), // machine translation
|
||||
new Model("qwen-mt-turbo", "Qwen-MT Turbo"), // machine translation
|
||||
|
||||
//Open source
|
||||
new Model("qwen2.5-14b-instruct-1m", "Qwen2.5 14b 1m context"),
|
||||
new Model("qwen2.5-7b-instruct-1m", "Qwen2.5 7b 1m context"),
|
||||
new Model("qwen2.5-72b-instruct", "Qwen2.5 72b"),
|
||||
new Model("qwen2.5-32b-instruct", "Qwen2.5 32b"),
|
||||
new Model("qwen2.5-14b-instruct", "Qwen2.5 14b"),
|
||||
new Model("qwen2.5-7b-instruct", "Qwen2.5 7b"),
|
||||
new Model("qwen2.5-omni-7b", "Qwen2.5-Omni 7b"), // omni-modal understanding and generation model
|
||||
new Model("qwen2.5-vl-72b-instruct", "Qwen2.5-VL 72b"),
|
||||
new Model("qwen2.5-vl-32b-instruct", "Qwen2.5-VL 32b"),
|
||||
new Model("qwen2.5-vl-7b-instruct", "Qwen2.5-VL 7b"),
|
||||
new Model("qwen2.5-vl-3b-instruct", "Qwen2.5-VL 3b"),
|
||||
};
|
||||
|
||||
var result = await this.LoadModels(["q"], SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token);
|
||||
return result with
|
||||
{
|
||||
// The API is the authority: when it reports a model we also keep as a fallback above,
|
||||
// its entry comes first and the fallback is dropped.
|
||||
Models = [..result.Models.Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
|
||||
};
|
||||
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token);
|
||||
return result with { Models = [..result.Models.Where(model => model.IsChatModel(this.Provider)).OrderBy(x => x.Id)] };
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -121,19 +89,8 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
||||
/// <inheritdoc />
|
||||
public override async Task<ModelLoadResult> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
|
||||
var additionalModels = new[]
|
||||
{
|
||||
new Model("text-embedding-v3", "text-embedding-v3"),
|
||||
};
|
||||
|
||||
var result = await this.LoadModels(["text-embedding-"], SecretStoreType.EMBEDDING_PROVIDER, apiKeyProvisional, token);
|
||||
return result with
|
||||
{
|
||||
// The API is the authority: when it reports a model we also keep as a fallback above,
|
||||
// its entry comes first and the fallback is dropped.
|
||||
Models = [..result.Models.Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
|
||||
};
|
||||
var result = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, apiKeyProvisional, token);
|
||||
return result with { Models = [..result.Models.Where(model => model.IsEmbeddingModel(this.Provider)).OrderBy(x => x.Id)] };
|
||||
}
|
||||
|
||||
#region Overrides of BaseProvider
|
||||
@@ -148,12 +105,23 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
||||
|
||||
#endregion
|
||||
|
||||
private Task<ModelLoadResult> LoadModels(string[] prefixes, SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token)
|
||||
/// <summary>
|
||||
/// Reads Model Studio's catalog, whole.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It used to be read through a prefix per list -- a single "q" for the models to talk to, and
|
||||
/// "text-embedding-" for the ones which answer in vectors. Neither survived what Model Studio
|
||||
/// became: the letter also brings qwen-image, qwen-tts, qwen3-asr and qwen-vl-ocr into the chat
|
||||
/// list, while it locks out DeepSeek, Kimi, GLM and MiniMax, which Alibaba serves through this
|
||||
/// very endpoint. A name has never been a statement about what a model is for; the callers ask
|
||||
/// the registry instead.
|
||||
/// </remarks>
|
||||
private Task<ModelLoadResult> LoadModels(SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
return this.LoadModelsResponse<ModelsResponse>(
|
||||
storeType,
|
||||
"models",
|
||||
modelResponse => modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture))),
|
||||
modelResponse => modelResponse.Data,
|
||||
apiKeyProvisional, token: token);
|
||||
}
|
||||
}
|
||||
@@ -213,9 +213,14 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
|
||||
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token);
|
||||
return result with
|
||||
{
|
||||
//
|
||||
// The API is the authority: when it reports a model we also keep as a fallback above,
|
||||
// its entry comes first and the fallback is dropped.
|
||||
Models = [..result.Models.Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
|
||||
// its entry comes first and the fallback is dropped. What it reports is asked about
|
||||
// first, though -- the route says nothing about what a model is made for, and Claude
|
||||
// has not always been only something to talk to. The six above skip that question
|
||||
// because they are not a catalog: every one of them was picked by hand.
|
||||
//
|
||||
Models = [..result.Models.Where(model => model.IsChatModel(this.Provider)).Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,16 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, new Uri(
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// The one transcription list which stays a plain list, where GWDG and Mistral ask their
|
||||
/// endpoint first. There is nothing to ask here: HasModelLoadingCapability is false and every
|
||||
/// other method above answers with nothing, which is why a chat model at Fireworks has to be
|
||||
/// typed in by hand. A list is all there is.
|
||||
///
|
||||
/// The commented-out entry is no oversight either. The documentation names Whisper v3 Turbo,
|
||||
/// and trying it does not work -- which is worth keeping written down, so that nobody adds it
|
||||
/// back and finds out the same way again.
|
||||
/// </remarks>
|
||||
public override Task<ModelLoadResult> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
// Source: https://docs.fireworks.ai/api-reference/audio-transcriptions#param-model
|
||||
|
||||
@@ -18,6 +18,12 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht
|
||||
new("qwen3-embedding-4b", "Qwen3 Embedding 4B"),
|
||||
];
|
||||
|
||||
// Source: https://docs.hpc.gwdg.de/services/saia/index.html#voice-to-text
|
||||
private static readonly Model[] KNOWN_TRANSCRIPTION_MODELS =
|
||||
[
|
||||
new("whisper-large-v2", "Whisper v2 Large"),
|
||||
];
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -123,13 +129,27 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<ModelLoadResult> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
/// <remarks>
|
||||
/// Built the same way as the embedding models above, and for the same reason: SAIA answers the
|
||||
/// models endpoint with its chat models only, so this comes back empty and the documented list
|
||||
/// stands in. Asking first costs nothing and means a speech model appearing in that answer one
|
||||
/// day shows up on its own, rather than waiting for somebody to notice and edit this file. A
|
||||
/// failed request is passed on unchanged, so a wrong API key stays visible as such.
|
||||
/// </remarks>
|
||||
public override async Task<ModelLoadResult> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
// Source: https://docs.hpc.gwdg.de/services/saia/index.html#voice-to-text
|
||||
return Task.FromResult(ModelLoadResult.FromModels(
|
||||
[
|
||||
new Model("whisper-large-v2", "Whisper v2 Large"),
|
||||
]));
|
||||
var result = await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, apiKeyProvisional, token);
|
||||
if (!result.Success)
|
||||
return result;
|
||||
|
||||
var transcriptionModels = result.Models.Where(model => model.IsTranscriptionModel(this.Provider)).ToList();
|
||||
if (transcriptionModels.Count is 0)
|
||||
return ModelLoadResult.FromModels(KNOWN_TRANSCRIPTION_MODELS);
|
||||
|
||||
return result with
|
||||
{
|
||||
Models = [..transcriptionModels]
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -172,11 +172,15 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, new Uri("https
|
||||
// Asking what a model is made for, rather than only ruling out the embedding ones.
|
||||
// Google names everything after the chat model it grew out of, so the catalog is
|
||||
// full of names which look like something to talk to and are not: the image models,
|
||||
// and the computer use model whose API refuses a request without its tool.
|
||||
// the computer use model whose API refuses a request without its tool, and the live
|
||||
// line which wants a connection held open in both directions.
|
||||
//
|
||||
..result.Models.Where(model =>
|
||||
model.Id.StartsWith("gemini-", StringComparison.OrdinalIgnoreCase) &&
|
||||
model.IsChatModel(this.Provider))
|
||||
// The question used to be asked of names beginning with "gemini" alone, and that
|
||||
// cost the two Gemma models Google serves on this very route. What the prefix kept
|
||||
// out besides them -- Lyria, Imagen, Veo, the research and coding agents, AQA --
|
||||
// is kept out by a rule now, where the reason is written down.
|
||||
//
|
||||
..result.Models.Where(model => model.IsChatModel(this.Provider))
|
||||
.Select(this.WithDisplayNameFallback)
|
||||
]
|
||||
};
|
||||
|
||||
@@ -421,17 +421,6 @@ public static class LLMProvidersExtensions
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsEmbeddingModelProvidedManually(this LLMProviders provider, Host host) => provider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => host is not Host.LM_STUDIO,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsTranscriptionModelProvidedManually(this LLMProviders provider, Host host) => provider switch
|
||||
{
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the model selection should be completely hidden for LLM providers.
|
||||
/// This is the case when the host does not support model selection.
|
||||
|
||||
@@ -93,12 +93,13 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
|
||||
{
|
||||
Models =
|
||||
[
|
||||
// Codestral is a fill-in-the-middle model, which we cannot use for chats. That is
|
||||
// specific to Mistral's catalog, which is why it is not part of the shared model
|
||||
// kind detection:
|
||||
..modelResponse.Models.Where(n =>
|
||||
!n.Id.StartsWith("code", StringComparison.OrdinalIgnoreCase) &&
|
||||
n.IsChatModel(this.Provider))
|
||||
//
|
||||
// Codestral is a fill-in-the-middle model, which we cannot use for chats. Its own
|
||||
// family says so now, bound to this provider, so the word "code" no longer has to
|
||||
// be tested for here -- and testing for it never reached mistral-code-fim-latest,
|
||||
// which does the same job under a name that begins differently.
|
||||
//
|
||||
..modelResponse.Models.Where(n => n.IsChatModel(this.Provider))
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -123,13 +124,16 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<ModelLoadResult> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
public override async Task<ModelLoadResult> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
// Source: https://docs.mistral.ai/capabilities/audio_transcription
|
||||
return Task.FromResult(ModelLoadResult.FromModels(
|
||||
[
|
||||
new Provider.Model("voxtral-mini-latest", "Voxtral Mini Latest"),
|
||||
]));
|
||||
var modelResponse = await this.LoadModelList(SecretStoreType.TRANSCRIPTION_PROVIDER, apiKeyProvisional, token);
|
||||
if (!modelResponse.Success)
|
||||
return modelResponse;
|
||||
|
||||
return modelResponse with
|
||||
{
|
||||
Models = [..modelResponse.Models.Where(n => n.IsTranscriptionModel(this.Provider))]
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -144,4 +148,4 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
|
||||
listingFactory: modelResponse => modelResponse.Data.Select(n => ModelListing.For(n.Id, n.ContextWindowTokens)),
|
||||
token: token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,16 @@ public enum ModelKind
|
||||
/// </summary>
|
||||
VIDEO_GENERATION,
|
||||
|
||||
/// <summary>
|
||||
/// The model composes music.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Audio comes out of it, but not speech: instruments, arrangement, and in Lyria's case singing
|
||||
/// with lyrics. Neither the speech synthesis list nor any other one fits, and a chat request to
|
||||
/// such a model gets nothing back that reads like an answer.
|
||||
/// </remarks>
|
||||
MUSIC_GENERATION,
|
||||
|
||||
/// <summary>
|
||||
/// The model transcribes audio into text.
|
||||
/// </summary>
|
||||
@@ -86,6 +96,27 @@ public enum ModelKind
|
||||
/// </remarks>
|
||||
COMPUTER_USE,
|
||||
|
||||
/// <summary>
|
||||
/// The model runs an errand of its own instead of answering.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// One request starts a loop which plans, calls tools, runs code and reads the web, and it can
|
||||
/// take minutes. Google serves its research and coding agents this way, through an API of their
|
||||
/// own which a chat request never reaches. Note that the name alone decides nothing here: what
|
||||
/// Perplexity calls deep research is an ordinary chat model with web search.
|
||||
/// </remarks>
|
||||
AGENT,
|
||||
|
||||
/// <summary>
|
||||
/// The model answers a question out of sources handed to it, and says where the answer came from.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Built for retrieval rather than for conversation: it is given passages along with the
|
||||
/// question, and returns the answer, the citations, and an estimate of whether the question
|
||||
/// could be answered from them at all. Reached through a route of its own.
|
||||
/// </remarks>
|
||||
GROUNDED_ANSWERING,
|
||||
|
||||
/// <summary>
|
||||
/// The model extracts text from images or scanned documents.
|
||||
/// </summary>
|
||||
|
||||
@@ -137,6 +137,11 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
/// 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.
|
||||
///
|
||||
/// Nothing is filtered either, for the same reason. The route is the statement: OpenRouter
|
||||
/// serves these to embed with, which is more than a name can say. Asking the registry on top
|
||||
/// could only drop a model whose name we do not recognize -- and where the two disagree, the
|
||||
/// answer is a rule in Models/, not a model missing from this list.
|
||||
/// </remarks>
|
||||
/// <param name="apiKeyProvisional">An API key which is not stored yet.</param>
|
||||
/// <param name="token">The cancellation token to use.</param>
|
||||
@@ -156,4 +161,4 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
},
|
||||
token: token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,12 +97,16 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
switch (host)
|
||||
{
|
||||
case Host.LLAMA_CPP:
|
||||
return await this.LoadLlamaCppTextModels(["embed"], [], apiKeyProvisional, token);
|
||||
|
||||
return await this.LoadLlamaCppTextModels(apiKeyProvisional, token);
|
||||
|
||||
case Host.LM_STUDIO:
|
||||
case Host.OLLAMA:
|
||||
case Host.VLLM:
|
||||
return await this.LoadModels( SecretStoreType.LLM_PROVIDER, ["embed"], [], apiKeyProvisional, token);
|
||||
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token);
|
||||
return result with
|
||||
{
|
||||
Models = [..result.Models.Where(model => model.IsChatModel(this.Provider))]
|
||||
};
|
||||
}
|
||||
|
||||
return ModelLoadResult.FromModels([]);
|
||||
@@ -129,14 +133,18 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
case Host.LM_STUDIO:
|
||||
case Host.OLLAMA:
|
||||
case Host.VLLM:
|
||||
return await this.LoadModels( SecretStoreType.EMBEDDING_PROVIDER, [], ["embed"], apiKeyProvisional, token);
|
||||
var result = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, apiKeyProvisional, token);
|
||||
return result with
|
||||
{
|
||||
Models = [..result.Models.Where(model => model.IsEmbeddingModel(this.Provider))]
|
||||
};
|
||||
}
|
||||
|
||||
return ModelLoadResult.FromModels([]);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOGGER.LogError($"Failed to load text models from self-hosted provider: {e.Message}");
|
||||
LOGGER.LogError($"Failed to load embedding models from self-hosted provider: {e.Message}");
|
||||
return ModelLoadResult.Failure(ModelLoadFailureReason.UNKNOWN, e.Message);
|
||||
}
|
||||
}
|
||||
@@ -154,10 +162,22 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
new Provider.Model("loaded-model", TB("Model as configured by whisper.cpp")),
|
||||
]);
|
||||
|
||||
//
|
||||
// These two answer the models endpoint with everything they serve, and nothing in
|
||||
// that answer says which of them listens. Asking what each model is made for is the
|
||||
// only thing standing between this list and every chat and embedding model of the
|
||||
// installation, which is what it used to hold. An engine running no speech model at
|
||||
// all therefore offers nothing here, and says so, rather than offering models which
|
||||
// would fail the moment audio reaches them.
|
||||
//
|
||||
case Host.OLLAMA:
|
||||
case Host.VLLM:
|
||||
return await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, [], [], apiKeyProvisional, token);
|
||||
|
||||
var result = await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, apiKeyProvisional, token);
|
||||
return result with
|
||||
{
|
||||
Models = [..result.Models.Where(model => model.IsTranscriptionModel(this.Provider))]
|
||||
};
|
||||
|
||||
default:
|
||||
return ModelLoadResult.FromModels([]);
|
||||
}
|
||||
@@ -171,7 +191,21 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<ModelLoadResult> LoadModels(SecretStoreType storeType, string[] ignorePhrases, string[] filterPhrases, string? apiKeyProvisional, CancellationToken token)
|
||||
/// <summary>
|
||||
/// Everything the engine lists, in the order it listed it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// What kind of model each of these is stays unanswered here. It used to be answered right in
|
||||
/// this method, by looking for the word "embed" in the name: the text models were the ones
|
||||
/// without it, the embedding models the ones with it. That reading lost bge-m3 and all-minilm,
|
||||
/// which say what they are through another word, and handed them to the chat list instead. The
|
||||
/// callers ask the shared model kind detection now, the way every other provider does.
|
||||
/// </remarks>
|
||||
/// <param name="storeType">Which key to send along.</param>
|
||||
/// <param name="apiKeyProvisional">A key from a dialog which has not stored it yet.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The models the engine named, unsorted and unfiltered.</returns>
|
||||
private async Task<ModelLoadResult> LoadModels(SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
var secretKey = await this.GetModelLoadingSecretKey(storeType, apiKeyProvisional, isTryingSecret: true);
|
||||
|
||||
@@ -211,10 +245,8 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
//
|
||||
ListedModels.Shared.Report(this.ConfiguredProviderId, ListingsOf(models));
|
||||
|
||||
return SuccessfulModelLoadResult(models.
|
||||
Where(model => !string.IsNullOrWhiteSpace(model.Id) &&
|
||||
!ignorePhrases.Any(ignorePhrase => model.Id.Contains(ignorePhrase, StringComparison.InvariantCulture)) &&
|
||||
filterPhrases.All( filter => model.Id.Contains(filter, StringComparison.InvariantCulture)))
|
||||
return SuccessfulModelLoadResult(models
|
||||
.Where(model => !string.IsNullOrWhiteSpace(model.Id))
|
||||
.Select(n => new Provider.Model(n.Id, null)));
|
||||
}
|
||||
catch (Exception e) when (this.IsTimeoutException(e, token))
|
||||
@@ -229,7 +261,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(["embed"], [], null, token);
|
||||
var modelLoadResult = await this.LoadLlamaCppTextModels(null, token);
|
||||
if (!modelLoadResult.Success)
|
||||
return chatModel;
|
||||
|
||||
@@ -266,7 +298,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
private async Task<ModelLoadResult> LoadLlamaCppTextModels(string[] ignorePhrases, string[] filterPhrases, string? apiKeyProvisional, CancellationToken token)
|
||||
private async Task<ModelLoadResult> LoadLlamaCppTextModels(string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
var secretKey = await this.GetModelLoadingSecretKey(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, true);
|
||||
|
||||
@@ -298,7 +330,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
return LlamaCppLegacyModelResult();
|
||||
|
||||
var models = responseModels
|
||||
.Where(model => IsMatchingLlamaCppTextModel(model, ignorePhrases, filterPhrases))
|
||||
.Where(this.IsMatchingLlamaCppTextModel)
|
||||
.Select(model => new Provider.Model(model.Id, null))
|
||||
.ToList();
|
||||
|
||||
@@ -330,15 +362,23 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
/// <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)
|
||||
/// <summary>
|
||||
/// Whether this is a model somebody can chat with, as far as llama.cpp and the rules say.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Two sources, and both have to agree. What a model is made for comes from the shared rules,
|
||||
/// the same answer the other engines get. What the running build of it puts out comes from
|
||||
/// llama.cpp itself, which states the modalities on this route: an engine serving a model that
|
||||
/// answers in something other than text knows that before any rule about the name could.
|
||||
/// </remarks>
|
||||
/// <param name="model">The model as llama.cpp listed it.</param>
|
||||
/// <returns>True when both agree that it answers a chat in text.</returns>
|
||||
private bool IsMatchingLlamaCppTextModel(Model model)
|
||||
{
|
||||
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)))
|
||||
if (!new Provider.Model(model.Id, null).IsChatModel(this.Provider))
|
||||
return false;
|
||||
|
||||
var outputModalities = model.Architecture?.OutputModalities;
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, new Uri("https://
|
||||
/// <inheritdoc />
|
||||
public override async Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, ["grok-"], apiKeyProvisional, token);
|
||||
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token);
|
||||
return result with
|
||||
{
|
||||
//
|
||||
@@ -108,19 +108,24 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, new Uri("https://
|
||||
|
||||
#endregion
|
||||
|
||||
private Task<ModelLoadResult> LoadModels(SecretStoreType storeType, string[] prefixes, string? apiKeyProvisional, CancellationToken token)
|
||||
/// <summary>
|
||||
/// Reads the xAI catalog, whole.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every name in it begins with "grok", which is why the prefix this used to filter by never
|
||||
/// took anything away -- and why it said nothing either. What it did carry was Grok 2, appended
|
||||
/// to every answer whether xAI still served it or not. It does not: the catalog has moved on to
|
||||
/// Grok 4, and an entry nobody can talk to is worse than one missing from the list.
|
||||
///
|
||||
/// What the catalog does hold besides the chat models is five names which draw or film. The
|
||||
/// caller asks the registry about those.
|
||||
/// </remarks>
|
||||
private Task<ModelLoadResult> LoadModels(SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
return this.LoadModelsResponse<ModelsResponse>(
|
||||
storeType,
|
||||
"models",
|
||||
modelResponse => modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture)))
|
||||
.Concat([
|
||||
new Model
|
||||
{
|
||||
Id = "grok-2-latest",
|
||||
DisplayName = "Grok 2.0 (latest)",
|
||||
}
|
||||
]),
|
||||
modelResponse => modelResponse.Data,
|
||||
apiKeyProvisional, token: token);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user