Added reasoning behavior overrides and updated related UI

This commit is contained in:
Thorsten Sommer 2026-07-05 12:19:44 +02:00
parent 4a3c8992f4
commit 3d516a4674
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
7 changed files with 211 additions and 9 deletions

View File

@ -4396,6 +4396,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1356621346"] = "Create acco
-- Load models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T15352225"] = "Load models"
-- Automatic
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1634363268"] = "Automatic"
-- Disabled (Auto)
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1671157437"] = "Disabled (Auto)"
@ -4405,6 +4408,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1689135032"] = "Add the par
-- Hostname
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname"
-- Always on
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1761671861"] = "Always on"
-- Reset
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T180921696"] = "Reset"
@ -4450,12 +4456,27 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2810182573"] = "No models l
-- Instance Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instance Name"
-- On by default
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2843289040"] = "On by default"
-- No reasoning (thinking) capability.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T301695429"] = "No reasoning (thinking) capability."
-- Please be careful: wrong expert settings can break model usage, disable supported features, or make unsupported features appear available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3079061205"] = "Please be careful: wrong expert settings can break model usage, disable supported features, or make unsupported features appear available."
-- Reasoning (thinking) is available and on unless additional API parameters disable it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T310420667"] = "Reasoning (thinking) is available and on unless additional API parameters disable it."
-- Disabled
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3217987877"] = "Disabled"
-- The model always uses reasoning (thinking); it cannot be disabled.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3294757333"] = "The model always uses reasoning (thinking); it cannot be disabled."
-- Can be enabled
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3299454847"] = "Can be enabled"
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
@ -4465,12 +4486,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3404621481"] = "Audio input
-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3502745319"] = "Invalid JSON: Add the parameters in proper JSON formatting, e.g., \\\"temperature\\\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though."
-- Reasoning (thinking) behavior
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3546126752"] = "Reasoning (thinking) behavior"
-- Reasoning (thinking) is available, but off unless additional API parameters enable it.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3548835672"] = "Reasoning (thinking) is available, but off unless additional API parameters enable it."
-- Show available models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
-- This host uses the model configured at the provider level. No model selection is available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
-- Use detected model behavior.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3790739687"] = "Use detected model behavior."
-- Duplicate key '{0}' found.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3804472591"] = "Duplicate key '{0}' found."
@ -4501,6 +4531,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host"
-- Multiple image input
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T858529900"] = "Multiple image input"
-- No reasoning (thinking)
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T87434533"] = "No reasoning (thinking)"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T900237532"] = "Provider"

View File

@ -170,7 +170,7 @@
@T("Stored default model capabilities may not reflect its full range. Override them here if needed.")
</MudJustifiedText>
<MudStack Class="mb-4" Spacing="2">
@foreach (var capability in ProviderCapabilityOverrides.SUPPORTED_CAPABILITIES)
@foreach (var capability in SWITCH_CAPABILITY_OVERRIDES)
{
<MudPaper Outlined="@true" Class="pa-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap">
@ -199,6 +199,25 @@
</MudStack>
</MudPaper>
}
<MudPaper Outlined="@true" Class="pa-3">
<MudSelect T="ReasoningOverrideMode"
Value="@this.GetReasoningOverrideMode()"
ValueChanged="@this.SetReasoningOverrideMode"
Label="@T("Reasoning (thinking) behavior")"
HelperText="@this.GetReasoningOverrideModeDescription(this.GetReasoningOverrideMode())"
Variant="Variant.Outlined"
Margin="Margin.Dense"
OpenIcon="@Icons.Material.Filled.Psychology"
AdornmentColor="Color.Info"
Adornment="Adornment.Start">
@foreach (var mode in REASONING_OVERRIDE_MODES)
{
<MudSelectItem Value="@mode">
@this.GetReasoningOverrideModeLabel(mode)
</MudSelectItem>
}
</MudSelect>
</MudPaper>
</MudStack>
<MudJustifiedText Typo="Typo.body2" Class="mb-4">
@string.Format(T("The current model uses the {0}."), this.GetCurrentModelApiLabel())

View File

@ -19,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!;
@ -95,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.
@ -398,6 +423,80 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
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 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 => T("Use detected model behavior."),
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)

View File

