mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-20 18:13:37 +00:00
Sorted every model list by what a model is made for (#984)
Some checks are pending
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
Some checks are pending
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:
parent
e42277beba
commit
f9c6075c50
@ -6547,9 +6547,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1356621346"] = "Cr
|
|||||||
-- Failed to validate the selected tokenizer. Please try again.
|
-- Failed to validate the selected tokenizer. Please try again.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1384494471"] = "Failed to validate the selected tokenizer. Please try again."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1384494471"] = "Failed to validate the selected tokenizer. Please try again."
|
||||||
|
|
||||||
-- Please enter an embedding model name.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1661085403"] = "Please enter an embedding model name."
|
|
||||||
|
|
||||||
-- Hostname
|
-- Hostname
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Hostname"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Hostname"
|
||||||
|
|
||||||
@ -6604,9 +6601,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "No
|
|||||||
-- Instance Name
|
-- Instance Name
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name"
|
||||||
|
|
||||||
-- Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually."
|
|
||||||
|
|
||||||
-- Token limit
|
-- Token limit
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2961294165"] = "Token limit"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2961294165"] = "Token limit"
|
||||||
|
|
||||||
@ -6616,6 +6610,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3316544737"] = "Pl
|
|||||||
-- Show Expert Settings
|
-- Show Expert Settings
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
||||||
|
|
||||||
|
-- This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3571276758"] = "This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again."
|
||||||
|
|
||||||
-- How many chunks are sent to the embedding provider at once. The default is 1.
|
-- How many chunks are sent to the embedding provider at once. The default is 1.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3780233303"] = "How many chunks are sent to the embedding provider at once. The default is 1."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3780233303"] = "How many chunks are sent to the embedding provider at once. The default is 1."
|
||||||
|
|
||||||
@ -6637,6 +6634,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Hos
|
|||||||
-- Please enter an embedding batch size greater than 0.
|
-- Please enter an embedding batch size greater than 0.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T840259907"] = "Please enter an embedding batch size greater than 0."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T840259907"] = "Please enter an embedding batch size greater than 0."
|
||||||
|
|
||||||
|
-- Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T859645108"] = "Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here."
|
||||||
|
|
||||||
-- Provider
|
-- Provider
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Provider"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Provider"
|
||||||
|
|
||||||
@ -8716,9 +8716,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1324664716"] =
|
|||||||
-- Create account
|
-- Create account
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Create account"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Create account"
|
||||||
|
|
||||||
-- Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1381635232"] = "Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually."
|
|
||||||
|
|
||||||
-- Hostname
|
-- Hostname
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
|
||||||
|
|
||||||
@ -8752,9 +8749,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] =
|
|||||||
-- Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.
|
-- Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3397943774"] = "Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3397943774"] = "Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting."
|
||||||
|
|
||||||
-- Please enter a transcription model name.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Please enter a transcription model name."
|
|
||||||
|
|
||||||
-- This host uses the model configured at the provider level. No model selection is available.
|
-- This host uses the model configured at the provider level. No model selection is available.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||||
|
|
||||||
|
|||||||
@ -85,48 +85,42 @@
|
|||||||
|
|
||||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||||
@if (this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost))
|
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
||||||
|
@T("Load")
|
||||||
|
</MudButton>
|
||||||
|
@if (this.availableModels.Count is 0)
|
||||||
{
|
{
|
||||||
<MudTextField
|
<MudText Typo="Typo.body1">
|
||||||
T="string"
|
@T("No models loaded or available.")
|
||||||
@bind-Text="@this.dataManuallyModel"
|
</MudText>
|
||||||
Label="@T("Model")"
|
|
||||||
Class="mb-3"
|
|
||||||
Adornment="Adornment.Start"
|
|
||||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
|
||||||
AdornmentColor="Color.Info"
|
|
||||||
Disabled="@this.IsEnterpriseConfiguration"
|
|
||||||
Validation="@this.ValidateManuallyModel"
|
|
||||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
|
||||||
HelperText="@T("Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.")"/>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
|
||||||
@T("Load")
|
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||||
</MudButton>
|
AdornmentColor="Color.Info" Adornment="Adornment.Start"
|
||||||
@if (this.availableModels.Count is 0)
|
Validation="@this.providerValidation.ValidatingModel">
|
||||||
{
|
@foreach (var model in this.availableModels)
|
||||||
<MudText Typo="Typo.body1">
|
{
|
||||||
@T("No models loaded or available.")
|
<MudSelectItem Value="@model">
|
||||||
</MudText>
|
@model
|
||||||
}
|
</MudSelectItem>
|
||||||
else
|
}
|
||||||
{
|
</MudSelect>
|
||||||
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
|
|
||||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
|
||||||
AdornmentColor="Color.Info" Adornment="Adornment.Start"
|
|
||||||
Validation="@this.providerValidation.ValidatingModel">
|
|
||||||
@foreach (var model in this.availableModels)
|
|
||||||
{
|
|
||||||
<MudSelectItem Value="@model">
|
|
||||||
@model
|
|
||||||
</MudSelectItem>
|
|
||||||
}
|
|
||||||
</MudSelect>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
@if (this.dataConfiguredModelIsNotOffered)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Class="mt-3">
|
||||||
|
@T("This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again.")
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
@if (this.ServerNamedNoEmbeddingModel)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Class="mt-3">
|
||||||
|
@T("Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here.")
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Error" Class="mt-3">
|
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||||
|
|||||||
@ -133,10 +133,11 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
private string[] dataIssues = [];
|
private string[] dataIssues = [];
|
||||||
private string dataAPIKey = string.Empty;
|
private string dataAPIKey = string.Empty;
|
||||||
private bool dataHadStoredAPIKeyOnLoad;
|
private bool dataHadStoredAPIKeyOnLoad;
|
||||||
private string dataManuallyModel = string.Empty;
|
|
||||||
private string dataAPIKeyStorageIssue = string.Empty;
|
private string dataAPIKeyStorageIssue = string.Empty;
|
||||||
private string dataEditingPreviousInstanceName = string.Empty;
|
private string dataEditingPreviousInstanceName = string.Empty;
|
||||||
private string dataLoadingModelsIssue = string.Empty;
|
private string dataLoadingModelsIssue = string.Empty;
|
||||||
|
private bool dataConfiguredModelIsNotOffered;
|
||||||
|
private bool dataServerWasAskedForItsModels;
|
||||||
private string dataFilePath = string.Empty;
|
private string dataFilePath = string.Empty;
|
||||||
private string dataTokenizerFingerprint = string.Empty;
|
private string dataTokenizerFingerprint = string.Empty;
|
||||||
private string dataCustomTokenizerValidationIssue = string.Empty;
|
private string dataCustomTokenizerValidationIssue = string.Empty;
|
||||||
@ -162,7 +163,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||||
GetHost = () => this.DataHost,
|
GetHost = () => this.DataHost,
|
||||||
IsModelProvidedManually = () => this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost),
|
|
||||||
GetCustomTokenizerValidationIssue = () => this.dataCustomTokenizerValidationIssue,
|
GetCustomTokenizerValidationIssue = () => this.dataCustomTokenizerValidationIssue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -170,24 +170,13 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
private EmbeddingProvider CreateEmbeddingProviderSettings()
|
private EmbeddingProvider CreateEmbeddingProviderSettings()
|
||||||
{
|
{
|
||||||
var cleanedHostname = this.DataHostname.Trim();
|
var cleanedHostname = this.DataHostname.Trim();
|
||||||
Model model = default;
|
|
||||||
if(this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
|
||||||
{
|
|
||||||
if (this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost))
|
|
||||||
model = new Model(this.dataManuallyModel, null);
|
|
||||||
else if (this.DataHost is Host.LM_STUDIO)
|
|
||||||
model = this.DataModel;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
model = this.DataModel;
|
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Num = this.DataNum,
|
Num = this.DataNum,
|
||||||
Id = this.DataId,
|
Id = this.DataId,
|
||||||
Name = this.DataName,
|
Name = this.DataName,
|
||||||
UsedLLMProvider = this.DataLLMProvider,
|
UsedLLMProvider = this.DataLLMProvider,
|
||||||
Model = model,
|
Model = this.DataModel,
|
||||||
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
||||||
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
|
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
|
||||||
Host = this.DataHost,
|
Host = this.DataHost,
|
||||||
@ -225,10 +214,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
|| this.DataTokenLimit != EmbeddingProvider.DEFAULT_TOKEN_LIMIT
|
|| this.DataTokenLimit != EmbeddingProvider.DEFAULT_TOKEN_LIMIT
|
||||||
|| this.DataEmbeddingBatchSize != EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
|
|| this.DataEmbeddingBatchSize != EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||||
|
|
||||||
// When using self-hosted embedding, we must copy the model name:
|
|
||||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
|
||||||
this.dataManuallyModel = this.DataModel.Id;
|
|
||||||
|
|
||||||
// Load the API key. A self-hosted server may well need one: LM Studio can ask for a
|
// Load the API key. A self-hosted server may well need one: LM Studio can ask for a
|
||||||
// token of its own, and any of these servers can sit behind an authenticating proxy.
|
// token of its own, and any of these servers can sit behind an authenticating proxy.
|
||||||
// So we try for every host and treat a missing key as the normal case (isTrying).
|
// So we try for every host and treat a missing key as the normal case (isTrying).
|
||||||
@ -358,14 +343,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
|
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ValidateManuallyModel(string manuallyModel)
|
|
||||||
{
|
|
||||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel))
|
|
||||||
return T("Please enter an embedding model name.");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? ValidateTokenLimit(int tokenLimit)
|
private string? ValidateTokenLimit(int tokenLimit)
|
||||||
{
|
{
|
||||||
if (tokenLimit < 1)
|
if (tokenLimit < 1)
|
||||||
@ -516,9 +493,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
// When the host changes, reset the model selection state:
|
// When the host changes, reset the model selection state:
|
||||||
this.DataHost = selectedHost;
|
this.DataHost = selectedHost;
|
||||||
this.DataModel = default;
|
this.DataModel = default;
|
||||||
this.dataManuallyModel = string.Empty;
|
|
||||||
this.availableModels.Clear();
|
this.availableModels.Clear();
|
||||||
this.dataLoadingModelsIssue = string.Empty;
|
this.dataLoadingModelsIssue = string.Empty;
|
||||||
|
this.dataConfiguredModelIsNotOffered = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -535,11 +512,13 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
this.DataModel = default;
|
this.DataModel = default;
|
||||||
this.availableModels.Clear();
|
this.availableModels.Clear();
|
||||||
this.dataLoadingModelsIssue = string.Empty;
|
this.dataLoadingModelsIssue = string.Empty;
|
||||||
|
this.dataConfiguredModelIsNotOffered = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReloadModels()
|
private async Task ReloadModels()
|
||||||
{
|
{
|
||||||
this.dataLoadingModelsIssue = string.Empty;
|
this.dataLoadingModelsIssue = string.Empty;
|
||||||
|
this.dataServerWasAskedForItsModels = true;
|
||||||
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
|
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
|
||||||
var provider = currentEmbeddingProviderSettings.CreateProvider();
|
var provider = currentEmbeddingProviderSettings.CreateProvider();
|
||||||
if (provider is NoProvider)
|
if (provider is NoProvider)
|
||||||
@ -562,8 +541,57 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");
|
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");
|
||||||
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whatever the server answered, and whether it answered at all, the model this provider was
|
||||||
|
// configured with stays on the list:
|
||||||
|
this.PinConfiguredModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps the configured model selectable, also when the server does not offer it right now.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is deliberately the opposite of what the chat provider dialog does, which replaces the
|
||||||
|
/// configured model with the one the server reported. An embedding provider carries indexed data
|
||||||
|
/// sources, and its model ID is part of the embedding signature: changing it -- even only in its
|
||||||
|
/// spelling -- means every prepared document is prepared again. So the stored model is added to
|
||||||
|
/// the list here rather than the list being applied to the stored model. A model nobody serves
|
||||||
|
/// any more stays visible and stays chosen, and changing it stays the user's decision, which
|
||||||
|
/// storing then asks about.
|
||||||
|
///
|
||||||
|
/// Comparing is what Model does, which is by ID and ordinal. Matching a differing spelling would
|
||||||
|
/// mean writing that other spelling into the settings, and that is the very change this avoids.
|
||||||
|
/// </remarks>
|
||||||
|
private void PinConfiguredModel()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(this.DataModel.Id))
|
||||||
|
{
|
||||||
|
this.dataConfiguredModelIsNotOffered = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataConfiguredModelIsNotOffered = !this.availableModels.Contains(this.DataModel);
|
||||||
|
if (this.dataConfiguredModelIsNotOffered)
|
||||||
|
this.availableModels.Insert(0, this.DataModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the server answered without naming a single embedding model.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Two situations end up here, and the user is the only one who can tell them apart: a server
|
||||||
|
/// running no embedding model at all, and one running an embedding model under a name no rule
|
||||||
|
/// covers. Saying so beats the bare "No models loaded or available.", which reads like a
|
||||||
|
/// failure and leaves nobody anywhere to go -- the field for typing a name is gone, on purpose.
|
||||||
|
/// Describing such a model in a model plugin is the way out, and naming it here is what turns
|
||||||
|
/// a dead end into one.
|
||||||
|
/// </remarks>
|
||||||
|
private bool ServerNamedNoEmbeddingModel =>
|
||||||
|
this.DataLLMProvider is LLMProviders.SELF_HOSTED &&
|
||||||
|
this.dataServerWasAskedForItsModels &&
|
||||||
|
string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue) &&
|
||||||
|
this.availableModels.Count is 0;
|
||||||
|
|
||||||
private string APIKeyText => this.DataLLMProvider switch
|
private string APIKeyText => this.DataLLMProvider switch
|
||||||
{
|
{
|
||||||
LLMProviders.SELF_HOSTED => T("(Optional) API Key"),
|
LLMProviders.SELF_HOSTED => T("(Optional) API Key"),
|
||||||
|
|||||||
@ -87,47 +87,28 @@
|
|||||||
{
|
{
|
||||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||||
@if (this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost))
|
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
||||||
|
@T("Load")
|
||||||
|
</MudButton>
|
||||||
|
@if(this.availableModels.Count is 0)
|
||||||
{
|
{
|
||||||
<MudTextField
|
<MudText Typo="Typo.body1">
|
||||||
T="string"
|
@T("No models loaded or available.")
|
||||||
@bind-Text="@this.dataManuallyModel"
|
</MudText>
|
||||||
Label="@T("Model")"
|
|
||||||
Class="mb-3"
|
|
||||||
Adornment="Adornment.Start"
|
|
||||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
|
||||||
AdornmentColor="Color.Info"
|
|
||||||
Disabled="@this.IsEnterpriseConfiguration"
|
|
||||||
Validation="@this.ValidateManuallyModel"
|
|
||||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
|
||||||
HelperText="@T("Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.")"
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudButton Disabled="@(this.IsEnterpriseConfiguration || !this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
|
||||||
@T("Load")
|
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||||
</MudButton>
|
AdornmentColor="Color.Info" Adornment="Adornment.Start"
|
||||||
@if(this.availableModels.Count is 0)
|
Validation="@this.providerValidation.ValidatingModel">
|
||||||
{
|
@foreach (var model in this.availableModels)
|
||||||
<MudText Typo="Typo.body1">
|
{
|
||||||
@T("No models loaded or available.")
|
<MudSelectItem Value="@model">
|
||||||
</MudText>
|
@model
|
||||||
}
|
</MudSelectItem>
|
||||||
else
|
}
|
||||||
{
|
</MudSelect>
|
||||||
<MudSelect Disabled="@(this.IsEnterpriseConfiguration || this.IsNoneProvider)" @bind-Value="@this.DataModel" Label="@T("Model")"
|
|
||||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
|
||||||
AdornmentColor="Color.Info" Adornment="Adornment.Start"
|
|
||||||
Validation="@this.providerValidation.ValidatingModel">
|
|
||||||
@foreach (var model in this.availableModels)
|
|
||||||
{
|
|
||||||
<MudSelectItem Value="@model">
|
|
||||||
@model
|
|
||||||
</MudSelectItem>
|
|
||||||
}
|
|
||||||
</MudSelect>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||||
|
|||||||
@ -106,7 +106,6 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
|||||||
private string[] dataIssues = [];
|
private string[] dataIssues = [];
|
||||||
private string dataAPIKey = string.Empty;
|
private string dataAPIKey = string.Empty;
|
||||||
private bool dataHadStoredAPIKeyOnLoad;
|
private bool dataHadStoredAPIKeyOnLoad;
|
||||||
private string dataManuallyModel = string.Empty;
|
|
||||||
private string dataAPIKeyStorageIssue = string.Empty;
|
private string dataAPIKeyStorageIssue = string.Empty;
|
||||||
private string dataEditingPreviousInstanceName = string.Empty;
|
private string dataEditingPreviousInstanceName = string.Empty;
|
||||||
private string dataLoadingModelsIssue = string.Empty;
|
private string dataLoadingModelsIssue = string.Empty;
|
||||||
@ -127,7 +126,6 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
|||||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||||
GetHost = () => this.DataHost,
|
GetHost = () => this.DataHost,
|
||||||
IsModelProvidedManually = () => this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,30 +133,9 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
|||||||
{
|
{
|
||||||
var cleanedHostname = this.DataHostname.Trim();
|
var cleanedHostname = this.DataHostname.Trim();
|
||||||
|
|
||||||
// Determine the model based on the provider and host configuration:
|
// whisper.cpp serves whatever it was started with and names no models, so the placeholder
|
||||||
Model model;
|
// stands in for the one model there is. Everywhere else the user picked one from the list:
|
||||||
if (this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost))
|
var model = this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost) ? Model.SYSTEM_MODEL : this.DataModel;
|
||||||
{
|
|
||||||
// Use system model placeholder for hosts that don't support model selection (e.g., whisper.cpp):
|
|
||||||
model = Model.SYSTEM_MODEL;
|
|
||||||
}
|
|
||||||
else if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
|
||||||
{
|
|
||||||
switch (this.DataHost)
|
|
||||||
{
|
|
||||||
case Host.OLLAMA:
|
|
||||||
model = new Model(this.dataManuallyModel, null);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Host.VLLM:
|
|
||||||
case Host.LM_STUDIO:
|
|
||||||
default:
|
|
||||||
model = this.DataModel;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
model = this.DataModel;
|
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
@ -194,10 +171,6 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
|||||||
if(this.IsEditing)
|
if(this.IsEditing)
|
||||||
{
|
{
|
||||||
this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant();
|
this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant();
|
||||||
|
|
||||||
// When using self-hosted models, we must copy the model name:
|
|
||||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
|
||||||
this.dataManuallyModel = this.DataModel.Id;
|
|
||||||
|
|
||||||
// Load the API key. A self-hosted server may well need one: LM Studio can ask for a
|
// Load the API key. A self-hosted server may well need one: LM Studio can ask for a
|
||||||
// token of its own, and any of these servers can sit behind an authenticating proxy.
|
// token of its own, and any of these servers can sit behind an authenticating proxy.
|
||||||
@ -302,14 +275,6 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
|||||||
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
|
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ValidateManuallyModel(string manuallyModel)
|
|
||||||
{
|
|
||||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel))
|
|
||||||
return T("Please enter a transcription model name.");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Cancel() => this.MudDialog.Cancel();
|
private void Cancel() => this.MudDialog.Cancel();
|
||||||
|
|
||||||
private async Task OnAPIKeyChanged(string apiKey)
|
private async Task OnAPIKeyChanged(string apiKey)
|
||||||
@ -327,7 +292,6 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
|||||||
// When the host changes, reset the model selection state:
|
// When the host changes, reset the model selection state:
|
||||||
this.DataHost = selectedHost;
|
this.DataHost = selectedHost;
|
||||||
this.DataModel = default;
|
this.DataModel = default;
|
||||||
this.dataManuallyModel = string.Empty;
|
|
||||||
this.availableModels.Clear();
|
this.availableModels.Clear();
|
||||||
this.dataLoadingModelsIssue = string.Empty;
|
this.dataLoadingModelsIssue = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,10 @@ namespace AIStudio.Models.Alibaba;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The previous rules answered for these with the Model Studio default and told them they call
|
/// The previous rules answered for these with the Model Studio default and told them they call
|
||||||
/// functions. The prefix is Alibaba's own: the app filters the catalog by "text-embedding-" to find
|
/// functions. The prefix is Alibaba's own, and the rule may be written that broadly because it is
|
||||||
/// them, which is also why the rule may be written that broadly -- bound to this provider, it can
|
/// bound to this provider: it can only ever meet the models Alibaba names that way. The provider
|
||||||
/// only ever meet the models Alibaba names that way.
|
/// carried the same prefix as a filter of its own until it started asking here, so this is now the
|
||||||
|
/// only place which says what those names mean.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class ModelStudioEmbeddingFamily : ModelFamily
|
public sealed class ModelStudioEmbeddingFamily : ModelFamily
|
||||||
{
|
{
|
||||||
@ -19,7 +20,7 @@ public sealed class ModelStudioEmbeddingFamily : ModelFamily
|
|||||||
public override ModelVendor Vendor => ModelVendor.ALIBABA;
|
public override ModelVendor Vendor => ModelVendor.ALIBABA;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ModelSource Source => new("https://www.alibabacloud.com/help/en/model-studio/embedding", new DateOnly(2026, 9, 11), "Provider/AlibabaCloud/ProviderAlibabaCloud.cs adds these in GetEmbeddingModels and filters the catalog by the prefix \"text-embedding-\".");
|
public override ModelSource Source => new("https://www.alibabacloud.com/help/en/model-studio/embedding", new DateOnly(2026, 9, 11), "Provider/AlibabaCloud/ProviderAlibabaCloud.cs used to add these in GetEmbeddingModels and to filter the catalog by the prefix \"text-embedding-\"; it asks this rule instead.");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Declare(ModelFamilyBuilder builder) =>
|
protected override void Declare(ModelFamilyBuilder builder) =>
|
||||||
|
|||||||
32
app/MindWork AI Studio/Models/Google/AqaFamily.cs
Normal file
32
app/MindWork AI Studio/Models/Google/AqaFamily.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using AIStudio.Provider;
|
||||||
|
|
||||||
|
using static AIStudio.Provider.Capability;
|
||||||
|
|
||||||
|
namespace AIStudio.Models.Google;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AQA, which answers a question out of the passages it was handed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Attributed Question Answering, and the one entry in Google's catalog whose whole name is three
|
||||||
|
/// letters. It answers on generateAnswer rather than on generateContent, together with the semantic
|
||||||
|
/// retriever, and what comes back is the answer, the passages it rests on, and an estimate of
|
||||||
|
/// whether the question could be answered from them at all.
|
||||||
|
///
|
||||||
|
/// Bound to Google, and it has to be: three letters are three letters, and a rule that short has no
|
||||||
|
/// business meeting a name from somewhere else. The catalog holds exactly one model it can match.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class AqaFamily : ModelFamily
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelVendor Vendor => ModelVendor.GOOGLE;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelSource Source => new("https://ai.google.dev/gemini-api/docs/semantic_retrieval", new DateOnly(2026, 9, 19), "Reads 7,168 tokens and writes 1,024, which is a size for an answer rather than for a conversation. The route it answers on is generateAnswer, so nothing the app sends over the chat completion API reaches it.");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Declare(ModelFamilyBuilder builder) =>
|
||||||
|
builder.Rule("aqa").AsExact().OnlyOn(LLMProviders.GOOGLE)
|
||||||
|
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
|
||||||
|
.Kind(ModelKind.GROUNDED_ANSWERING);
|
||||||
|
}
|
||||||
48
app/MindWork AI Studio/Models/Google/GoogleAgentFamily.cs
Normal file
48
app/MindWork AI Studio/Models/Google/GoogleAgentFamily.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using AIStudio.Provider;
|
||||||
|
|
||||||
|
using static AIStudio.Provider.Capability;
|
||||||
|
|
||||||
|
namespace AIStudio.Models.Google;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Google models which are handed a job rather than a message.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Both of these answer on the Interactions API alone, never on generateContent: one request starts
|
||||||
|
/// an autonomous loop which plans, runs code, manages files and searches the web, and a research
|
||||||
|
/// run takes minutes rather than seconds. A chat request does not time out against them -- it never
|
||||||
|
/// arrives.
|
||||||
|
///
|
||||||
|
/// Deep Research is the reason these rules are bound to Google instead of standing among the kinds.
|
||||||
|
/// Perplexity and OpenAI both sell something under that name, and both of those answer over the
|
||||||
|
/// chat completion API like any other model: sonar-deep-research states it in its own family, and
|
||||||
|
/// o3-deep-research is held in the corpus. The same two words, three different things, and only the
|
||||||
|
/// provider tells them apart.
|
||||||
|
///
|
||||||
|
/// Written as a prefix on top of that, because Google puts the words at the front of the name while
|
||||||
|
/// the other two hang them onto a model they already had. Either guard alone would do; together
|
||||||
|
/// they also cover whatever Google names this way next.
|
||||||
|
///
|
||||||
|
/// Antigravity needs no such guard -- nobody else names a model that -- but it is a statement about
|
||||||
|
/// Google's catalog all the same, so it stands where the other one stands.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class GoogleAgentFamily : ModelFamily
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelVendor Vendor => ModelVendor.GOOGLE;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelSource Source => new("https://ai.google.dev/gemini-api/docs/deep-research", new DateOnly(2026, 9, 19), "The page states it for the two 04-2026 models: Deep Research runs only through the Interactions API, never through generateContent, and only in the background, because a single run takes five to twenty minutes. The catalog also serves deep-research-pro-preview-12-2025, which the page no longer lists; that it works the same way is read off the naming line rather than off a source. The Antigravity agent is documented at https://ai.google.dev/gemini-api/docs/antigravity-agent and runs on a sandbox Google hosts.");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Declare(ModelFamilyBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Rule("deep-research").AsPrefix().OnlyOn(LLMProviders.GOOGLE)
|
||||||
|
.Capabilities(TEXT_INPUT)
|
||||||
|
.Kind(ModelKind.AGENT);
|
||||||
|
|
||||||
|
builder.Rule("antigravity").AsSegment().OnlyOn(LLMProviders.GOOGLE)
|
||||||
|
.Capabilities(TEXT_INPUT)
|
||||||
|
.Kind(ModelKind.AGENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
app/MindWork AI Studio/Models/Google/LyriaFamily.cs
Normal file
35
app/MindWork AI Studio/Models/Google/LyriaFamily.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using AIStudio.Provider;
|
||||||
|
|
||||||
|
using static AIStudio.Provider.Capability;
|
||||||
|
|
||||||
|
namespace AIStudio.Models.Google;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lyria, which writes music from a description.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Nothing knew the name, so the catalog answered for it the way it answers for everything nobody
|
||||||
|
/// wrote a rule for: a chat model which reads text, writes text and calls tools. What comes back is
|
||||||
|
/// a stereo recording with instruments and, from 3.5 on, sung lyrics.
|
||||||
|
///
|
||||||
|
/// Nobody ran into it because the Google provider showed only names beginning with "gemini", which
|
||||||
|
/// kept four Lyria models out of sight along with the two Gemma models somebody actually wants. The
|
||||||
|
/// prefix is gone now, so the rule has to carry what the prefix carried by accident.
|
||||||
|
///
|
||||||
|
/// Whole name parts rather than a substring, for the reason Imagen gives next door. The rule is not
|
||||||
|
/// bound to Google, unlike the agent ones: wherever a model called Lyria turns up, it is this.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class LyriaFamily : ModelFamily
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelVendor Vendor => ModelVendor.GOOGLE;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelSource Source => new("https://ai.google.dev/gemini-api/docs/music-generation", new DateOnly(2026, 9, 19), "A description goes in and 44.1 kHz stereo music comes out, with vocals and timed lyrics from Lyria 3.5 on. The realtime variant holds a connection open instead, which the realtime rule states and outranks this with.");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Declare(ModelFamilyBuilder builder) =>
|
||||||
|
builder.Rule("lyria").AsSegment()
|
||||||
|
.Capabilities(TEXT_INPUT)
|
||||||
|
.Kind(ModelKind.MUSIC_GENERATION);
|
||||||
|
}
|
||||||
@ -27,7 +27,7 @@ public sealed class EmbeddingModelsFamily : ModelFamily
|
|||||||
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ModelSource Source => new("https://huggingface.co/models?pipeline_tag=feature-extraction", new DateOnly(2026, 9, 12), "Ported from the embedding markers of Provider/ModelKindExtensions.cs. The e5 line says it in its own family, so it is not repeated here.");
|
public override ModelSource Source => new("https://huggingface.co/models?pipeline_tag=feature-extraction", new DateOnly(2026, 9, 19), "Ported from the embedding markers of Provider/ModelKindExtensions.cs. The e5 line says it in its own family, so it is not repeated here. The four names at the end came later, from going through the widely used embedding models whose name carries none of the words above.");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Declare(ModelFamilyBuilder builder)
|
protected override void Declare(ModelFamilyBuilder builder)
|
||||||
@ -55,5 +55,24 @@ public sealed class EmbeddingModelsFamily : ModelFamily
|
|||||||
// General Text Embeddings, from Alibaba. Written as a name part rather than as a substring,
|
// General Text Embeddings, from Alibaba. Written as a name part rather than as a substring,
|
||||||
// because three letters appear inside far too many unrelated words:
|
// because three letters appear inside far too many unrelated words:
|
||||||
builder.Modifier("gte").AsSegment().Inherits();
|
builder.Modifier("gte").AsSegment().Inherits();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Four that say nothing about embedding in their name, and are embedding models all the
|
||||||
|
// same. Every one of them is widely used, so leaving them out does not cost an exotic case:
|
||||||
|
// it puts them among the chat models, where somebody picks one and waits for an answer it
|
||||||
|
// cannot give. All four are written as name parts rather than as substrings, for the reason
|
||||||
|
// gte above gives -- short words which appear inside unrelated names.
|
||||||
|
//
|
||||||
|
// Note that "instructor" is a different word from the "instruct" which half the chat models
|
||||||
|
// carry, and a name part never matches half of one.
|
||||||
|
//
|
||||||
|
builder.Modifier("stella").AsSegment().Inherits();
|
||||||
|
|
||||||
|
builder.Modifier("labse").AsSegment().Inherits();
|
||||||
|
|
||||||
|
builder.Modifier("instructor").AsSegment().Inherits();
|
||||||
|
|
||||||
|
// Generalizable T5 Retrieval, from Google:
|
||||||
|
builder.Modifier("gtr").AsSegment().Inherits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ public sealed class ImageGenerationModelsFamily : ModelFamily
|
|||||||
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ModelSource Source => new("https://huggingface.co/models?pipeline_tag=text-to-image", new DateOnly(2026, 9, 12), "Ported from the image generation markers of Provider/ModelKindExtensions.cs. Imagen and the Gemini image models state it in their own families as well, where the capabilities stand next to it.");
|
public override ModelSource Source => new("https://huggingface.co/models?pipeline_tag=text-to-image", new DateOnly(2026, 9, 12), "Ported from the image generation markers of Provider/ModelKindExtensions.cs. Imagen and the Gemini image models state it in their own families as well, where the capabilities stand next to it. Nano Banana came later, off Google's own catalog at https://generativelanguage.googleapis.com/v1beta/openai/models, read on 2026-09-19.");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Declare(ModelFamilyBuilder builder)
|
protected override void Declare(ModelFamilyBuilder builder)
|
||||||
@ -38,5 +38,9 @@ public sealed class ImageGenerationModelsFamily : ModelFamily
|
|||||||
|
|
||||||
// The other half of Grok Imagine, which the video rule steps aside for:
|
// The other half of Grok Imagine, which the video rule steps aside for:
|
||||||
builder.Modifier("grok-imagine").AsSegment().NotContains("video").Inherits();
|
builder.Modifier("grok-imagine").AsSegment().NotContains("video").Inherits();
|
||||||
|
|
||||||
|
// Google's codename for the image model it serves next to Gemini, and the one name in its
|
||||||
|
// catalog which says nothing about drawing: nano-banana-pro-preview.
|
||||||
|
builder.Modifier("nano-banana").AsSegment().Inherits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ public sealed class RealtimeModelsFamily : ModelFamily
|
|||||||
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ModelSource Source => new("https://developers.openai.com/api/docs/models/gpt-live-1", new DateOnly(2026, 9, 12), "Ported from the realtime marker of Provider/ModelKindExtensions.cs, where the same precedence was written as the order of two if statements. GPT-Live was added after it turned up in the chat list while testing.");
|
public override ModelSource Source => new("https://developers.openai.com/api/docs/models/gpt-live-1", new DateOnly(2026, 9, 12), "Ported from the realtime marker of Provider/ModelKindExtensions.cs, where the same precedence was written as the order of two if statements. The live rule was added after GPT-Live turned up in the chat list while testing, and widened when Google's catalog turned out to name its whole two-way line that way: https://ai.google.dev/gemini-api/docs/live");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Declare(ModelFamilyBuilder builder)
|
protected override void Declare(ModelFamilyBuilder builder)
|
||||||
@ -33,10 +33,15 @@ public sealed class RealtimeModelsFamily : ModelFamily
|
|||||||
.Kind(ModelKind.REALTIME);
|
.Kind(ModelKind.REALTIME);
|
||||||
|
|
||||||
//
|
//
|
||||||
// The line which dropped the word. GPT-Live listens and speaks at the same time and leaves
|
// The other word for the same connection. OpenAI's GPT-Live listens and speaks at once and
|
||||||
// the thinking to a text model behind it, so there is even less of a conversation in it than
|
// leaves the thinking to a text model behind it, and Google names its whole two-way line
|
||||||
// in the realtime models it succeeds -- and nothing in the name says so any more.
|
// this way: gemini-3.8-live, gemini-3.1-flash-live-preview, gemini-3.5-live-translate-preview.
|
||||||
|
// None of them can be talked to the way a chat model can, and all of them stood in the chat
|
||||||
|
// list until this rule was written.
|
||||||
//
|
//
|
||||||
builder.Modifier("gpt-live").AsSegment().Inherits();
|
// A name part rather than a substring, because four letters sit inside "delivery",
|
||||||
|
// "olive" and plenty of words which promise no connection at all.
|
||||||
|
//
|
||||||
|
builder.Modifier("live").AsSegment().Inherits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ public sealed class TextCompletionModelsFamily : ModelFamily
|
|||||||
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ModelSource Source => new("https://platform.openai.com/docs/api-reference/completions", new DateOnly(2026, 9, 12), "Ported unchanged from the text completion markers of Provider/ModelKindExtensions.cs.");
|
public override ModelSource Source => new("https://platform.openai.com/docs/api-reference/completions", new DateOnly(2026, 9, 12), "Ported unchanged from the text completion markers of Provider/ModelKindExtensions.cs. Fill-in-the-middle came later, off Mistral's own catalog at https://api.mistral.ai/v1/models, read on 2026-09-19.");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Declare(ModelFamilyBuilder builder)
|
protected override void Declare(ModelFamilyBuilder builder)
|
||||||
@ -32,5 +32,13 @@ public sealed class TextCompletionModelsFamily : ModelFamily
|
|||||||
|
|
||||||
// The one model of the 3.5 line which never learned to chat, next to the ones which did:
|
// The one model of the 3.5 line which never learned to chat, next to the ones which did:
|
||||||
builder.Modifier("gpt-3.5-turbo-instruct").AsSegment().Inherits();
|
builder.Modifier("gpt-3.5-turbo-instruct").AsSegment().Inherits();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Fill in the middle: a model handed the code on either side of a gap rather than a
|
||||||
|
// conversation. Mistral writes it into the name of the one it serves for that,
|
||||||
|
// mistral-code-fim-latest, which stood in the chat list because nothing looked for the
|
||||||
|
// word. Codestral does the same job and says so through its family instead.
|
||||||
|
//
|
||||||
|
builder.Modifier("fim").AsSegment().Inherits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -16,7 +16,7 @@ public sealed class TranscriptionModelsFamily : ModelFamily
|
|||||||
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
public override ModelVendor Vendor => ModelVendor.UNKNOWN;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ModelSource Source => new("https://huggingface.co/models?pipeline_tag=automatic-speech-recognition", new DateOnly(2026, 9, 12), "Ported from the transcription markers of Provider/ModelKindExtensions.cs, minus the two which their own families now state.");
|
public override ModelSource Source => new("https://huggingface.co/models?pipeline_tag=automatic-speech-recognition", new DateOnly(2026, 9, 19), "Ported from the transcription markers of Provider/ModelKindExtensions.cs, minus the two which their own families now state. Canary and asr came later: the markers named neither, so both reached the answer meant for everything nobody wrote a rule for. Alibaba's speech line is documented at https://www.alibabacloud.com/help/en/model-studio/qwen-asr-api-reference.");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Declare(ModelFamilyBuilder builder)
|
protected override void Declare(ModelFamilyBuilder builder)
|
||||||
@ -27,5 +27,25 @@ public sealed class TranscriptionModelsFamily : ModelFamily
|
|||||||
builder.Modifier("wav2vec").AsSubstring().Inherits();
|
builder.Modifier("wav2vec").AsSubstring().Inherits();
|
||||||
|
|
||||||
builder.Modifier("parakeet").AsSubstring().Inherits();
|
builder.Modifier("parakeet").AsSubstring().Inherits();
|
||||||
|
|
||||||
|
// NVIDIA's other line of speech models, written plain: canary-1b, canary-1b-flash,
|
||||||
|
// canary-180m-flash. A segment rather than a substring, because canary is an ordinary
|
||||||
|
// English word which would otherwise reach into names it has nothing to do with -- the
|
||||||
|
// same reason the embedding family gives for gte.
|
||||||
|
builder.Modifier("canary").AsSegment().Inherits();
|
||||||
|
|
||||||
|
//
|
||||||
|
// The abbreviation the whole field goes by, and the one Alibaba names its speech line
|
||||||
|
// after: qwen3-asr-flash, qwen3-asr-1.7b, fun-asr-realtime. Three letters, so a name part
|
||||||
|
// and never a substring -- "laser" and "eraser" carry them without meaning any of this.
|
||||||
|
//
|
||||||
|
builder.Modifier("asr").AsSegment().Inherits();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Alibaba also builds the line into its audio models, and "audio" is the longer word, so
|
||||||
|
// without this the speech synthesis rule would answer for a model which only listens. A
|
||||||
|
// name carrying both words is a transcription model whatever else it is called.
|
||||||
|
//
|
||||||
|
builder.Modifier("audio").AsSegment().AlsoContains("asr").Inherits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
using AIStudio.Provider;
|
||||||
|
|
||||||
using static AIStudio.Provider.Capability;
|
using static AIStudio.Provider.Capability;
|
||||||
|
|
||||||
namespace AIStudio.Models.Mistral;
|
namespace AIStudio.Models.Mistral;
|
||||||
@ -10,6 +12,12 @@ namespace AIStudio.Models.Mistral;
|
|||||||
/// words the Mistral block looked for, so it walked past every rule and reached the answer meant
|
/// words the Mistral block looked for, so it walked past every rule and reached the answer meant
|
||||||
/// for everything nobody had written one for. That answer happened to describe it correctly, which
|
/// for everything nobody had written one for. That answer happened to describe it correctly, which
|
||||||
/// is why nothing looked wrong -- and is exactly the situation this rebuild is meant to end.
|
/// is why nothing looked wrong -- and is exactly the situation this rebuild is meant to end.
|
||||||
|
///
|
||||||
|
/// That Mistral serves it to fill in the middle of a file rather than to talk to was the one thing
|
||||||
|
/// the rebuild left behind: it stayed in the Mistral provider, as a name check which dropped every
|
||||||
|
/// model whose ID begins with "code". It says something about a model, so it belongs to the model,
|
||||||
|
/// and it is bound to the provider because it is only true there. Somebody's own server and the
|
||||||
|
/// gateways serve the same weights to chat with, which is what the unbound rule above keeps saying.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class CodestralFamily : ModelFamily
|
public sealed class CodestralFamily : ModelFamily
|
||||||
{
|
{
|
||||||
@ -17,11 +25,22 @@ public sealed class CodestralFamily : ModelFamily
|
|||||||
public override ModelVendor Vendor => ModelVendor.MISTRAL_AI;
|
public override ModelVendor Vendor => ModelVendor.MISTRAL_AI;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override ModelSource Source => new("https://docs.mistral.ai/getting-started/models/models_overview/", new DateOnly(2026, 9, 11), "The answer the previous rules gave it through their fallback: text in, text out, tool calling.");
|
public override ModelSource Source => new("https://docs.mistral.ai/getting-started/models/models_overview/", new DateOnly(2026, 9, 19), "The answer the previous rules gave it through their fallback: text in, text out, tool calling. What Mistral's own catalog makes of it was read from the same page, where Codestral is the model behind the FIM endpoint.");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Declare(ModelFamilyBuilder builder) =>
|
protected override void Declare(ModelFamilyBuilder builder)
|
||||||
|
{
|
||||||
builder.Rule("codestral").AsSegment()
|
builder.Rule("codestral").AsSegment()
|
||||||
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
|
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
|
||||||
.Apis(CHAT_COMPLETION_API);
|
.Apis(CHAT_COMPLETION_API);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Bound to Mistral, which makes it the more specific of the two and lets it win there
|
||||||
|
// without anybody writing an order. It continues a text instead of answering in a
|
||||||
|
// conversation, so it must not stand among the models somebody picks for a chat.
|
||||||
|
//
|
||||||
|
builder.Rule("codestral").AsSegment().OnlyOn(LLMProviders.MISTRAL)
|
||||||
|
.Inherits()
|
||||||
|
.Kind(ModelKind.TEXT_COMPLETION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -6549,9 +6549,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1356621346"] = "Ko
|
|||||||
-- Failed to validate the selected tokenizer. Please try again.
|
-- Failed to validate the selected tokenizer. Please try again.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1384494471"] = "Die Überprüfung des ausgewählten Tokenizers ist fehlgeschlagen. Bitte versuchen Sie es erneut."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1384494471"] = "Die Überprüfung des ausgewählten Tokenizers ist fehlgeschlagen. Bitte versuchen Sie es erneut."
|
||||||
|
|
||||||
-- Please enter an embedding model name.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1661085403"] = "Bitte geben Sie einen Modellnamen für die Einbettung ein."
|
|
||||||
|
|
||||||
-- Hostname
|
-- Hostname
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Hostname"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Hostname"
|
||||||
|
|
||||||
@ -6606,9 +6603,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "Ke
|
|||||||
-- Instance Name
|
-- Instance Name
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instanzname"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instanzname"
|
||||||
|
|
||||||
-- Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Derzeit können wir die Einbettungs-Modelle für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
|
|
||||||
|
|
||||||
-- Token limit
|
-- Token limit
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2961294165"] = "Token-Limit"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2961294165"] = "Token-Limit"
|
||||||
|
|
||||||
@ -6618,6 +6612,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3316544737"] = "Bi
|
|||||||
-- Show Expert Settings
|
-- Show Expert Settings
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3361153305"] = "Experten-Einstellungen anzeigen"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3361153305"] = "Experten-Einstellungen anzeigen"
|
||||||
|
|
||||||
|
-- This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3571276758"] = "Dieser Server bietet das ausgewählte Modell derzeit nicht an. Das Modell bleibt ausgewählt, damit die bereits vorbereiteten Dokumente weiterhin funktionieren. Wenn Sie ein anderes Modell wählen, werden alle Dokumente der Datenquellen dieses Anbieters erneut vorbereitet."
|
||||||
|
|
||||||
-- How many chunks are sent to the embedding provider at once. The default is 1.
|
-- How many chunks are sent to the embedding provider at once. The default is 1.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3780233303"] = "Wie viele Blöcke gleichzeitig an den Einbettungsanbieter gesendet werden. Der Standardwert ist 1."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3780233303"] = "Wie viele Blöcke gleichzeitig an den Einbettungsanbieter gesendet werden. Der Standardwert ist 1."
|
||||||
|
|
||||||
@ -6639,6 +6636,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Hos
|
|||||||
-- Please enter an embedding batch size greater than 0.
|
-- Please enter an embedding batch size greater than 0.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T840259907"] = "Bitte geben Sie eine Batch-Größe für Einbettungen größer als 0 ein."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T840259907"] = "Bitte geben Sie eine Batch-Größe für Einbettungen größer als 0 ein."
|
||||||
|
|
||||||
|
-- Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T859645108"] = "Ihr Server hat geantwortet, aber keines der bereitgestellten Modelle ist uns als Modell zur Erstellung von Einbettungen bekannt. Entweder ist kein solches Modell installiert oder es läuft unter einem Namen, den wir nicht erkennen. Im letzteren Fall kann Ihre Organisation das Modell in einem Modell-Plugin beschreiben. Dann wird es hier angezeigt."
|
||||||
|
|
||||||
-- Provider
|
-- Provider
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Anbieter"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Anbieter"
|
||||||
|
|
||||||
@ -8718,9 +8718,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1324664716"] =
|
|||||||
-- Create account
|
-- Create account
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Konto erstellen"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Konto erstellen"
|
||||||
|
|
||||||
-- Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1381635232"] = "Derzeit können wir die Modelle für Transkriptionen für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
|
|
||||||
|
|
||||||
-- Hostname
|
-- Hostname
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
|
||||||
|
|
||||||
@ -8754,9 +8751,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] =
|
|||||||
-- Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.
|
-- Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3397943774"] = "Hugging Face transkribiert Audio nur über einige seiner Inferenzanbieter. Deshalb ist diese Liste kürzer als die für den Chat."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3397943774"] = "Hugging Face transkribiert Audio nur über einige seiner Inferenzanbieter. Deshalb ist diese Liste kürzer als die für den Chat."
|
||||||
|
|
||||||
-- Please enter a transcription model name.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Bitte geben Sie den Namen eines Transkriptionsmodells ein."
|
|
||||||
|
|
||||||
-- This host uses the model configured at the provider level. No model selection is available.
|
-- This host uses the model configured at the provider level. No model selection is available.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Eine Modellauswahl ist nicht verfügbar."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Eine Modellauswahl ist nicht verfügbar."
|
||||||
|
|
||||||
|
|||||||
@ -6549,9 +6549,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1356621346"] = "Cr
|
|||||||
-- Failed to validate the selected tokenizer. Please try again.
|
-- Failed to validate the selected tokenizer. Please try again.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1384494471"] = "Failed to validate the selected tokenizer. Please try again."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1384494471"] = "Failed to validate the selected tokenizer. Please try again."
|
||||||
|
|
||||||
-- Please enter an embedding model name.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1661085403"] = "Please enter an embedding model name."
|
|
||||||
|
|
||||||
-- Hostname
|
-- Hostname
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Hostname"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1727440780"] = "Hostname"
|
||||||
|
|
||||||
@ -6606,9 +6603,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "No
|
|||||||
-- Instance Name
|
-- Instance Name
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name"
|
||||||
|
|
||||||
-- Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually."
|
|
||||||
|
|
||||||
-- Token limit
|
-- Token limit
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2961294165"] = "Token limit"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2961294165"] = "Token limit"
|
||||||
|
|
||||||
@ -6618,6 +6612,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3316544737"] = "Pl
|
|||||||
-- Show Expert Settings
|
-- Show Expert Settings
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
||||||
|
|
||||||
|
-- This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3571276758"] = "This server does not offer the selected model right now. It stays selected, so the documents you already prepared keep working. Choosing another model means every document of the data sources behind this provider is prepared again."
|
||||||
|
|
||||||
-- How many chunks are sent to the embedding provider at once. The default is 1.
|
-- How many chunks are sent to the embedding provider at once. The default is 1.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3780233303"] = "How many chunks are sent to the embedding provider at once. The default is 1."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3780233303"] = "How many chunks are sent to the embedding provider at once. The default is 1."
|
||||||
|
|
||||||
@ -6639,6 +6636,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Hos
|
|||||||
-- Please enter an embedding batch size greater than 0.
|
-- Please enter an embedding batch size greater than 0.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T840259907"] = "Please enter an embedding batch size greater than 0."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T840259907"] = "Please enter an embedding batch size greater than 0."
|
||||||
|
|
||||||
|
-- Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T859645108"] = "Your server answered, but none of the models it serves is one we know to create embeddings. Either there is none installed, or it runs under a name we do not recognize. In the latter case, your organization can describe the model in a model plugin, and it will show up here."
|
||||||
|
|
||||||
-- Provider
|
-- Provider
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Provider"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Provider"
|
||||||
|
|
||||||
@ -8718,9 +8718,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1324664716"] =
|
|||||||
-- Create account
|
-- Create account
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Create account"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Create account"
|
||||||
|
|
||||||
-- Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1381635232"] = "Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually."
|
|
||||||
|
|
||||||
-- Hostname
|
-- Hostname
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
|
||||||
|
|
||||||
@ -8754,9 +8751,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] =
|
|||||||
-- Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.
|
-- Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3397943774"] = "Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3397943774"] = "Hugging Face transcribes audio through a few of its inference providers only, which is why this list is shorter than the one for chatting."
|
||||||
|
|
||||||
-- Please enter a transcription model name.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Please enter a transcription model name."
|
|
||||||
|
|
||||||
-- This host uses the model configured at the provider level. No model selection is available.
|
-- This host uses the model configured at the provider level. No model selection is available.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||||
|
|
||||||
|
|||||||
@ -146,10 +146,13 @@ MODELS = {}
|
|||||||
--
|
--
|
||||||
-- -- What the model is made for. Optional, defaults to CHAT.
|
-- -- What the model is made for. Optional, defaults to CHAT.
|
||||||
-- -- Allowed values are: CHAT, TEXT_COMPLETION, EMBEDDING, RERANKING,
|
-- -- Allowed values are: CHAT, TEXT_COMPLETION, EMBEDDING, RERANKING,
|
||||||
-- -- IMAGE_GENERATION, VIDEO_GENERATION, TRANSCRIPTION, SPEECH_SYNTHESIS,
|
-- -- IMAGE_GENERATION, VIDEO_GENERATION, MUSIC_GENERATION, TRANSCRIPTION,
|
||||||
-- -- REALTIME, COMPUTER_USE, OCR, MODERATION, OTHER
|
-- -- SPEECH_SYNTHESIS, REALTIME, COMPUTER_USE, AGENT, GROUNDED_ANSWERING,
|
||||||
|
-- -- OCR, MODERATION, OTHER
|
||||||
-- -- This decides which lists the model appears in. Use OTHER for entries
|
-- -- This decides which lists the model appears in. Use OTHER for entries
|
||||||
-- -- which are no models at all.
|
-- -- which are no models at all. Use AGENT for a model which is handed a
|
||||||
|
-- -- job and works on it by itself, and GROUNDED_ANSWERING for one which
|
||||||
|
-- -- answers out of passages it is given and cites them.
|
||||||
-- ["KIND"] = "CHAT",
|
-- ["KIND"] = "CHAT",
|
||||||
--
|
--
|
||||||
-- -- Optional: how many tokens the model reads and writes in one
|
-- -- Optional: how many tokens the model reads and writes in one
|
||||||
|
|||||||
@ -76,40 +76,8 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
public override async Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var additionalModels = new[]
|
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)] };
|
||||||
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)]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -121,19 +89,8 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task<ModelLoadResult> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
public override async Task<ModelLoadResult> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
var result = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, apiKeyProvisional, token);
|
||||||
var additionalModels = new[]
|
return result with { Models = [..result.Models.Where(model => model.IsEmbeddingModel(this.Provider)).OrderBy(x => x.Id)] };
|
||||||
{
|
|
||||||
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)]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Overrides of BaseProvider
|
#region Overrides of BaseProvider
|
||||||
@ -148,12 +105,23 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
|||||||
|
|
||||||
#endregion
|
#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>(
|
return this.LoadModelsResponse<ModelsResponse>(
|
||||||
storeType,
|
storeType,
|
||||||
"models",
|
"models",
|
||||||
modelResponse => modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture))),
|
modelResponse => modelResponse.Data,
|
||||||
apiKeyProvisional, token: token);
|
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);
|
var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token);
|
||||||
return result with
|
return result with
|
||||||
{
|
{
|
||||||
|
//
|
||||||
// The API is the authority: when it reports a model we also keep as a fallback above,
|
// 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.
|
// its entry comes first and the fallback is dropped. What it reports is asked about
|
||||||
Models = [..result.Models.Concat(additionalModels).DistinctBy(x => x.Id).OrderBy(x => x.Id)]
|
// 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 />
|
/// <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)
|
public override Task<ModelLoadResult> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// Source: https://docs.fireworks.ai/api-reference/audio-transcriptions#param-model
|
// 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"),
|
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
|
#region Implementation of IProvider
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -123,13 +129,27 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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
|
var result = await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, apiKeyProvisional, token);
|
||||||
return Task.FromResult(ModelLoadResult.FromModels(
|
if (!result.Success)
|
||||||
[
|
return result;
|
||||||
new Model("whisper-large-v2", "Whisper v2 Large"),
|
|
||||||
]));
|
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
|
#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.
|
// 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
|
// 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,
|
// 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 =>
|
// The question used to be asked of names beginning with "gemini" alone, and that
|
||||||
model.Id.StartsWith("gemini-", StringComparison.OrdinalIgnoreCase) &&
|
// cost the two Gemma models Google serves on this very route. What the prefix kept
|
||||||
model.IsChatModel(this.Provider))
|
// 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)
|
.Select(this.WithDisplayNameFallback)
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -421,17 +421,6 @@ public static class LLMProvidersExtensions
|
|||||||
_ => false,
|
_ => 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>
|
/// <summary>
|
||||||
/// Determines if the model selection should be completely hidden for LLM providers.
|
/// Determines if the model selection should be completely hidden for LLM providers.
|
||||||
/// This is the case when the host does not support model selection.
|
/// 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 =
|
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
|
// Codestral is a fill-in-the-middle model, which we cannot use for chats. Its own
|
||||||
// kind detection:
|
// family says so now, bound to this provider, so the word "code" no longer has to
|
||||||
..modelResponse.Models.Where(n =>
|
// be tested for here -- and testing for it never reached mistral-code-fim-latest,
|
||||||
!n.Id.StartsWith("code", StringComparison.OrdinalIgnoreCase) &&
|
// which does the same job under a name that begins differently.
|
||||||
n.IsChatModel(this.Provider))
|
//
|
||||||
|
..modelResponse.Models.Where(n => n.IsChatModel(this.Provider))
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -123,13 +124,16 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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
|
var modelResponse = await this.LoadModelList(SecretStoreType.TRANSCRIPTION_PROVIDER, apiKeyProvisional, token);
|
||||||
return Task.FromResult(ModelLoadResult.FromModels(
|
if (!modelResponse.Success)
|
||||||
[
|
return modelResponse;
|
||||||
new Provider.Model("voxtral-mini-latest", "Voxtral Mini Latest"),
|
|
||||||
]));
|
return modelResponse with
|
||||||
|
{
|
||||||
|
Models = [..modelResponse.Models.Where(n => n.IsTranscriptionModel(this.Provider))]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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)),
|
listingFactory: modelResponse => modelResponse.Data.Select(n => ModelListing.For(n.Id, n.ContextWindowTokens)),
|
||||||
token: token);
|
token: token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,6 +52,16 @@ public enum ModelKind
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
VIDEO_GENERATION,
|
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>
|
/// <summary>
|
||||||
/// The model transcribes audio into text.
|
/// The model transcribes audio into text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -86,6 +96,27 @@ public enum ModelKind
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
COMPUTER_USE,
|
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>
|
/// <summary>
|
||||||
/// The model extracts text from images or scanned documents.
|
/// The model extracts text from images or scanned documents.
|
||||||
/// </summary>
|
/// </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
|
/// 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
|
/// is reported replaces everything an instance said before. The windows of the chat models
|
||||||
/// would go missing the moment somebody opens the embedding settings.
|
/// 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>
|
/// </remarks>
|
||||||
/// <param name="apiKeyProvisional">An API key which is not stored yet.</param>
|
/// <param name="apiKeyProvisional">An API key which is not stored yet.</param>
|
||||||
/// <param name="token">The cancellation token to use.</param>
|
/// <param name="token">The cancellation token to use.</param>
|
||||||
@ -156,4 +161,4 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
|||||||
},
|
},
|
||||||
token: token);
|
token: token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,12 +97,16 @@ 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(["embed"], [], apiKeyProvisional, token);
|
return await this.LoadLlamaCppTextModels(apiKeyProvisional, token);
|
||||||
|
|
||||||
case Host.LM_STUDIO:
|
case Host.LM_STUDIO:
|
||||||
case Host.OLLAMA:
|
case Host.OLLAMA:
|
||||||
case Host.VLLM:
|
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([]);
|
return ModelLoadResult.FromModels([]);
|
||||||
@ -129,14 +133,18 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
|||||||
case Host.LM_STUDIO:
|
case Host.LM_STUDIO:
|
||||||
case Host.OLLAMA:
|
case Host.OLLAMA:
|
||||||
case Host.VLLM:
|
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([]);
|
return ModelLoadResult.FromModels([]);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
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);
|
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")),
|
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.OLLAMA:
|
||||||
case Host.VLLM:
|
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:
|
default:
|
||||||
return ModelLoadResult.FromModels([]);
|
return ModelLoadResult.FromModels([]);
|
||||||
}
|
}
|
||||||
@ -171,7 +191,21 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
|||||||
|
|
||||||
#endregion
|
#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);
|
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));
|
ListedModels.Shared.Report(this.ConfiguredProviderId, ListingsOf(models));
|
||||||
|
|
||||||
return SuccessfulModelLoadResult(models.
|
return SuccessfulModelLoadResult(models
|
||||||
Where(model => !string.IsNullOrWhiteSpace(model.Id) &&
|
.Where(model => !string.IsNullOrWhiteSpace(model.Id))
|
||||||
!ignorePhrases.Any(ignorePhrase => model.Id.Contains(ignorePhrase, StringComparison.InvariantCulture)) &&
|
|
||||||
filterPhrases.All( filter => model.Id.Contains(filter, StringComparison.InvariantCulture)))
|
|
||||||
.Select(n => new Provider.Model(n.Id, null)));
|
.Select(n => new Provider.Model(n.Id, null)));
|
||||||
}
|
}
|
||||||
catch (Exception e) when (this.IsTimeoutException(e, token))
|
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)
|
if (host is not Host.LLAMA_CPP || !chatModel.IsSystemModel)
|
||||||
return chatModel;
|
return chatModel;
|
||||||
|
|
||||||
var modelLoadResult = await this.LoadLlamaCppTextModels(["embed"], [], null, token);
|
var modelLoadResult = await this.LoadLlamaCppTextModels(null, token);
|
||||||
if (!modelLoadResult.Success)
|
if (!modelLoadResult.Success)
|
||||||
return chatModel;
|
return chatModel;
|
||||||
|
|
||||||
@ -266,7 +298,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
|||||||
return chatModel;
|
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);
|
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();
|
return LlamaCppLegacyModelResult();
|
||||||
|
|
||||||
var models = responseModels
|
var models = responseModels
|
||||||
.Where(model => IsMatchingLlamaCppTextModel(model, ignorePhrases, filterPhrases))
|
.Where(this.IsMatchingLlamaCppTextModel)
|
||||||
.Select(model => new Provider.Model(model.Id, null))
|
.Select(model => new Provider.Model(model.Id, null))
|
||||||
.ToList();
|
.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>
|
/// <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 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))
|
if (string.IsNullOrWhiteSpace(model.Id))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (ignorePhrases.Any(ignorePhrase => model.Id.Contains(ignorePhrase, StringComparison.InvariantCultureIgnoreCase)))
|
if (!new Provider.Model(model.Id, null).IsChatModel(this.Provider))
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!filterPhrases.All(filter => model.Id.Contains(filter, StringComparison.InvariantCultureIgnoreCase)))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var outputModalities = model.Architecture?.OutputModalities;
|
var outputModalities = model.Architecture?.OutputModalities;
|
||||||
|
|||||||
@ -76,7 +76,7 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, new Uri("https://
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task<ModelLoadResult> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
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
|
return result with
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@ -108,19 +108,24 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, new Uri("https://
|
|||||||
|
|
||||||
#endregion
|
#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>(
|
return this.LoadModelsResponse<ModelsResponse>(
|
||||||
storeType,
|
storeType,
|
||||||
"models",
|
"models",
|
||||||
modelResponse => modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture)))
|
modelResponse => modelResponse.Data,
|
||||||
.Concat([
|
|
||||||
new Model
|
|
||||||
{
|
|
||||||
Id = "grok-2-latest",
|
|
||||||
DisplayName = "Grok 2.0 (latest)",
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
apiKeyProvisional, token: token);
|
apiKeyProvisional, token: token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,6 +36,7 @@
|
|||||||
- Improved the provider selection throughout the assistants: when there is nothing to choose from, it now says why. Either you have not set up a provider yet, or none of yours is trusted enough for what you are doing. Before, the list was simply empty.
|
- Improved the provider selection throughout the assistants: when there is nothing to choose from, it now says why. Either you have not set up a provider yet, or none of yours is trusted enough for what you are doing. Before, the list was simply empty.
|
||||||
- Improved the settings of your embedding providers and data sources. Some of them decide how your documents are read, so changing one means preparing every document all over again. AI Studio now asks before that happens, names the data sources it would affect, and says when a cloud provider charges you for it.
|
- Improved the settings of your embedding providers and data sources. Some of them decide how your documents are read, so changing one means preparing every document all over again. AI Studio now asks before that happens, names the data sources it would affect, and says when a cloud provider charges you for it.
|
||||||
- Improved the dialogs of your embedding providers and data sources: when a change would mean preparing all your documents again, and you decide against it, nothing is saved and the dialog stays open with your change in front of you, ready to be corrected.
|
- Improved the dialogs of your embedding providers and data sources: when a change would mean preparing all your documents again, and you decide against it, nothing is saved and the dialog stays open with your change in front of you, ready to be corrected.
|
||||||
|
- Improved what happens when you open an embedding provider whose server is unreachable or no longer offers the model you chose. That model stays selected, and AI Studio tells you the server does not have it right now. The documents you already prepared keep working, and you are not asked to prepare them again over a change you never made.
|
||||||
- Improved the question AI Studio asks before you delete an embedding provider. It now names the data sources depending on that provider, together with what they can still do without it.
|
- Improved the question AI Studio asks before you delete an embedding provider. It now names the data sources depending on that provider, together with what they can still do without it.
|
||||||
- Improved what the AI is told when it answers from your own documents (RAG): it now learns which page a passage came from, so it can name the page an answer rests on.
|
- Improved what the AI is told when it answers from your own documents (RAG): it now learns which page a passage came from, so it can name the page an answer rests on.
|
||||||
- Changed how provider trust and provider confidence work together. Marking a provider as trustworthy in a configuration no longer also satisfies a required confidence level: one says who runs the provider, the other how confidential it is. Organizations raise a provider's level in their own confidence scheme instead. This applies beyond local data sources, for example, when a model reads a page from your intranet.
|
- Changed how provider trust and provider confidence work together. Marking a provider as trustworthy in a configuration no longer also satisfies a required confidence level: one says who runs the provider, the other how confidential it is. Organizations raise a provider's level in their own confidence scheme instead. This applies beyond local data sources, for example, when a model reads a page from your intranet.
|
||||||
@ -44,7 +45,11 @@
|
|||||||
- Fixed a renamed policy losing its new name in the Document Analysis assistant. The name was kept only when you happened to change something else afterward.
|
- Fixed a renamed policy losing its new name in the Document Analysis assistant. The name was kept only when you happened to change something else afterward.
|
||||||
- Fixed model names that a provider writes in its own way not being recognized at all, such as the colon Ollama puts before the variant. Those models were treated as plain text models and lost every other ability.
|
- Fixed model names that a provider writes in its own way not being recognized at all, such as the colon Ollama puts before the variant. Those models were treated as plain text models and lost every other ability.
|
||||||
- Fixed a model resold under a plain name not getting the abilities it really has.
|
- Fixed a model resold under a plain name not getting the abilities it really has.
|
||||||
- Fixed image and video generation models showing up among the chat models.
|
- Fixed models showing up among the chat models, although nobody can chat with them, such as the ones that draw, film, compose music, transcribe speech, read scanned documents, or work through a task on their own. The same goes for models that need a live connection AI Studio cannot open.
|
||||||
|
- Fixed the model lists of your providers showing the wrong models. Some your provider offers were missing, among them Google's Gemma models, while others that no longer exist were still on offer, among them an older Grok version. The lists now follow what your provider reports.
|
||||||
|
- Fixed having to type the name of an embedding model by hand when you set up a provider on your own Ollama or vLLM server. AI Studio now asks your server which models it has and offers them in a list, the same way it has always done for LM Studio.
|
||||||
|
- Fixed the model list for transcription on your own server offering everything the server has, including models that can only chat or create embeddings. You are now offered the models that can actually transcribe, and nothing else.
|
||||||
|
- Fixed the model of a transcription provider on your own Ollama server not being saved. An empty model was stored instead, so transcribing with it could not work, and picking a different model changed nothing.
|
||||||
- Fixed a dropped file being processed several times, e.g., after the computer woke up from sleep.
|
- Fixed a dropped file being processed several times, e.g., after the computer woke up from sleep.
|
||||||
- Fixed nothing happening when you dropped a file onto the list of your attached files. You can now add files to that list while it is open.
|
- Fixed nothing happening when you dropped a file onto the list of your attached files. You can now add files to that list while it is open.
|
||||||
- Fixed the preview of an attached file ignoring dropped files. Drop another file onto the preview, and it is attached and shown right away.
|
- Fixed the preview of an attached file ignoring dropped files. Drop another file onto the preview, and it is attached and shown right away.
|
||||||
|
|||||||
@ -20,10 +20,7 @@
|
|||||||
# what each of them must answer, but it states capabilities alone.
|
# what each of them must answer, but it states capabilities alone.
|
||||||
#
|
#
|
||||||
ALIBABA_CLOUD | qvq-max | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
ALIBABA_CLOUD | qvq-max | ALWAYS_REASONING, CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
ALIBABA_CLOUD | qwen-max-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
|
||||||
ALIBABA_CLOUD | qwen-mt-plus | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
ALIBABA_CLOUD | qwen-mt-plus | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
ALIBABA_CLOUD | qwen-plus-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
|
||||||
ALIBABA_CLOUD | qwen-turbo-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
|
||||||
ALIBABA_CLOUD | qwen-vl-max | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
ALIBABA_CLOUD | qwen-vl-max | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
ALIBABA_CLOUD | qwen2.5-14b-instruct-1m | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
ALIBABA_CLOUD | qwen2.5-14b-instruct-1m | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
ALIBABA_CLOUD | qwen2.5-72b-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
ALIBABA_CLOUD | qwen2.5-72b-instruct | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
@ -67,7 +64,7 @@ FIREWORKS | accounts/fireworks/models/qwen3-235b-a22b | CHAT_COMPLETION_API, FUN
|
|||||||
FIREWORKS | whisper-v3 | SPEECH_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
FIREWORKS | whisper-v3 | SPEECH_INPUT, TEXT_OUTPUT | TRANSCRIPTION | (unknown) | (unknown) | (unknown)
|
||||||
GOOGLE | gemini-1.0-pro-vision | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
GOOGLE | gemini-1.0-pro-vision | CHAT_COMPLETION_API, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
GOOGLE | gemini-2.0-flash | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | 3600 per request | PROVIDER_API countTokens
|
GOOGLE | gemini-2.0-flash | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | 3600 per request | PROVIDER_API countTokens
|
||||||
GOOGLE | gemini-2.0-flash-live-001 | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, SPEECH_INPUT, SPEECH_OUTPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | (unknown) | (unknown) | (unknown)
|
GOOGLE | gemini-2.0-flash-live-001 | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, SPEECH_INPUT, SPEECH_OUTPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | REALTIME | (unknown) | (unknown) | (unknown)
|
||||||
GOOGLE | gemini-2.5-flash | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1048576 | 3600 per request | PROVIDER_API countTokens
|
GOOGLE | gemini-2.5-flash | ALWAYS_REASONING, AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1048576 | 3600 per request | PROVIDER_API countTokens
|
||||||
GOOGLE | gemini-2.5-flash-image | CHAT_COMPLETION_API, IMAGE_OUTPUT, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | IMAGE_GENERATION | (unknown) | (unknown) | (unknown)
|
GOOGLE | gemini-2.5-flash-image | CHAT_COMPLETION_API, IMAGE_OUTPUT, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | IMAGE_GENERATION | (unknown) | (unknown) | (unknown)
|
||||||
GOOGLE | gemini-2.5-flash-lite | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1048576 | 3600 per request | PROVIDER_API countTokens
|
GOOGLE | gemini-2.5-flash-lite | AUDIO_INPUT, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, OPTIONAL_REASONING, SPEECH_INPUT, TEXT_INPUT, TEXT_OUTPUT, VIDEO_INPUT | CHAT | 1048576 | 3600 per request | PROVIDER_API countTokens
|
||||||
@ -115,7 +112,7 @@ LITE_LLM | anthropic/claude-sonnet-5 | CHAT_COMPLETION_API, FUNCTION_CALLING, MU
|
|||||||
LITE_LLM | azure/gpt-5.6 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
LITE_LLM | azure/gpt-5.6 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, REASONING_BY_DEFAULT, TEXT_INPUT, TEXT_OUTPUT, WEB_SEARCH | CHAT | 1050000 | (unknown) | TIKTOKEN o200k_base
|
||||||
LITE_LLM | bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
LITE_LLM | bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
LITE_LLM | the-fast-one | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
LITE_LLM | the-fast-one | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
MISTRAL | codestral-2508 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
MISTRAL | codestral-2508 | CHAT_COMPLETION_API, FUNCTION_CALLING, TEXT_INPUT, TEXT_OUTPUT | TEXT_COMPLETION | (unknown) | (unknown) | (unknown)
|
||||||
MISTRAL | magistral-medium-2506 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
MISTRAL | magistral-medium-2506 | ALWAYS_REASONING, CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
MISTRAL | ministral-14b-2512 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
MISTRAL | ministral-14b-2512 | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
MISTRAL | ministral-3b-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
MISTRAL | ministral-3b-latest | CHAT_COMPLETION_API, FUNCTION_CALLING, MULTIPLE_IMAGE_INPUT, TEXT_INPUT, TEXT_OUTPUT | CHAT | (unknown) | (unknown) | (unknown)
|
||||||
|
|||||||
@ -17,8 +17,8 @@ public enum CorpusOrigin
|
|||||||
NAMED_BY_A_RULE,
|
NAMED_BY_A_RULE,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The app carries this model in a built-in list, such as the one Alibaba Cloud models are
|
/// The app carries this model in a built-in list, such as the aliases Anthropic answers to but
|
||||||
/// picked from when the provider serves no catalog.
|
/// does not list, or the transcription model GWDG serves without naming it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BUILT_INTO_THE_APP,
|
BUILT_INTO_THE_APP,
|
||||||
|
|
||||||
|
|||||||
@ -134,23 +134,28 @@ public static class ModelCorpus
|
|||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Alibaba Cloud. Everything below the two dozen models the app carries is one Qwen tier per
|
/// Alibaba Cloud. One Qwen tier per entry, because each tier answers differently about thinking
|
||||||
/// entry, because each tier answers differently about thinking and vision.
|
/// and vision.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The app carried two dozen of these names in a list of its own until the catalog became the
|
||||||
|
/// only source. Three went with it and are not replaced: qwen-max-latest, qwen-plus-latest and
|
||||||
|
/// qwen-turbo-latest are a naming convention Alibaba has left behind -- its rolling names carry
|
||||||
|
/// no suffix now, and the tier once called turbo is called flash. The ones which stayed are
|
||||||
|
/// here for the other reason: no rule spells any of them out, so they say what becomes of a
|
||||||
|
/// name the rules were not written for.
|
||||||
|
/// </remarks>
|
||||||
private static readonly CorpusEntry[] ALIBABA_ENTRIES =
|
private static readonly CorpusEntry[] ALIBABA_ENTRIES =
|
||||||
[
|
[
|
||||||
new(ALIBABA_CLOUD, "qwq-plus", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qwq-plus", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen-max-latest", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qvq-max", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen-plus-latest", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qwen-vl-max", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen-turbo-latest", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qwen-mt-plus", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qvq-max", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qwen2.5-72b-instruct", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen-vl-max", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qwen2.5-14b-instruct-1m", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen-mt-plus", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qwen2.5-omni-7b", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen2.5-72b-instruct", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "qwen2.5-vl-72b-instruct", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen2.5-14b-instruct-1m", BUILT_INTO_THE_APP),
|
new(ALIBABA_CLOUD, "text-embedding-v3", NAMED_BY_NO_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen2.5-omni-7b", BUILT_INTO_THE_APP),
|
|
||||||
new(ALIBABA_CLOUD, "qwen2.5-vl-72b-instruct", BUILT_INTO_THE_APP),
|
|
||||||
new(ALIBABA_CLOUD, "text-embedding-v3", BUILT_INTO_THE_APP),
|
|
||||||
new(ALIBABA_CLOUD, "qwen3-omni-flash", NAMED_BY_A_RULE),
|
new(ALIBABA_CLOUD, "qwen3-omni-flash", NAMED_BY_A_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen3-vl-plus", NAMED_BY_A_RULE),
|
new(ALIBABA_CLOUD, "qwen3-vl-plus", NAMED_BY_A_RULE),
|
||||||
new(ALIBABA_CLOUD, "qwen3-235b-a22b", NAMED_BY_A_RULE),
|
new(ALIBABA_CLOUD, "qwen3-235b-a22b", NAMED_BY_A_RULE),
|
||||||
|
|||||||
@ -33,6 +33,39 @@ public static class ModelKindCorpus
|
|||||||
|
|
||||||
// The one name whose only marker used to be the organization it was published under:
|
// The one name whose only marker used to be the organization it was published under:
|
||||||
new(SELF_HOSTED, "sentence-transformers/all-MiniLM-L6-v2", EMBEDDING),
|
new(SELF_HOSTED, "sentence-transformers/all-MiniLM-L6-v2", EMBEDDING),
|
||||||
|
|
||||||
|
// Mistral's embedding checkpoint for code. It carries the name of a family which is a text
|
||||||
|
// completion model on this very provider, and stays an embedding model regardless: what a
|
||||||
|
// model is for is said by the word which says it, not by the family it was built from.
|
||||||
|
new(MISTRAL, "codestral-embed", EMBEDDING),
|
||||||
|
|
||||||
|
// Alibaba names its own by the prefix the provider used to filter the catalog by. The rule
|
||||||
|
// says it now, so the prefix is free to go:
|
||||||
|
new(ALIBABA_CLOUD, "text-embedding-v3", EMBEDDING),
|
||||||
|
new(ALIBABA_CLOUD, "text-embedding-v4", EMBEDDING),
|
||||||
|
|
||||||
|
//
|
||||||
|
// What a local Ollama installation serves, taken off its models endpoint rather than
|
||||||
|
// written from memory. The last two are the ones worth having: neither name carries the
|
||||||
|
// word "embed", so both were lost by the phrase the self-hosted provider used to filter
|
||||||
|
// with, and turned up among the chat models instead. Here they are answered by "bge" and
|
||||||
|
// "minilm", which is what those words are written for.
|
||||||
|
//
|
||||||
|
new(SELF_HOSTED, "qwen3-embedding:0.6b", EMBEDDING),
|
||||||
|
new(SELF_HOSTED, "qwen3-embedding:latest", EMBEDDING),
|
||||||
|
new(SELF_HOSTED, "nomic-embed-text:latest", EMBEDDING),
|
||||||
|
new(SELF_HOSTED, "bge-m3:latest", EMBEDDING),
|
||||||
|
new(SELF_HOSTED, "all-minilm:latest", EMBEDDING),
|
||||||
|
|
||||||
|
//
|
||||||
|
// The four whose names say nothing about embedding at all. They are here because the list
|
||||||
|
// above answers them through a word they happen to carry, and these carry none: without a
|
||||||
|
// rule of their own they would count as chat models, which is where they stood.
|
||||||
|
//
|
||||||
|
new(SELF_HOSTED, "stella_en_400M_v5", EMBEDDING),
|
||||||
|
new(SELF_HOSTED, "LaBSE", EMBEDDING),
|
||||||
|
new(SELF_HOSTED, "instructor-xl", EMBEDDING),
|
||||||
|
new(SELF_HOSTED, "gtr-t5-large", EMBEDDING),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -57,6 +90,12 @@ public static class ModelKindCorpus
|
|||||||
new(GOOGLE, "gemini-3-pro-image", IMAGE_GENERATION),
|
new(GOOGLE, "gemini-3-pro-image", IMAGE_GENERATION),
|
||||||
|
|
||||||
new(GOOGLE, "imagen-4.0-generate-001", IMAGE_GENERATION, AnsweredTodayAs: CHAT, Reason: "The markers never knew the name; the family ported in the Google step states it. Nobody noticed because the Google provider shows only names beginning with gemini."),
|
new(GOOGLE, "imagen-4.0-generate-001", IMAGE_GENERATION, AnsweredTodayAs: CHAT, Reason: "The markers never knew the name; the family ported in the Google step states it. Nobody noticed because the Google provider shows only names beginning with gemini."),
|
||||||
|
|
||||||
|
new(ALIBABA_CLOUD, "qwen-image-edit", IMAGE_GENERATION),
|
||||||
|
|
||||||
|
// Google ships one image model under a codename instead of a description. Nothing in it
|
||||||
|
// says drawing, and the provider only ever saw it because its catalog was read whole.
|
||||||
|
new(GOOGLE, "nano-banana-pro-preview", IMAGE_GENERATION),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,6 +121,29 @@ public static class ModelKindCorpus
|
|||||||
new(SELF_HOSTED, "parakeet-tdt-0.6b-v2", TRANSCRIPTION),
|
new(SELF_HOSTED, "parakeet-tdt-0.6b-v2", TRANSCRIPTION),
|
||||||
new(SELF_HOSTED, "wav2vec2-large-xlsr-53", TRANSCRIPTION),
|
new(SELF_HOSTED, "wav2vec2-large-xlsr-53", TRANSCRIPTION),
|
||||||
new(MISTRAL, "voxtral-mini-latest", TRANSCRIPTION),
|
new(MISTRAL, "voxtral-mini-latest", TRANSCRIPTION),
|
||||||
|
|
||||||
|
// The rest of what Mistral actually serves, off its own catalog. The app offered the one
|
||||||
|
// name above alone, because that is the one the documentation names; these three were there
|
||||||
|
// the whole time. Both sizes come as a rolling name and as a dated snapshot.
|
||||||
|
new(MISTRAL, "voxtral-small-latest", TRANSCRIPTION),
|
||||||
|
new(MISTRAL, "voxtral-mini-2602", TRANSCRIPTION),
|
||||||
|
new(MISTRAL, "voxtral-small-2507", TRANSCRIPTION),
|
||||||
|
|
||||||
|
// NVIDIA's other speech line, once plain and once as the hub names it. The second one is
|
||||||
|
// what makes the rule a segment worth keeping: the organization comes off before any rule
|
||||||
|
// sees the name, so what is left has to carry the word on its own.
|
||||||
|
new(SELF_HOSTED, "canary-1b-flash", TRANSCRIPTION),
|
||||||
|
new(SELF_HOSTED, "nvidia/canary-180m-flash", TRANSCRIPTION),
|
||||||
|
|
||||||
|
//
|
||||||
|
// Alibaba's speech line. Every one of these begins with the letter the Alibaba Cloud
|
||||||
|
// provider kept its whole chat list by, so all of them stood among the models somebody
|
||||||
|
// talks to. The last one carries two words at once, and the one which decides is not the
|
||||||
|
// longer one.
|
||||||
|
//
|
||||||
|
new(ALIBABA_CLOUD, "qwen3-asr-flash", TRANSCRIPTION),
|
||||||
|
new(ALIBABA_CLOUD, "qwen3-asr-1.7b", TRANSCRIPTION),
|
||||||
|
new(ALIBABA_CLOUD, "qwen-audio-3.0-asr-flash-streaming", TRANSCRIPTION),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -97,6 +159,13 @@ public static class ModelKindCorpus
|
|||||||
// The one name which glues the word to something else, and the reason the three words are
|
// The one name which glues the word to something else, and the reason the three words are
|
||||||
// not loosened into substrings:
|
// not loosened into substrings:
|
||||||
new(SELF_HOSTED, "xtts-v2", SPEECH_SYNTHESIS),
|
new(SELF_HOSTED, "xtts-v2", SPEECH_SYNTHESIS),
|
||||||
|
|
||||||
|
new(ALIBABA_CLOUD, "qwen-tts", SPEECH_SYNTHESIS),
|
||||||
|
|
||||||
|
// The Voxtral which speaks instead of listening. It stands here to hold the other half of
|
||||||
|
// Mistral's transcription list: the catalog is asked now, so what is not a transcription
|
||||||
|
// model has to be kept out by what it is, not by the list having been short.
|
||||||
|
new(MISTRAL, "voxtral-mini-tts-latest", SPEECH_SYNTHESIS),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -111,6 +180,75 @@ public static class ModelKindCorpus
|
|||||||
new(OPEN_AI, "gpt-realtime-whisper", REALTIME),
|
new(OPEN_AI, "gpt-realtime-whisper", REALTIME),
|
||||||
|
|
||||||
new(OPEN_AI, "gpt-live-1", REALTIME, AnsweredTodayAs: CHAT, Reason: "Found in the chat list while testing. The line which succeeds the realtime models dropped the word, and it is even less of a chat partner: it listens and speaks at once and leaves the thinking to a text model behind it."),
|
new(OPEN_AI, "gpt-live-1", REALTIME, AnsweredTodayAs: CHAT, Reason: "Found in the chat list while testing. The line which succeeds the realtime models dropped the word, and it is even less of a chat partner: it listens and speaks at once and leaves the thinking to a text model behind it."),
|
||||||
|
|
||||||
|
//
|
||||||
|
// Alibaba builds the word into both of its speech lines, and both are here to hold the
|
||||||
|
// rank the realtime rule carries: a connection AI Studio cannot open stays out of every
|
||||||
|
// list, whether the model would otherwise have spoken or listened.
|
||||||
|
//
|
||||||
|
new(ALIBABA_CLOUD, "qwen-tts-realtime", REALTIME),
|
||||||
|
new(ALIBABA_CLOUD, "qwen3-asr-flash-realtime", REALTIME),
|
||||||
|
|
||||||
|
// The other two of Mistral's Voxtral line. The second one carries "transcribe" and stays
|
||||||
|
// out of the transcription list all the same: whatever it does, it does over a connection
|
||||||
|
// the app cannot open, and that is what the rank on the realtime rule is for.
|
||||||
|
new(MISTRAL, "voxtral-mini-realtime-latest", REALTIME),
|
||||||
|
new(MISTRAL, "voxtral-mini-transcribe-realtime-2602", REALTIME),
|
||||||
|
|
||||||
|
//
|
||||||
|
// Google's whole two-way line, taken off its catalog rather than written from memory. It
|
||||||
|
// uses the other word for the thing OpenAI calls realtime, so none of these was recognized
|
||||||
|
// and all four stood among the models to talk to -- which none of them can be. The last
|
||||||
|
// one translates between two people speaking, which is as far from a chat as it gets.
|
||||||
|
//
|
||||||
|
new(GOOGLE, "gemini-3.8-live", REALTIME),
|
||||||
|
new(GOOGLE, "gemini-3.8-live-extended-thinking", REALTIME),
|
||||||
|
new(GOOGLE, "gemini-3.1-flash-live-preview", REALTIME),
|
||||||
|
new(GOOGLE, "gemini-3.5-live-translate-preview", REALTIME),
|
||||||
|
|
||||||
|
// Google's experimental music model, which is recognized already: it carries the other
|
||||||
|
// word, and this holds that the two rules do not fall out with each other.
|
||||||
|
new(GOOGLE, "lyria-realtime-exp", REALTIME),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The models which write music.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// All four off Google's own catalog. They were never seen because the provider showed only
|
||||||
|
/// names beginning with "gemini" -- the same prefix which kept Gemma out, which is why it had
|
||||||
|
/// to go and why these needed a rule of their own before it could.
|
||||||
|
/// </remarks>
|
||||||
|
private static readonly ModelKindExample[] MUSIC_ENTRIES =
|
||||||
|
[
|
||||||
|
new(GOOGLE, "lyria-3.5", MUSIC_GENERATION),
|
||||||
|
new(GOOGLE, "lyria-3-pro-preview", MUSIC_GENERATION),
|
||||||
|
new(GOOGLE, "lyria-3-clip-preview", MUSIC_GENERATION),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The models which are handed a job instead of a message.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The last two are the point of binding these rules to Google. Perplexity sells deep research
|
||||||
|
/// as well, and what it sells is a chat model -- so the same two words have to mean different
|
||||||
|
/// things at different providers, which is exactly what a binding is for.
|
||||||
|
/// </remarks>
|
||||||
|
private static readonly ModelKindExample[] AGENT_ENTRIES =
|
||||||
|
[
|
||||||
|
new(GOOGLE, "deep-research-preview-04-2026", AGENT),
|
||||||
|
new(GOOGLE, "deep-research-max-preview-04-2026", AGENT),
|
||||||
|
new(GOOGLE, "deep-research-pro-preview-12-2025", AGENT),
|
||||||
|
new(GOOGLE, "antigravity-preview-05-2026", AGENT),
|
||||||
|
new(GOOGLE, "antigravity-preview-09-2026", AGENT),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The model which answers out of what it was handed, and says where the answer came from.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ModelKindExample[] GROUNDED_ANSWERING_ENTRIES =
|
||||||
|
[
|
||||||
|
new(GOOGLE, "aqa", GROUNDED_ANSWERING),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -122,13 +260,23 @@ public static class ModelKindCorpus
|
|||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The models from before chat completions existed.
|
/// The models which continue a text instead of answering in a conversation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ModelKindExample[] TEXT_COMPLETION_ENTRIES =
|
private static readonly ModelKindExample[] TEXT_COMPLETION_ENTRIES =
|
||||||
[
|
[
|
||||||
new(HELMHOLTZ, "text-davinci-003", TEXT_COMPLETION),
|
new(HELMHOLTZ, "text-davinci-003", TEXT_COMPLETION),
|
||||||
|
|
||||||
|
// The model Mistral serves for filling a gap in a file. Its name does not begin with the
|
||||||
|
// word the provider used to sort its list by, so it stood among the chat models -- next to
|
||||||
|
// Codestral, which does the same job and was kept out by that very word.
|
||||||
|
new(MISTRAL, "mistral-code-fim-latest", TEXT_COMPLETION),
|
||||||
new(OPEN_AI, "babbage-002", TEXT_COMPLETION),
|
new(OPEN_AI, "babbage-002", TEXT_COMPLETION),
|
||||||
new(OPEN_AI, "gpt-3.5-turbo-instruct", TEXT_COMPLETION),
|
new(OPEN_AI, "gpt-3.5-turbo-instruct", TEXT_COMPLETION),
|
||||||
|
|
||||||
|
// The one of these which is not old: Mistral serves Codestral to fill in the middle of a
|
||||||
|
// file. It says so only for Mistral's own catalog, which is why the open weights of the
|
||||||
|
// same name stay a chat model further down.
|
||||||
|
new(MISTRAL, "codestral-latest", TEXT_COMPLETION),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -137,6 +285,7 @@ public static class ModelKindCorpus
|
|||||||
private static readonly ModelKindExample[] OCR_ENTRIES =
|
private static readonly ModelKindExample[] OCR_ENTRIES =
|
||||||
[
|
[
|
||||||
new(MISTRAL, "mistral-ocr-latest", OCR),
|
new(MISTRAL, "mistral-ocr-latest", OCR),
|
||||||
|
new(ALIBABA_CLOUD, "qwen-vl-ocr", OCR),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -173,6 +322,67 @@ public static class ModelKindCorpus
|
|||||||
new(SELF_HOSTED, "llama3.3:70b", CHAT),
|
new(SELF_HOSTED, "llama3.3:70b", CHAT),
|
||||||
new(OPEN_AI, "gpt-5.1", CHAT),
|
new(OPEN_AI, "gpt-5.1", CHAT),
|
||||||
|
|
||||||
|
// The open weights of the model Mistral itself serves to fill in the middle of a file.
|
||||||
|
// Whoever runs them runs them behind a chat completion API, so here the name means
|
||||||
|
// something to talk to -- which is what binding that other rule to Mistral protects.
|
||||||
|
new(SELF_HOSTED, "codestral-22b-v0.1", CHAT),
|
||||||
|
|
||||||
|
// The other half of that same Ollama installation, and the pair which makes the point:
|
||||||
|
// qwen3.8 and qwen3-embedding are one family and two answers. A rule written to select
|
||||||
|
// rather than to modify would have to beat the family name to get there.
|
||||||
|
new(SELF_HOSTED, "qwen3.8:latest", CHAT),
|
||||||
|
new(SELF_HOSTED, "gpt-oss:latest", CHAT),
|
||||||
|
|
||||||
|
// Half the chat models of the world carry this word, and one of the embedding names above
|
||||||
|
// is one letter longer than it. A name part is what keeps the two apart:
|
||||||
|
new(SELF_HOSTED, "mistral-7b-instruct", CHAT),
|
||||||
|
|
||||||
|
//
|
||||||
|
// What somebody actually comes to Alibaba Cloud for, held here because the provider is
|
||||||
|
// about to stop keeping its chat list by the letter every one of these begins with. The
|
||||||
|
// last one translates rather than converses, and it does so through the chat completion
|
||||||
|
// API like the others, so this is where it belongs.
|
||||||
|
//
|
||||||
|
new(ALIBABA_CLOUD, "qwen3.8-max", CHAT),
|
||||||
|
new(ALIBABA_CLOUD, "qwq-plus", CHAT),
|
||||||
|
new(ALIBABA_CLOUD, "qvq-max", CHAT),
|
||||||
|
new(ALIBABA_CLOUD, "qwen-mt-turbo", CHAT),
|
||||||
|
|
||||||
|
//
|
||||||
|
// Perplexity's whole catalog, which the app carries as a list because there is no route to
|
||||||
|
// ask. A list somebody picked by hand is not filtered at runtime -- a filter over it could
|
||||||
|
// only ever take a model away, never find one -- so it is held here instead: a rule which
|
||||||
|
// turns one of these into something other than a chat model fails the build rather than
|
||||||
|
// quietly emptying the dropdown.
|
||||||
|
//
|
||||||
|
new(PERPLEXITY, "sonar", CHAT),
|
||||||
|
new(PERPLEXITY, "sonar-pro", CHAT),
|
||||||
|
new(PERPLEXITY, "sonar-reasoning", CHAT),
|
||||||
|
new(PERPLEXITY, "sonar-reasoning-pro", CHAT),
|
||||||
|
new(PERPLEXITY, "sonar-deep-research", CHAT),
|
||||||
|
|
||||||
|
//
|
||||||
|
// The other two deep research models of the world, and the reason Google's rule is bound
|
||||||
|
// to Google and written as a prefix. Perplexity and OpenAI both sell something under that
|
||||||
|
// name which answers over the API the app already speaks, so both stay chat models. Only
|
||||||
|
// Google's own line, whose names start with the words, is handed a job instead.
|
||||||
|
//
|
||||||
|
new(OPEN_AI, "o3-deep-research", CHAT),
|
||||||
|
new(OPEN_AI, "o4-mini-deep-research", CHAT),
|
||||||
|
|
||||||
|
//
|
||||||
|
// The counter-sample to the three rules above, taken off the same three catalogs. Every
|
||||||
|
// one of these carries a word which now means something -- code, live, image -- without
|
||||||
|
// being what that word says, and every one of them has to stay a chat model.
|
||||||
|
//
|
||||||
|
new(MISTRAL, "mistral-code-latest", CHAT),
|
||||||
|
new(MISTRAL, "mistral-vibe-cli-latest", CHAT),
|
||||||
|
new(MISTRAL, "zai-glm-latest", CHAT),
|
||||||
|
new(GOOGLE, "gemma-4-31b-it", CHAT),
|
||||||
|
new(GOOGLE, "gemini-3.8-flash", CHAT),
|
||||||
|
new(X, "grok-4.6", CHAT),
|
||||||
|
new(X, "grok-build-0.1", CHAT),
|
||||||
|
|
||||||
//
|
//
|
||||||
// Three which were questioned while testing and stay all the same. Grok Build is the coding
|
// Three which were questioned while testing and stay all the same. Grok Build is the coding
|
||||||
// model behind the xAI CLI and answers like any other Grok. The Groq compound systems are
|
// model behind the xAI CLI and answers like any other Grok. The Groq compound systems are
|
||||||
@ -199,6 +409,9 @@ public static class ModelKindCorpus
|
|||||||
..TRANSCRIPTION_ENTRIES,
|
..TRANSCRIPTION_ENTRIES,
|
||||||
..SPEECH_ENTRIES,
|
..SPEECH_ENTRIES,
|
||||||
..REALTIME_ENTRIES,
|
..REALTIME_ENTRIES,
|
||||||
|
..MUSIC_ENTRIES,
|
||||||
|
..AGENT_ENTRIES,
|
||||||
|
..GROUNDED_ANSWERING_ENTRIES,
|
||||||
..COMPUTER_USE_ENTRIES,
|
..COMPUTER_USE_ENTRIES,
|
||||||
..TEXT_COMPLETION_ENTRIES,
|
..TEXT_COMPLETION_ENTRIES,
|
||||||
..OCR_ENTRIES,
|
..OCR_ENTRIES,
|
||||||
|
|||||||
135
app/Tests/Provider/SelfHostedModelListTests.cs
Normal file
135
app/Tests/Provider/SelfHostedModelListTests.cs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using AIStudio.Provider;
|
||||||
|
using AIStudio.Settings;
|
||||||
|
|
||||||
|
using SelfHostedModelsResponse = AIStudio.Provider.SelfHosted.ModelsResponse;
|
||||||
|
|
||||||
|
namespace AIStudio.Tests.Provider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks how the models of somebody's own server are sorted into the three lists they are offered in.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The engines answer one route with everything they serve, and that answer says nothing about what
|
||||||
|
/// any of it is made for: an ID, the word "model", and at Ollama a timestamp. Which list a model
|
||||||
|
/// ends up in is therefore decided afterwards, and for a long time it was decided by looking for
|
||||||
|
/// the word "embed" in the name -- the chat list was everything without it, the embedding list
|
||||||
|
/// everything with it, and the transcription list was not filtered at all.
|
||||||
|
///
|
||||||
|
/// The body below is the real answer of a local Ollama, copied off the route rather than written
|
||||||
|
/// from memory, and it holds the two names which that reading got wrong. What it costs is visible
|
||||||
|
/// in the assertions: an embedding model in the chat list is one somebody picks and then waits for
|
||||||
|
/// an answer which never comes.
|
||||||
|
/// </remarks>
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class SelfHostedModelListTests
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions AS_THE_PROVIDERS_READ_IT = new()
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What "GET /v1/models" answers on a local Ollama, shortened to the fields it sends.
|
||||||
|
/// </summary>
|
||||||
|
private const string WHAT_A_LOCAL_OLLAMA_ANSWERS =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"object": "list",
|
||||||
|
"data": [
|
||||||
|
{ "id": "all-minilm:latest", "object": "model", "created": 1789809739, "owned_by": "library" },
|
||||||
|
{ "id": "bge-m3:latest", "object": "model", "created": 1789809702, "owned_by": "library" },
|
||||||
|
{ "id": "qwen3-embedding:0.6b", "object": "model", "created": 1788699716, "owned_by": "library" },
|
||||||
|
{ "id": "qwen3-embedding:4b", "object": "model", "created": 1788699586, "owned_by": "library" },
|
||||||
|
{ "id": "qwen3-embedding:latest", "object": "model", "created": 1788699253, "owned_by": "library" },
|
||||||
|
{ "id": "qwen3.8:latest", "object": "model", "created": 1788698492, "owned_by": "library" },
|
||||||
|
{ "id": "gpt-oss:latest", "object": "model", "created": 1756645805, "owned_by": "library" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private static IReadOnlyList<Model> TheModelsTheEngineListed()
|
||||||
|
{
|
||||||
|
var response = JsonSerializer.Deserialize<SelfHostedModelsResponse>(WHAT_A_LOCAL_OLLAMA_ANSWERS, AS_THE_PROVIDERS_READ_IT);
|
||||||
|
Assert.That(response.Data, Is.Not.Null, "The answer has to be readable before anything can be sorted out of it.");
|
||||||
|
|
||||||
|
return response.Data!
|
||||||
|
.Where(model => !string.IsNullOrWhiteSpace(model.Id))
|
||||||
|
.Select(model => new Model(model.Id, null))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TheChatListHoldsWhatSomebodyCanTalkTo()
|
||||||
|
{
|
||||||
|
var chatModels = TheModelsTheEngineListed()
|
||||||
|
.Where(model => model.IsChatModel(LLMProviders.SELF_HOSTED))
|
||||||
|
.Select(model => model.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.That(chatModels, Is.EquivalentTo(new[] { "qwen3.8:latest", "gpt-oss:latest" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TheEmbeddingListHoldsTheModelsWhichSayNothingAboutEmbedding()
|
||||||
|
{
|
||||||
|
var embeddingModels = TheModelsTheEngineListed()
|
||||||
|
.Where(model => model.IsEmbeddingModel(LLMProviders.SELF_HOSTED))
|
||||||
|
.Select(model => model.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(embeddingModels, Does.Contain("bge-m3:latest"), "Named after the family which built it, with no word about what it does.");
|
||||||
|
Assert.That(embeddingModels, Does.Contain("all-minilm:latest"), "The same, and without the organization which used to be the only marker.");
|
||||||
|
Assert.That(embeddingModels, Has.Count.EqualTo(5), "The three Qwen embedding tags belong here as well, and nothing else does.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NothingIsInTwoListsAtOnce()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// The two lists were cut from one name with one word, so a model could only ever be in one
|
||||||
|
// of them. They are cut by two questions now, and two questions can both say yes.
|
||||||
|
//
|
||||||
|
var models = TheModelsTheEngineListed();
|
||||||
|
var inBothLists = models
|
||||||
|
.Where(model => model.IsChatModel(LLMProviders.SELF_HOSTED) && model.IsEmbeddingModel(LLMProviders.SELF_HOSTED))
|
||||||
|
.Select(model => model.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.That(inBothLists, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AnEngineWithoutASpeechModelOffersNoneForTranscription()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Ollama serves no speech-to-text model of its own, and the list said otherwise: it was
|
||||||
|
// handed through unfiltered, so all seven of these stood there to be picked.
|
||||||
|
//
|
||||||
|
var transcriptionModels = TheModelsTheEngineListed()
|
||||||
|
.Where(model => model.IsTranscriptionModel(LLMProviders.SELF_HOSTED))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.That(transcriptionModels, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ASpeechModelOnSuchAServerIsOfferedForTranscription()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// The other half of the one above: the empty list has to come from there being no speech
|
||||||
|
// model, not from the question never saying yes on this provider.
|
||||||
|
//
|
||||||
|
var models = new[] { "whisper-large-v3", "faster-whisper-large-v3", "canary-1b-flash" }
|
||||||
|
.Select(id => new Model(id, null))
|
||||||
|
.Where(model => model.IsTranscriptionModel(LLMProviders.SELF_HOSTED))
|
||||||
|
.Select(model => model.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.That(models, Has.Count.EqualTo(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -47,6 +47,41 @@ public sealed class EmbeddingChangeImpactTests
|
|||||||
"Another model means another vector space.");
|
"Another model means another vector space.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A model ID which differs only in how it is written is a different model here.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is why the embedding provider dialog adds a configured model to the list it loaded
|
||||||
|
/// instead of matching it against that list. A server which writes the same model slightly
|
||||||
|
/// differently -- with a tag where the user typed none, or in another case -- would otherwise
|
||||||
|
/// have its spelling written into the settings on the next save, and every document of every
|
||||||
|
/// data source behind that provider would be prepared again for a change nobody made.
|
||||||
|
/// </remarks>
|
||||||
|
[Test]
|
||||||
|
public void AModelIdWhichOnlyReadsDifferentlyDropsTheStoredIndexAsWell()
|
||||||
|
{
|
||||||
|
var dataSource = StoredDataSource();
|
||||||
|
var stored = StoredEmbeddingProvider() with { Model = new("nomic-embed-text", null) };
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(
|
||||||
|
EmbeddingChangeImpact.AffectsStoredIndex(dataSource, stored, stored with { Model = new("nomic-embed-text:latest", null) }),
|
||||||
|
Is.True,
|
||||||
|
"The tag a server appends is part of the ID, and the ID is part of the signature.");
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
EmbeddingChangeImpact.AffectsStoredIndex(dataSource, stored, stored with { Model = new("NOMIC-EMBED-TEXT", null) }),
|
||||||
|
Is.True,
|
||||||
|
"Compared ordinally, so another case is another model rather than the same one written louder.");
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
EmbeddingChangeImpact.AffectsStoredIndex(dataSource, stored, stored with { Model = new("nomic-embed-text", "Nomic Embed Text") }),
|
||||||
|
Is.False,
|
||||||
|
"The display name is decoration and reaches no vector, so loading the list may fill it in.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ChangingTheTokenLimitDropsTheStoredIndex()
|
public void ChangingTheTokenLimitDropsTheStoredIndex()
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user