diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 88e1ec20..45fabf86 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -81,7 +81,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable protected override async Task OnInitializedAsync() { // Apply the filters for the message bus: - this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED ]); + this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED, Event.CONFIGURATION_CHANGED ]); // Configure the spellchecking for the user input: this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); @@ -1007,6 +1007,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable case Event.WORKSPACE_LOADED_CHAT_CHANGED: await this.LoadedChatChanged(); break; + + case Event.CONFIGURATION_CHANGED: + await this.InvokeAsync(this.StateHasChanged); + break; } } diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor index c10c1983..238b0133 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor @@ -1,3 +1,4 @@ +@using AIStudio.Provider @using AIStudio.Tools.ToolCallingSystem @inherits SettingsPanelBase @@ -11,6 +12,7 @@ @T("Icon") @T("Name") @T("Description") + @T("Minimum provider confidence") @T("State") @T("Settings") @@ -24,6 +26,16 @@ @context.Implementation.GetDescription() + + + @foreach (var confidenceLevel in this.GetSelectableConfidenceLevels()) + { + + @this.GetConfidenceLevelName(confidenceLevel) + + } + + @if (context.ConfigurationState.IsConfigured) { diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs index c978d16c..36b0e7a8 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs @@ -1,4 +1,6 @@ +using AIStudio.Provider; using AIStudio.Dialogs.Settings; +using AIStudio.Settings; using AIStudio.Tools; using AIStudio.Tools.ToolCallingSystem; @@ -15,8 +17,9 @@ public partial class SettingsPanelTools : SettingsPanelBase protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions()); + await base.OnInitializedAsync(); } private async Task OpenSettings(string toolId) @@ -47,4 +50,40 @@ public partial class SettingsPanelTools : SettingsPanelBase return item.Implementation.GetSettingsFieldLabel(fieldName, fieldDefinition); } + + private IEnumerable GetSelectableConfidenceLevels() => + Enum.GetValues().OrderBy(x => x).Where(x => x is not ConfidenceLevel.UNKNOWN); + + private string GetCurrentConfidenceLevelName(ToolCatalogItem item) => this.GetConfidenceLevelName(this.GetMinimumProviderConfidence(item)); + + private string GetConfidenceLevelName(ConfidenceLevel confidenceLevel) => confidenceLevel is ConfidenceLevel.NONE + ? this.T("No minimum confidence level chosen") + : confidenceLevel.GetName(); + + private string SetCurrentConfidenceLevelColorStyle(ToolCatalogItem item) => + $"background-color: {this.GetMinimumProviderConfidence(item).GetColor(this.SettingsManager)};"; + + private bool IsToolConfidenceManaged() => + ManagedConfiguration.TryGet(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, out var meta) && meta.IsLocked; + + private ConfidenceLevel GetMinimumProviderConfidence(ToolCatalogItem item) => this.SettingsManager.GetMinimumProviderConfidenceForTool(item.Definition.Id); + + private async Task ChangeMinimumProviderConfidence(ToolCatalogItem item, ConfidenceLevel confidenceLevel) + { + this.SettingsManager.SetMinimumProviderConfidenceForTool(item.Definition.Id, confidenceLevel); + await this.SettingsManager.StoreSettings(); + this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions()); + await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); + } + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.CONFIGURATION_CHANGED: + this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions()); + await this.InvokeAsync(this.StateHasChanged); + break; + } + } } diff --git a/app/MindWork AI Studio/Components/ToolSelection.razor b/app/MindWork AI Studio/Components/ToolSelection.razor index a98f02e2..23613c26 100644 --- a/app/MindWork AI Studio/Components/ToolSelection.razor +++ b/app/MindWork AI Studio/Components/ToolSelection.razor @@ -40,10 +40,12 @@ var isSelected = this.SelectedToolIds.Contains(item.Definition.Id); var isConfigured = item.ConfigurationState.IsConfigured; var dependencyHint = this.GetDependencyHint(item.Definition.Id); + var providerConfidenceHint = this.GetProviderConfidenceHint(item); + var isBlockedByProviderConfidence = this.IsBlockedByProviderConfidence(item); - + @item.Implementation.GetDisplayName() @@ -59,6 +61,10 @@ { @dependencyHint } + @if (!string.IsNullOrWhiteSpace(providerConfidenceHint)) + { + @providerConfidenceHint + } } } diff --git a/app/MindWork AI Studio/Components/ToolSelection.razor.cs b/app/MindWork AI Studio/Components/ToolSelection.razor.cs index 7f96fb06..0f81eb0c 100644 --- a/app/MindWork AI Studio/Components/ToolSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ToolSelection.razor.cs @@ -43,11 +43,21 @@ public partial class ToolSelection : MSGComponentBase base.OnParametersSet(); } + protected override async Task OnInitializedAsync() + { + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); + await base.OnInitializedAsync(); + } + private bool SupportsTools => this.LLMProvider != AIStudio.Settings.Provider.NONE && this.LLMProvider.GetModelCapabilities().Contains(Capability.CHAT_COMPLETION_API) && this.LLMProvider.GetModelCapabilities().Contains(Capability.FUNCTION_CALLING); + private ConfidenceLevel ProviderConfidence => this.LLMProvider == AIStudio.Settings.Provider.NONE + ? ConfidenceLevel.NONE + : this.LLMProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level; + private async Task ToggleSelection() { this.showSelection = !this.showSelection; @@ -72,6 +82,10 @@ public partial class ToolSelection : MSGComponentBase private bool IsSelectionLockedByDependency(string toolId) => ToolSelectionRules.IsRequiredBySelectedTools(toolId, this.SelectedToolIds); + private ConfidenceLevel GetMinimumProviderConfidence(ToolCatalogItem item) => this.SettingsManager.GetMinimumProviderConfidenceForTool(item.Definition.Id); + + private bool IsBlockedByProviderConfidence(ToolCatalogItem item) => !ToolSelectionRules.IsProviderConfidenceAllowed(this.ProviderConfidence, this.GetMinimumProviderConfidence(item)); + private string? GetDependencyHint(string toolId) { if (toolId == ToolSelectionRules.WEB_SEARCH_TOOL_ID) @@ -83,6 +97,17 @@ public partial class ToolSelection : MSGComponentBase return null; } + private string? GetProviderConfidenceHint(ToolCatalogItem item) + { + if (!this.IsBlockedByProviderConfidence(item)) + return null; + + return string.Format( + this.T("This tool requires provider confidence {0}. The selected provider has {1}."), + this.GetMinimumProviderConfidence(item).GetName(), + this.ProviderConfidence.GetName()); + } + private async Task OpenSettings(string toolId) { var parameters = new DialogParameters @@ -95,4 +120,15 @@ public partial class ToolSelection : MSGComponentBase this.catalog = await this.ToolRegistry.GetCatalogAsync(this.Component); this.StateHasChanged(); } + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.CONFIGURATION_CHANGED when this.showSelection: + this.catalog = await this.ToolRegistry.GetCatalogAsync(this.Component); + await this.InvokeAsync(this.StateHasChanged); + break; + } + } } diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index 5f717d8b..3d7d280e 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -5,6 +5,7 @@ using System.Text.Json; using AIStudio.Chat; using AIStudio.Settings; +using AIStudio.Tools.ToolCallingSystem; namespace AIStudio.Provider.OpenAI; @@ -78,11 +79,12 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https // // Prepare the tools we want to use: // - IList providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) switch - { - true => [ ProviderTools.WEB_SEARCH ], - _ => [] - }; + var providerConfidence = this.Provider.GetConfidence(settingsManager).Level; + var minimumWebSearchConfidence = settingsManager.GetMinimumProviderConfidenceForTool(ToolSelectionRules.WEB_SEARCH_TOOL_ID); + var isWebSearchAllowed = ToolSelectionRules.IsProviderConfidenceAllowed(providerConfidence, minimumWebSearchConfidence); + IList providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) && isWebSearchAllowed + ? [ ProviderTools.WEB_SEARCH ] + : []; // Parse the API parameters: diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs index f90cf538..05f718c0 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Text.Json.Nodes; +using AIStudio.Provider; using AIStudio.Settings; namespace AIStudio.Tools.ToolCallingSystem; @@ -90,6 +91,8 @@ public sealed class ToolCatalogItem public required IToolImplementation Implementation { get; init; } public required ToolConfigurationState ConfigurationState { get; init; } + + public ConfidenceLevel MinimumProviderConfidence { get; init; } = ConfidenceLevel.NONE; } public sealed class ToolSelectionState diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs index ea4732d2..9b95162f 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs @@ -1,6 +1,7 @@ using System.Text.Json; using AIStudio.Provider; +using AIStudio.Settings; using Microsoft.AspNetCore.Hosting; @@ -9,6 +10,7 @@ namespace AIStudio.Tools.ToolCallingSystem; public sealed class ToolRegistry { private readonly ILogger logger; + private readonly SettingsManager settingsManager; private readonly ToolSettingsService toolSettingsService; private readonly Dictionary definitionsById = new(StringComparer.Ordinal); private readonly Dictionary implementationsByKey = new(StringComparer.Ordinal); @@ -16,10 +18,12 @@ public sealed class ToolRegistry public ToolRegistry( IWebHostEnvironment webHostEnvironment, IEnumerable implementations, + SettingsManager settingsManager, ToolSettingsService toolSettingsService, ILogger logger) { this.logger = logger; + this.settingsManager = settingsManager; this.toolSettingsService = toolSettingsService; foreach (var implementation in implementations) @@ -101,6 +105,7 @@ public sealed class ToolRegistry Definition = definition, Implementation = implementation, ConfigurationState = await this.toolSettingsService.GetConfigurationStateAsync(definition, implementation), + MinimumProviderConfidence = this.settingsManager.GetMinimumProviderConfidenceForTool(definition.Id), }); } @@ -111,6 +116,7 @@ public sealed class ToolRegistry AIStudio.Tools.Components component, IEnumerable selectedToolIds, IReadOnlyCollection modelCapabilities, + ConfidenceLevel providerConfidence, bool isToolSelectionVisible) { if (!isToolSelectionVisible) @@ -131,6 +137,10 @@ public sealed class ToolRegistry if (!configurationState.IsConfigured) continue; + var minimumToolConfidence = this.settingsManager.GetMinimumProviderConfidenceForTool(definition.Id); + if (!ToolSelectionRules.IsProviderConfidenceAllowed(providerConfidence, minimumToolConfidence)) + continue; + result.Add((definition, implementation)); } diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs index fc5b9d39..fcd34580 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; +using AIStudio.Provider; + namespace AIStudio.Tools.ToolCallingSystem; public static class ToolSelectionRules @@ -22,4 +24,14 @@ public static class ToolSelectionRules var normalized = NormalizeSelection(selectedToolIds); return toolId == READ_WEB_PAGE_TOOL_ID && normalized.Contains(WEB_SEARCH_TOOL_ID); } + + public static ConfidenceLevel GetDefaultMinimumProviderConfidence(string toolId) => toolId switch + { + WEB_SEARCH_TOOL_ID => ConfidenceLevel.MEDIUM, + READ_WEB_PAGE_TOOL_ID => ConfidenceLevel.MEDIUM, + _ => ConfidenceLevel.NONE, + }; + + public static bool IsProviderConfidenceAllowed(ConfidenceLevel providerConfidence, ConfidenceLevel minimumToolConfidence) => + minimumToolConfidence is ConfidenceLevel.NONE || providerConfidence >= minimumToolConfidence; } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md index f4d5274d..ddf9a890 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md @@ -7,6 +7,7 @@ - Added a start-page setting, so AI Studio can now open directly on your preferred page when the app starts. Configuration plugins can also provide and optionally lock this default for organizations. - Added math rendering in chats for LaTeX display formulas, including block formats such as `$$ ... $$` and `\[ ... \]`. - Added the latest OpenAI models. +- Added minimum provider confidence settings for tools, so sensitive tools such as web search can be limited to trusted providers. Configuration plugins can also manage these defaults for organizations. - Released the document analysis assistant after an intense testing phase. - Improved enterprise deployment for organizations: administrators can now provide up to 10 centrally managed enterprise configuration slots, use policy files on Linux and macOS, and continue using older configuration formats as a fallback during migration. - Improved the profile selection for assistants and the chat. You can now explicitly choose between the app default profile, no profile, or a specific profile. @@ -27,4 +28,4 @@ - Fixed an issue where the app could turn white or appear invisible in certain chats after HTML-like content was shown. Thanks, Inga, for reporting this issue and providing some context on how to reproduce it. - Fixed security issues in the native app runtime by strengthening how AI Studio creates and protects the secret values used for its internal secure connection. - Updated several security-sensitive Rust dependencies in the native runtime to address known vulnerabilities. -- Updated .NET to v9.0.14 \ No newline at end of file +- Updated .NET to v9.0.14