@ -76,8 +76,11 @@ CONFIG["LLM_PROVIDERS"] = {}
--
-- -- Optional: expert capability overrides.
-- -- Allowed keys are exactly:
-- -- AUDIO_INPUT, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, VIDEO_INPUT, ALWAYS_REASONING
-- -- AUDIO_INPUT, MULTIPLE_IMAGE_INPUT, SPEECH_INPUT, VIDEO_INPUT,
-- -- OPTIONAL_REASONING, ALWAYS_REASONING, REASONING_BY_DEFAULT
-- -- Allowed values are booleans only.
-- -- For default-on reasoning (rhinking), set OPTIONAL_REASONING and REASONING_BY_DEFAULT to true.
-- -- ALWAYS_REASONING means the model cannot disable reasoning (thinking).
-- -- Missing keys keep the automatic capability detection result.
-- -- ["CapabilityOverrides"] = {
-- -- ["VIDEO_INPUT"] = false,

View File

@ -4399,7 +4399,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1356621346"] = "Konto erste
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T15352225"] = "Modelle laden"
-- Disabled (Auto)
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1671157437"] = "Deaktiviert (Automatisch)"
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1671157437"] = "Deaktiviert (automatisch)"
-- Add the parameters in proper JSON formatting, e.g., "temperature": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1689135032"] = "Fügen Sie die Parameter in korrekter JSON-Formatierung hinzu, z. B. \"temperature\": 0.5. Entfernen Sie abschließende Kommas. Die üblichen äußeren geschweiften Klammern {} dürfen dabei jedoch nicht verwendet werden."
@ -4423,7 +4423,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1874348907"] = "Spracheinga
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Bitte geben Sie einen Modellnamen ein."
-- Enabled (Auto)
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2001330464"] = "Aktiviert (Automatisch)"
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2001330464"] = "Aktiviert (automatisch)"
-- The current model uses the {0}.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2029870721"] = "Das aktuelle Modell nutzt die {0}."
@ -4495,7 +4495,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4289835208"] = "Videoeingab
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "Wir können derzeit nicht mit dem Anbieter kommunizieren, um Modelle zu laden. Bitte versuchen Sie es später erneut."
-- Always reasoning
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T641757736"] = "Nachdenkend"
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T641757736"] = "Immer schlussfolgernd (Denken)"
-- Host
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host"

View File

@ -15,13 +15,15 @@ namespace AIStudio.Settings;
/// </summary>
public sealed record ProviderCapabilityOverrides
{
public static readonly IReadOnlyList<Capability> SUPPORTED_CAPABILITIES =
private static readonly IReadOnlyList<Capability> SUPPORTED_CAPABILITIES =
[
Capability.AUDIO_INPUT,
Capability.MULTIPLE_IMAGE_INPUT,
Capability.SPEECH_INPUT,
Capability.VIDEO_INPUT,
Capability.ALWAYS_REASONING
Capability.OPTIONAL_REASONING,
Capability.ALWAYS_REASONING,
Capability.REASONING_BY_DEFAULT
];
[JsonPropertyName("AUDIO_INPUT")]
@ -40,17 +42,27 @@ public sealed record ProviderCapabilityOverrides
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? VideoInput { get; init; }
[JsonPropertyName("OPTIONAL_REASONING")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? OptionalReasoning { get; init; }
[JsonPropertyName("ALWAYS_REASONING")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? AlwaysReasoning { get; init; }
[JsonPropertyName("REASONING_BY_DEFAULT")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? ReasoningByDefault { get; init; }
[JsonIgnore]
public bool HasOverrides =>
this.AudioInput is not null ||
this.MultipleImageInput is not null ||
this.SpeechInput is not null ||
this.VideoInput is not null ||
this.AlwaysReasoning is not null;
this.OptionalReasoning is not null ||
this.AlwaysReasoning is not null ||
this.ReasoningByDefault is not null;
public bool? GetOverride(Capability capability) => capability switch
{
@ -58,7 +70,9 @@ public sealed record ProviderCapabilityOverrides
Capability.MULTIPLE_IMAGE_INPUT => this.MultipleImageInput,
Capability.SPEECH_INPUT => this.SpeechInput,
Capability.VIDEO_INPUT => this.VideoInput,
Capability.OPTIONAL_REASONING => this.OptionalReasoning,
Capability.ALWAYS_REASONING => this.AlwaysReasoning,
Capability.REASONING_BY_DEFAULT => this.ReasoningByDefault,
_ => null
};
@ -68,7 +82,9 @@ public sealed record ProviderCapabilityOverrides
Capability.MULTIPLE_IMAGE_INPUT => this with { MultipleImageInput = value },
Capability.SPEECH_INPUT => this with { SpeechInput = value },
Capability.VIDEO_INPUT => this with { VideoInput = value },
Capability.OPTIONAL_REASONING => this with { OptionalReasoning = value },
Capability.ALWAYS_REASONING => this with { AlwaysReasoning = value },
Capability.REASONING_BY_DEFAULT => this with { ReasoningByDefault = value },
_ => this
};
@ -84,9 +100,41 @@ public sealed record ProviderCapabilityOverrides
mergedCapabilities.Remove(capability);
}
this.NormalizeReasoningCapabilities(mergedCapabilities);
return mergedCapabilities;
}
private void NormalizeReasoningCapabilities(List<Capability> capabilities)
{
if (this.AlwaysReasoning == true ||
this.AlwaysReasoning is not false &&
this.OptionalReasoning is not true &&
this.ReasoningByDefault is not true &&
capabilities.Contains(Capability.ALWAYS_REASONING))
{
capabilities.Remove(Capability.OPTIONAL_REASONING);
capabilities.Remove(Capability.REASONING_BY_DEFAULT);
return;
}
if (this.AlwaysReasoning == false ||
this.OptionalReasoning == true ||
this.ReasoningByDefault == true)
capabilities.Remove(Capability.ALWAYS_REASONING);
if (this.OptionalReasoning == false)
{
capabilities.Remove(Capability.REASONING_BY_DEFAULT);
return;
}
if (this.ReasoningByDefault == true && !capabilities.Contains(Capability.OPTIONAL_REASONING))
capabilities.Add(Capability.OPTIONAL_REASONING);
if (!capabilities.Contains(Capability.OPTIONAL_REASONING))
capabilities.Remove(Capability.REASONING_BY_DEFAULT);
}
public string ExportAsLuaTable(string indentation)
{
if (!this.HasOverrides)

View File

@ -1,5 +1,5 @@
# v26.6.3, build 243 (2026-06-xx xx:xx UTC)
- Added expert capability overrides for providers, so advanced users and configuration plugins can manually enable or disable selected model capabilities when automatic detection needs adjustment.
- Added expert capability overrides for providers, so advanced users and configuration plugins can manually adjust selected model capabilities, including reasoning (thinking) behavior, when automatic detection needs adjustment.
- Improved the provider selection by showing small capability icons for supported audio, image, speech, and reasoning features of the selected model.
- Improved source links in chat answers when file or document names contained spaces, umlauts, or other special characters. Source entries now open much more reliably for documents with names such as PDFs from shared portals or internal knowledge bases.
- Improved all assistants, so running tasks can continue when you leave the assistant and return later.