use individual confidence levels for each tool to protect tool callings from using unsafe providers

This commit is contained in:
krut_ni 2026-05-12 17:51:11 +02:00
parent 0cd24c5ec1
commit f2c63ac4b5
No known key found for this signature in database
GPG Key ID: A5C0151B4DDB172C
10 changed files with 134 additions and 9 deletions

View File

@ -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;
}
}

View File

@ -1,3 +1,4 @@
@using AIStudio.Provider
@using AIStudio.Tools.ToolCallingSystem
@inherits SettingsPanelBase
@ -11,6 +12,7 @@
<MudTh>@T("Icon")</MudTh>
<MudTh>@T("Name")</MudTh>
<MudTh>@T("Description")</MudTh>
<MudTh>@T("Minimum provider confidence")</MudTh>
<MudTh>@T("State")</MudTh>
<MudTh>@T("Settings")</MudTh>
</HeaderContent>
@ -24,6 +26,16 @@
<MudTd>
<MudText Typo="Typo.body2">@context.Implementation.GetDescription()</MudText>
</MudTd>
<MudTd>
<MudMenu StartIcon="@Icons.Material.Filled.Security" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@this.GetCurrentConfidenceLevelName(context)" Variant="Variant.Filled" Style="@this.SetCurrentConfidenceLevelColorStyle(context)" Disabled="@this.IsToolConfidenceManaged()">
@foreach (var confidenceLevel in this.GetSelectableConfidenceLevels())
{
<MudMenuItem OnClick="@(async () => await this.ChangeMinimumProviderConfidence(context, confidenceLevel))">
@this.GetConfidenceLevelName(confidenceLevel)
</MudMenuItem>
}
</MudMenu>
</MudTd>
<MudTd>
@if (context.ConfigurationState.IsConfigured)
{

View File

@ -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<ConfidenceLevel> GetSelectableConfidenceLevels() =>
Enum.GetValues<ConfidenceLevel>().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<bool>(this, Event.CONFIGURATION_CHANGED);
}
protected override async Task ProcessIncomingMessage<T>(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;
}
}
}

View File

@ -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);
<MudPaper Class="pa-2 mb-2 border rounded-lg">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudSwitch T="bool" Color="Color.Primary" Value="@isSelected" ValueChanged="@(value => this.ChangeSelection(item.Definition.Id, value))" Disabled="@(!isConfigured || this.Disabled || !this.SupportsTools || this.IsSelectionLockedByDependency(item.Definition.Id))" />
<MudSwitch T="bool" Color="Color.Primary" Value="@isSelected" ValueChanged="@(value => this.ChangeSelection(item.Definition.Id, value))" Disabled="@(!isConfigured || isBlockedByProviderConfidence || this.Disabled || !this.SupportsTools || this.IsSelectionLockedByDependency(item.Definition.Id))" />
<MudIcon Icon="@item.Implementation.Icon" Color="Color.Info" />
<MudTooltip Text="@item.Implementation.GetDescription()">
<MudText Typo="Typo.body1">@item.Implementation.GetDisplayName()</MudText>
@ -59,6 +61,10 @@
{
<MudText Typo="Typo.caption" Color="Color.Info">@dependencyHint</MudText>
}
@if (!string.IsNullOrWhiteSpace(providerConfidenceHint))
{
<MudText Typo="Typo.caption" Color="Color.Warning">@providerConfidenceHint</MudText>
}
</MudPaper>
}
}

View File

@ -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<ToolSettingsDialog>
@ -95,4 +120,15 @@ public partial class ToolSelection : MSGComponentBase
this.catalog = await this.ToolRegistry.GetCatalogAsync(this.Component);
this.StateHasChanged();
}
protected override async Task ProcessIncomingMessage<T>(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;
}
}
}

View File

@ -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<ProviderTool> 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<ProviderTool> providerTools = modelCapabilities.Contains(Capability.WEB_SEARCH) && isWebSearchAllowed
? [ ProviderTools.WEB_SEARCH ]
: [];
// Parse the API parameters:

View File

@ -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

View File

@ -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<ToolRegistry> logger;
private readonly SettingsManager settingsManager;
private readonly ToolSettingsService toolSettingsService;
private readonly Dictionary<string, ToolDefinition> definitionsById = new(StringComparer.Ordinal);
private readonly Dictionary<string, IToolImplementation> implementationsByKey = new(StringComparer.Ordinal);
@ -16,10 +18,12 @@ public sealed class ToolRegistry
public ToolRegistry(
IWebHostEnvironment webHostEnvironment,
IEnumerable<IToolImplementation> implementations,
SettingsManager settingsManager,
ToolSettingsService toolSettingsService,
ILogger<ToolRegistry> 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<string> selectedToolIds,
IReadOnlyCollection<Capability> 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));
}

View File

@ -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;
}

View File

@ -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
- Updated .NET to v9.0.14