Added an expert option to override model capabilities (#830)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
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

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Peer HogeterpandThorsten Sommer authored and GitHub committed 2026-07-05 12:42:55 +02:00
1 parent 431f0b78d8
commit e3afe7ff9b
12 files changed
+726 -27

No files matched your search

@@ -4,6 +4,7 @@ using System.Text.Json;
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Provider.HuggingFace;
using AIStudio.Settings;
using AIStudio.Tools.Services;
using AIStudio.Tools.Validation;
@@ -18,6 +19,15 @@ namespace AIStudio.Dialogs;
/// </summary>
public partial class ProviderDialog : MSGComponentBase, ISecretId
{
private enum ReasoningOverrideMode
{
AUTOMATIC,
NO_REASONING,
CAN_BE_ENABLED,
ON_BY_DEFAULT,
ALWAYS_ON
}
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -83,6 +93,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
[Parameter]
public string AdditionalJsonApiParameters { get; set; } = string.Empty;
[Parameter]
public ProviderCapabilityOverrides? DataCapabilityOverrides { get; set; }
[Inject]
private RustService RustService { get; init; } = null!;
@@ -91,6 +104,22 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private ILogger<ProviderDialog> Logger { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private static readonly IReadOnlyList<Capability> SWITCH_CAPABILITY_OVERRIDES =
[
Capability.AUDIO_INPUT,
Capability.MULTIPLE_IMAGE_INPUT,
Capability.SPEECH_INPUT,
Capability.VIDEO_INPUT
];
private static readonly IReadOnlyList<ReasoningOverrideMode> REASONING_OVERRIDE_MODES =
[
ReasoningOverrideMode.AUTOMATIC,
ReasoningOverrideMode.NO_REASONING,
ReasoningOverrideMode.CAN_BE_ENABLED,
ReasoningOverrideMode.ON_BY_DEFAULT,
ReasoningOverrideMode.ALWAYS_ON
];
/// <summary>
/// The list of used instance names. We need this to check for uniqueness.
@@ -106,6 +135,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private string dataLoadingModelsIssue = string.Empty;
private bool usesLegacySystemModelFallback;
private bool showExpertSettings;
private ProviderCapabilityOverrides capabilityOverrides = new();
// We get the form reference from Blazor code to validate it manually:
private MudForm form = null!;
@@ -160,6 +190,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
Host = this.DataHost,
HFInferenceProvider = this.HFInferenceProviderId,
AdditionalJsonApiParameters = this.AdditionalJsonApiParameters,
CapabilityOverrides = this.capabilityOverrides.HasOverrides ? this.capabilityOverrides : null,
};
}
@@ -178,7 +209,8 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
this.UsedInstanceNames = this.SettingsManager.ConfigurationData.Providers.Select(x => x.InstanceName.ToLowerInvariant()).ToList();
#pragma warning restore MWAIS0001
this.showExpertSettings = !string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters);
this.capabilityOverrides = this.DataCapabilityOverrides ?? new();
this.showExpertSettings = !string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters) || this.capabilityOverrides.HasOverrides;
// When editing, we need to load the data:
if(this.IsEditing)
@@ -300,10 +332,18 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
this.DataHost = selectedHost;
this.DataModel = default;
this.dataManuallyModel = string.Empty;
this.capabilityOverrides = new();
this.availableModels.Clear();
this.dataLoadingModelsIssue = string.Empty;
this.usesLegacySystemModelFallback = false;
}
private Task OnModelChanged(Model selectedModel)
{
this.DataModel = selectedModel;
this.capabilityOverrides = new();
return Task.CompletedTask;
}
private async Task ReloadModels()
{
@@ -369,6 +409,156 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private void ToggleExpertSettings() => this.showExpertSettings = !this.showExpertSettings;
private void SetCapabilityOverride(Capability capability, bool value)
{
this.capabilityOverrides = this.capabilityOverrides.SetOverride(capability, value);
}
private Task OnCapabilitySwitchChanged(Capability capability, bool value)
{
this.SetCapabilityOverride(capability, value);
return Task.CompletedTask;
}
private void ResetCapabilityOverride(Capability capability) =>
this.capabilityOverrides = this.capabilityOverrides.SetOverride(capability, null);
private ReasoningOverrideMode GetReasoningOverrideMode()
{
var alwaysReasoning = this.capabilityOverrides.GetOverride(Capability.ALWAYS_REASONING);
var optionalReasoning = this.capabilityOverrides.GetOverride(Capability.OPTIONAL_REASONING);
var reasoningByDefault = this.capabilityOverrides.GetOverride(Capability.REASONING_BY_DEFAULT);
if (alwaysReasoning is null && optionalReasoning is null && reasoningByDefault is null)
return ReasoningOverrideMode.AUTOMATIC;
var capabilities = this.GetCurrentModelCapabilities();
if (capabilities.Contains(Capability.ALWAYS_REASONING))
return ReasoningOverrideMode.ALWAYS_ON;
if (capabilities.Contains(Capability.REASONING_BY_DEFAULT))
return ReasoningOverrideMode.ON_BY_DEFAULT;
if (capabilities.Contains(Capability.OPTIONAL_REASONING))
return ReasoningOverrideMode.CAN_BE_ENABLED;
return ReasoningOverrideMode.NO_REASONING;
}
private ReasoningOverrideMode GetAutomaticReasoningOverrideMode()
{
var capabilities = this.GetAutomaticModelCapabilities();
if (capabilities.Contains(Capability.ALWAYS_REASONING))
return ReasoningOverrideMode.ALWAYS_ON;
if (capabilities.Contains(Capability.REASONING_BY_DEFAULT))
return ReasoningOverrideMode.ON_BY_DEFAULT;
if (capabilities.Contains(Capability.OPTIONAL_REASONING))
return ReasoningOverrideMode.CAN_BE_ENABLED;
return ReasoningOverrideMode.NO_REASONING;
}
private void SetReasoningOverrideMode(ReasoningOverrideMode mode)
{
this.capabilityOverrides = mode switch
{
ReasoningOverrideMode.AUTOMATIC => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, null)
.SetOverride(Capability.OPTIONAL_REASONING, null)
.SetOverride(Capability.REASONING_BY_DEFAULT, null),
ReasoningOverrideMode.NO_REASONING => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, false)
.SetOverride(Capability.OPTIONAL_REASONING, false)
.SetOverride(Capability.REASONING_BY_DEFAULT, false),
ReasoningOverrideMode.CAN_BE_ENABLED => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, false)
.SetOverride(Capability.OPTIONAL_REASONING, true)
.SetOverride(Capability.REASONING_BY_DEFAULT, false),
ReasoningOverrideMode.ON_BY_DEFAULT => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, false)
.SetOverride(Capability.OPTIONAL_REASONING, true)
.SetOverride(Capability.REASONING_BY_DEFAULT, true),
ReasoningOverrideMode.ALWAYS_ON => this.capabilityOverrides
.SetOverride(Capability.ALWAYS_REASONING, true)
.SetOverride(Capability.OPTIONAL_REASONING, false)
.SetOverride(Capability.REASONING_BY_DEFAULT, false),
_ => this.capabilityOverrides
};
}
private string GetReasoningOverrideModeLabel(ReasoningOverrideMode mode) => mode switch
{
ReasoningOverrideMode.AUTOMATIC => T("Automatic"),
ReasoningOverrideMode.NO_REASONING => T("No reasoning (thinking)"),
ReasoningOverrideMode.CAN_BE_ENABLED => T("Can be enabled"),
ReasoningOverrideMode.ON_BY_DEFAULT => T("On by default"),
ReasoningOverrideMode.ALWAYS_ON => T("Always on"),
_ => mode.ToString()
};
private string GetReasoningOverrideModeDescription(ReasoningOverrideMode mode) => mode switch
{
ReasoningOverrideMode.AUTOMATIC => string.Format(T("Use detected model behavior: {0}."), this.GetReasoningOverrideModeLabel(this.GetAutomaticReasoningOverrideMode())),
ReasoningOverrideMode.NO_REASONING => T("No reasoning (thinking) capability."),
ReasoningOverrideMode.CAN_BE_ENABLED => T("Reasoning (thinking) is available, but off unless additional API parameters enable it."),
ReasoningOverrideMode.ON_BY_DEFAULT => T("Reasoning (thinking) is available and on unless additional API parameters disable it."),
ReasoningOverrideMode.ALWAYS_ON => T("The model always uses reasoning (thinking); it cannot be disabled."),
_ => string.Empty
};
private bool HasCapabilityOverride(Capability capability) => this.capabilityOverrides.GetOverride(capability) is not null;
private bool IsCapabilityEnabled(Capability capability)
{
var capabilities = this.GetCurrentModelCapabilities();
return capabilities.Contains(capability);
}
private string GetCapabilityEffectiveLabel(Capability capability)
{
var isEnabled = this.IsCapabilityEnabled(capability);
if (this.HasCapabilityOverride(capability))
return isEnabled ? T("Enabled") : T("Disabled");
return isEnabled ? T("Enabled (Auto)") : T("Disabled (Auto)");
}
private List<Capability> GetCurrentModelCapabilities()
{
var currentProviderSettings = this.CreateProviderSettings();
return currentProviderSettings.GetModelCapabilities();
}
private List<Capability> GetAutomaticModelCapabilities() => this.DataLLMProvider.GetModelCapabilities(this.DataModel);
private string GetCurrentModelApiLabel()
{
var capabilities = this.GetCurrentModelCapabilities();
if (capabilities.Contains(Capability.RESPONSES_API))
return "Responses API";
if (capabilities.Contains(Capability.CHAT_COMPLETION_API))
return "Chat Completions API";
return "Unknown";
}
private string GetCapabilityOverrideLabel(Capability capability) => capability switch
{
Capability.AUDIO_INPUT => T("Audio input"),
Capability.MULTIPLE_IMAGE_INPUT => T("Multiple image input"),
Capability.SPEECH_INPUT => T("Speech input"),
Capability.VIDEO_INPUT => T("Video input"),
Capability.ALWAYS_REASONING => T("Always reasoning"),
_ => capability.ToString()
};
private void OnInputChangeExpertSettings()
{
this.AdditionalJsonApiParameters = NormalizeAdditionalJsonApiParameters(this.AdditionalJsonApiParameters)
@@ -536,7 +726,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
}
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
private static string GetPlaceholderExpertSettings =>
"""
"temperature": 0.5,