mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-30 05:44:26 +00:00
Added a way to switch off tools you never need (#1115)
This commit is contained in:
1 parent
dcba62b45c
commit
04546c25ac
31 files changed
+638
-37
No files matched your search
+3
-1
@@ -83,7 +83,9 @@ configuration plugin provides and how to add each of them.
|
||||
|
||||
**Documentation:** `documentation/Tools.md`
|
||||
|
||||
Selections, `DataTools.DisabledToolIds`, and the minimum provider confidence name tool collections; a tool outside a declared collection forms one under its own ID, and the ID of a tool inside one stands for its whole collection. Normalize a selection with `ToolRegistry.NormalizeSelection`, and expand it into tools with `ToolRegistry.ExpandSelection` only where the view of the model counts.
|
||||
Selections, `DataTools.DisabledToolIds`, `DataTools.UserDisabledToolIds`, and the minimum provider confidence name tool collections; a tool outside a declared collection forms one under its own ID, and the ID of a tool inside one stands for its whole collection. Normalize a selection with `ToolRegistry.NormalizeSelection`, and expand it into tools with `ToolRegistry.ExpandSelection` only where the view of the model counts.
|
||||
|
||||
Build a list of tools for a component with `ToolRegistry.GetCatalogAsync(Components)`, never from the definitions: it leaves out the tools the user switched off. Whether a tool may be used at all is answered by `ToolRegistry.IsToolActive`, which checks what the organization and what the user switched off. A field which lets somebody change a stored selection shows it through `ToolRegistry.LeaveOutSwitchedOffByUser` and stores the change through `ToolRegistry.KeepSwitchedOffByUser`, so a tool switched off only hides and comes back when switched on again. See "Switched-Off Tools" in `documentation/Tools.md`.
|
||||
|
||||
Code which decides something on behalf of a request — whether the classic RAG process steps back, say — asks `ToolRegistry.GetOfferBlockReasonAsync` or `ToolRegistry.GetEffectiveRetrievalModeAsync` with the provider settings of the request (`IProvider.CreateSettingsProvider`), never a check of its own: two answers which drift apart leave a chat searching nothing or twice.
|
||||
|
||||
|
||||
@@ -4573,6 +4573,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1087321364"] = "Yes
|
||||
-- Yes, let the AI validate & filter the retrieved data.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1309929755"] = "Yes, let the AI validate & filter the retrieved data."
|
||||
|
||||
-- You switched Semantic Search off in the tool settings, so AI Studio uses classic RAG instead and searches your data sources with every message.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T147584079"] = "You switched Semantic Search off in the tool settings, so AI Studio uses classic RAG instead and searches your data sources with every message."
|
||||
|
||||
-- Data Source Selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T15302104"] = "Data Source Selection"
|
||||
|
||||
@@ -4795,6 +4798,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T2430645786"] = "Not
|
||||
-- Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools."
|
||||
|
||||
-- Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3803950041"] = "Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them."
|
||||
|
||||
-- Your Pandoc installation meets the requirements.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements."
|
||||
|
||||
@@ -5779,6 +5785,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T579100
|
||||
-- Open Dashboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T78223861"] = "Open Dashboard"
|
||||
|
||||
-- Switch this tool off if you never need it. It then appears in no tool selection, and the AI cannot use it.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1112962504"] = "Switch this tool off if you never need it. It then appears in no tool selection, and the AI cannot use it."
|
||||
|
||||
-- Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1258653480"] = "Settings"
|
||||
|
||||
@@ -5791,12 +5800,24 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1759955728
|
||||
-- This tool still needs to be configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "This tool still needs to be configured."
|
||||
|
||||
-- Switch off the tools you never need: they then appear in no tool selection, and the AI cannot use them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2160193517"] = "Switch off the tools you never need: they then appear in no tool selection, and the AI cannot use them."
|
||||
|
||||
-- Missing required settings: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579"] = "Missing required settings: {0}"
|
||||
|
||||
-- Enabled
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2626085950"] = "Enabled"
|
||||
|
||||
-- Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name"
|
||||
|
||||
-- Switch this tool on to use it again.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2805631051"] = "Switch this tool on to use it again."
|
||||
|
||||
-- You switched off this tool. It appears in no tool selection, and the AI cannot use it.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3035030488"] = "You switched off this tool. It appears in no tool selection, and the AI cannot use it."
|
||||
|
||||
-- Minimum provider confidence
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimum provider confidence"
|
||||
|
||||
@@ -10267,6 +10288,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3907843187"]
|
||||
-- {0} Default: {1}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T403490413"] = "{0} Default: {1}"
|
||||
|
||||
-- You switched off this tool. It appears in no tool selection, and the AI cannot use it. You can switch it on again in the tool settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T4131368882"] = "You switched off this tool. It appears in no tool selection, and the AI cannot use it. You can switch it on again in the tool settings."
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
|
||||
@@ -397,6 +397,7 @@ public partial class DataSourceSelection : MSGComponentBase
|
||||
ToolOfferBlockReason.TOOLS_SWITCHED_OFF => T("Your organization has switched tools off, so AI Studio uses classic RAG instead and searches your data sources with every message."),
|
||||
ToolOfferBlockReason.MODEL_CANNOT_USE_TOOLS => T("The selected model cannot use tools, so AI Studio uses classic RAG instead and searches your data sources with every message."),
|
||||
ToolOfferBlockReason.TOOL_SWITCHED_OFF => T("Your organization has switched Semantic Search off, so AI Studio uses classic RAG instead and searches your data sources with every message."),
|
||||
ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER => T("You switched Semantic Search off in the tool settings, so AI Studio uses classic RAG instead and searches your data sources with every message."),
|
||||
ToolOfferBlockReason.PROVIDER_CONFIDENCE_TOO_LOW => T("The selected provider is not trusted enough for Semantic Search, so AI Studio uses classic RAG instead and searches your data sources with every message."),
|
||||
_ => T("Semantic Search cannot be used here, so AI Studio uses classic RAG instead and searches your data sources with every message."),
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Two independent reasons a tool stays out of reach, so both may show at once: *@
|
||||
@* Independent reasons a tool stays out of reach, so several may show at once: *@
|
||||
@if (this.ToolsNeedingConfiguration.Count > 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true" Class="@this.Class">
|
||||
@@ -23,4 +23,11 @@ else
|
||||
@(string.Format(T("Not all tools selected for this run can be used with the chosen AI provider: {0}. Please choose a provider with a higher confidence level to use all of them."), string.Join(", ", this.ToolsBeyondProviderConfidence)))
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
@if (this.ToolsSwitchedOffByUser.Count > 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="@true" Class="@this.Class">
|
||||
@(string.Format(T("Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them."), string.Join(", ", this.ToolsSwitchedOffByUser)))
|
||||
</MudAlert>
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public partial class ManagedToolsWarning : MSGComponentBase
|
||||
private ToolRegistry ToolRegistry { get; init; } = null!;
|
||||
|
||||
private IReadOnlyList<ToolCatalogItem> availableTools = [];
|
||||
private IReadOnlyList<ToolCatalogItem> toolsSwitchedOffByUser = [];
|
||||
|
||||
/// <summary>
|
||||
/// Whether this run expects tools while the selected provider cannot call any.
|
||||
@@ -92,20 +93,59 @@ public partial class ManagedToolsWarning : MSGComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The tools of this run which the user switched off in the tool settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Whoever named these tools did not know that the user never needs them. The run goes on
|
||||
/// without them, as the user asked for, but saying nothing would leave them wondering why a
|
||||
/// policy does not search the web, say, long after they switched the web search off. Tools an
|
||||
/// organization switched off stay unnamed, as above.
|
||||
/// </remarks>
|
||||
private IReadOnlyList<string> ToolsSwitchedOffByUser
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.ToolIds.Count is 0 || !this.SettingsManager.AreToolsEnabled())
|
||||
return [];
|
||||
|
||||
var collectionIds = this.ToolRegistry.NormalizeSelection(this.ToolIds);
|
||||
return this.toolsSwitchedOffByUser
|
||||
.Where(x => collectionIds.Contains(x.Id))
|
||||
.Select(x => x.DisplayName)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.availableTools = await this.ToolRegistry.GetCatalogAsync(this.Component);
|
||||
await this.LoadToolsAsync();
|
||||
|
||||
this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]);
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the tools of this component: those the user may use, and those they switched off.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The catalog of the component leaves out the tools the user switched off, so they come from
|
||||
/// all the tools this component knows.
|
||||
/// </remarks>
|
||||
private async Task LoadToolsAsync()
|
||||
{
|
||||
this.availableTools = await this.ToolRegistry.GetCatalogAsync(this.Component);
|
||||
this.toolsSwitchedOffByUser = (await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetDefinitionsForComponent(this.Component)))
|
||||
.Where(x => x.SwitchOffReason is ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.CONFIGURATION_CHANGED:
|
||||
this.availableTools = await this.ToolRegistry.GetCatalogAsync(this.Component);
|
||||
await this.LoadToolsAsync();
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Build" HeaderText="@T("Tool Settings")">
|
||||
<MudText Typo="Typo.body1" Class="mb-4">
|
||||
@T("Configure global settings for each tool.")
|
||||
@T("Configure global settings for each tool.") @T("Switch off the tools you never need: they then appear in no tool selection, and the AI cannot use them.")
|
||||
</MudText>
|
||||
|
||||
<MudTable Items="@this.items" Hover="@true" Dense="@true">
|
||||
<HeaderContent>
|
||||
<MudTh>@T("Enabled")</MudTh>
|
||||
<MudTh>@T("Icon")</MudTh>
|
||||
<MudTh>@T("Name")</MudTh>
|
||||
<MudTh>@T("Description")</MudTh>
|
||||
@@ -15,6 +16,11 @@
|
||||
<MudTh>@T("Settings")</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<MudTooltip Text="@this.GetSwitchTooltip(context)">
|
||||
<MudSwitch T="bool" Value="@context.IsActive" ValueChanged="@(isOn => this.SwitchToolOnOrOff(context, isOn))" Disabled="@IsSwitchLocked(context)" Color="Color.Primary" />
|
||||
</MudTooltip>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIcon Icon="@context.Icon" Color="Color.Info" />
|
||||
</MudTd>
|
||||
@@ -35,7 +41,14 @@
|
||||
</MudMenu>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
@if (!context.IsActive)
|
||||
@if (IsSwitchedOffByUser(context))
|
||||
{
|
||||
@* Whether its settings are complete does not matter while nobody can use the tool: *@
|
||||
<MudTooltip Text="@T("You switched off this tool. It appears in no tool selection, and the AI cannot use it.")">
|
||||
<MudIcon Icon="@Icons.Material.Filled.VisibilityOff" Color="Color.Default" />
|
||||
</MudTooltip>
|
||||
}
|
||||
else if (!context.IsActive)
|
||||
{
|
||||
<MudTooltip Text="@T("This tool has been disabled by your organization.")">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Error" />
|
||||
|
||||
@@ -47,6 +47,36 @@ public partial class SettingsPanelTools : SettingsPanelBase
|
||||
await this.DialogService.ShowAsync<ToolSettingsExportDialog>(null, parameters, Dialogs.DialogOptions.FULLSCREEN);
|
||||
}
|
||||
|
||||
private static bool IsSwitchedOffByUser(ToolCatalogItem item) => item.SwitchOffReason is ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user cannot switch this tool on or off, because their organization switched it or all tools off.
|
||||
/// </summary>
|
||||
private static bool IsSwitchLocked(ToolCatalogItem item) => item.SwitchOffReason is ToolOfferBlockReason.TOOLS_SWITCHED_OFF or ToolOfferBlockReason.TOOL_SWITCHED_OFF;
|
||||
|
||||
private string GetSwitchTooltip(ToolCatalogItem item)
|
||||
{
|
||||
if (IsSwitchLocked(item))
|
||||
return this.T("This tool has been disabled by your organization.");
|
||||
|
||||
return IsSwitchedOffByUser(item)
|
||||
? this.T("Switch this tool on to use it again.")
|
||||
: this.T("Switch this tool off if you never need it. It then appears in no tool selection, and the AI cannot use it.");
|
||||
}
|
||||
|
||||
private async Task SwitchToolOnOrOff(ToolCatalogItem item, bool isOn)
|
||||
{
|
||||
if (IsSwitchLocked(item))
|
||||
return;
|
||||
|
||||
this.ToolRegistry.SetSwitchedOffByUser(item.Id, !isOn);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
|
||||
|
||||
// Tool selections which are open right now load their list anew:
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private string GetConfigurationTooltip(ToolCatalogItem item) => item.ConfigurationState.MissingRequiredFields.Count switch
|
||||
{
|
||||
_ when !string.IsNullOrWhiteSpace(item.ConfigurationState.Message) => item.ConfigurationState.Message,
|
||||
|
||||
@@ -48,7 +48,9 @@ public partial class ToolDefaultsConfiguration : MSGComponentBase
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private HashSet<string> GetSelectedValues() => this.ToolRegistry.GetDefaultToolIds(this.Component);
|
||||
// The list holds no tool the user switched off, so it must not count one as selected either:
|
||||
private HashSet<string> GetSelectedValues() => this.ToolRegistry.LeaveOutSwitchedOffByUser(this.ToolRegistry.GetDefaultToolIds(this.Component));
|
||||
|
||||
private void UpdateSelection(HashSet<string> values) => this.SettingsManager.ConfigurationData.Tools.DefaultToolIdsByComponent[this.Component.ToString()] = [..this.ToolRegistry.NormalizeSelection(values)];
|
||||
// Tools the user switched off stay preselected, so switching them on again brings them back:
|
||||
private void UpdateSelection(HashSet<string> values) => this.SettingsManager.ConfigurationData.Tools.DefaultToolIdsByComponent[this.Component.ToString()] = this.ToolRegistry.KeepSwitchedOffByUser(this.ToolRegistry.GetDefaultToolIds(this.Component), values);
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
OptionDescription="@this.Label"
|
||||
OptionHelp="@this.Help"
|
||||
Data="@this.availableTools"
|
||||
SelectedValues="@(() => this.SelectedToolIds)"
|
||||
SelectedValues="@this.GetShownToolIds"
|
||||
SelectionUpdateAsync="@this.OptionChangedAsync"
|
||||
Disabled="@(() => this.ReadOnly || this.Disabled)"
|
||||
IsItemLocked="@this.IsToolLocked"
|
||||
|
||||
@@ -69,9 +69,13 @@ public partial class ToolSelectionField : MSGComponentBase
|
||||
|
||||
private bool IsToolLocked(string toolId) => !this.ToolRegistry.IsToolActive(toolId);
|
||||
|
||||
// The field lists no tool the user switched off, so it must not count one as selected either:
|
||||
private HashSet<string> GetShownToolIds() => this.ToolRegistry.LeaveOutSwitchedOffByUser(this.SelectedToolIds);
|
||||
|
||||
private async Task OptionChangedAsync(HashSet<string> updatedToolIds)
|
||||
{
|
||||
this.SelectedToolIds = this.ToolRegistry.NormalizeSelection(updatedToolIds);
|
||||
// Tools the user switched off stay selected, so switching them on again brings them back:
|
||||
this.SelectedToolIds = this.ToolRegistry.KeepSwitchedOffByUser(this.SelectedToolIds, updatedToolIds);
|
||||
await this.SelectedToolIdsChanged.InvokeAsync(this.SelectedToolIds);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
@this.item.Description
|
||||
</MudJustifiedText>
|
||||
|
||||
@if (!this.ToolRegistry.IsToolActive(this.item.Id))
|
||||
@if (this.IsSwitchedOffByUser)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="mb-4">@T("You switched off this tool. It appears in no tool selection, and the AI cannot use it. You can switch it on again in the tool settings.")</MudAlert>
|
||||
}
|
||||
else if (!this.ToolRegistry.IsToolActive(this.item.Id))
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Class="mb-4">@T("This tool has been disabled by your organization.")</MudAlert>
|
||||
}
|
||||
|
||||
@@ -45,6 +45,15 @@ public partial class ToolSettingsDialog : SettingsDialogBase
|
||||
this.sections = loadedSections;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user switched this tool off, and nothing but the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When the organization switched it off as well, that is what the dialog says: switching it on
|
||||
/// again would not help the user.
|
||||
/// </remarks>
|
||||
private bool IsSwitchedOffByUser => this.item?.SwitchOffReason is ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER;
|
||||
|
||||
/// <summary>
|
||||
/// Whether each section is headed by the name of its tool.
|
||||
/// </summary>
|
||||
|
||||
@@ -1093,6 +1093,8 @@ CONFIG["SETTINGS"] = {}
|
||||
-- it offers itself whenever a chat has data sources to search. Disabling it makes AI Studio
|
||||
-- search the data sources with every message instead, the way it does for models without
|
||||
-- tool usage.
|
||||
-- Users can switch off further tools themselves in the tool settings, e.g., one they never need.
|
||||
-- They can never switch on a tool you disable here.
|
||||
-- CONFIG["SETTINGS"]["DataTools.DisabledToolIds"] = { "web_search" }
|
||||
|
||||
-- Configure the minimum provider confidence level required for individual tools or tool
|
||||
|
||||
+27
-3
@@ -4575,6 +4575,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1087321364"] = "Ja,
|
||||
-- Yes, let the AI validate & filter the retrieved data.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1309929755"] = "Ja, die KI soll die abgerufenen Daten überprüfen und filtern."
|
||||
|
||||
-- You switched Semantic Search off in the tool settings, so AI Studio uses classic RAG instead and searches your data sources with every message.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T147584079"] = "Sie haben die semantische Suche in den Werkzeugeinstellungen deaktiviert. Daher nutzt AI Studio das klassische RAG und durchsucht Ihre Datenquellen bei jeder Nachricht."
|
||||
|
||||
-- Data Source Selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T15302104"] = "Datenauswahl"
|
||||
|
||||
@@ -4789,14 +4792,17 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MAILFOLDERPICKER::T421974311"] = "Neuer O
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MAILFOLDERPICKER::T913937956"] = "Gesamtes Postfach"
|
||||
|
||||
-- Some tools selected for this run are not fully configured and stay unused: {0}. Please complete their settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T1319635088"] = "Einige der für diesen Durchlauf ausgewählten Werkzeuge sind nicht vollständig eingerichtet und bleiben daher ungenutzt: \"{0}\". Bitte vervollständigen Sie deren Einstellungen."
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T1319635088"] = "Einige der für diesen Durchlauf ausgewählten Werkzeuge sind nicht vollständig eingerichtet und bleiben daher ungenutzt: {0}. Bitte vervollständigen Sie deren Einstellungen."
|
||||
|
||||
-- Not all tools selected for this run can be used with the chosen AI provider: {0}. Please choose a provider with a higher confidence level to use all of them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T2430645786"] = "Nicht alle für diesen Durchlauf ausgewählten Werkzeuge können mit dem gewählten KI-Anbieter „{0}“ verwendet werden. Bitte wählen Sie einen Anbieter mit einem höheren Vertrauensniveau, um alle Werkzeuge zu nutzen."
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T2430645786"] = "Nicht alle für diesen Durchlauf ausgewählten Werkzeuge können mit dem gewählten KI-Anbieter verwendet werden: {0}. Bitte wählen Sie einen Anbieter mit einem höheren Vertrauensniveau, um alle Werkzeuge zu nutzen."
|
||||
|
||||
-- Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Für diesen Durchlauf wurden Werkzeuge ausgewählt, aber das ausgewählte Modell kann keine Werkzeuge verwenden. Es wird ohne sie ausgeführt. Bitte wählen Sie ein Modell, das Werkzeuge unterstützt."
|
||||
|
||||
-- Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3803950041"] = "Einige der für diesen Durchlauf ausgewählten Werkzeuge sind in Ihren Werkzeugeinstellungen deaktiviert und bleiben daher ungenutzt: {0}. Aktivieren Sie sie in den App-Einstellungen wieder, um sie zu nutzen."
|
||||
|
||||
-- Your Pandoc installation meets the requirements.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Ihre Pandoc-Installation erfüllt die Anforderungen."
|
||||
|
||||
@@ -5781,6 +5787,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T579100
|
||||
-- Open Dashboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T78223861"] = "Dashboard öffnen"
|
||||
|
||||
-- Switch this tool off if you never need it. It then appears in no tool selection, and the AI cannot use it.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1112962504"] = "Deaktivieren Sie dieses Werkzeug, wenn Sie es nie benötigen. Es erscheint dann in keiner Werkzeugauswahl, und die KI kann es nicht verwenden."
|
||||
|
||||
-- Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1258653480"] = "Einstellungen"
|
||||
|
||||
@@ -5793,17 +5802,29 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1759955728
|
||||
-- This tool still needs to be configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "Dieses Werkzeug muss noch konfiguriert werden."
|
||||
|
||||
-- Switch off the tools you never need: they then appear in no tool selection, and the AI cannot use them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2160193517"] = "Deaktivieren Sie die Werkzeuge, die Sie nie benötigen. Diese erscheinen dann in keiner Werkzeugauswahl, und die KI kann sie nicht verwenden."
|
||||
|
||||
-- Missing required settings: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579"] = "Fehlende erforderliche Einstellungen: {0}"
|
||||
|
||||
-- Enabled
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2626085950"] = "Aktiviert"
|
||||
|
||||
-- Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name"
|
||||
|
||||
-- Switch this tool on to use it again.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2805631051"] = "Aktivieren Sie dieses Werkzeug, um es wieder zu verwenden."
|
||||
|
||||
-- You switched off this tool. It appears in no tool selection, and the AI cannot use it.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3035030488"] = "Sie haben dieses Werkzeug deaktiviert. Es erscheint in keiner Werkzeugauswahl, und die KI kann es nicht verwenden."
|
||||
|
||||
-- Minimum provider confidence
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimales Vertrauensniveau für Anbieter"
|
||||
|
||||
-- Configure global settings for each tool.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3728248397"] = "Konfiguriere globale Einstellungen für jedes Werkzeug."
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3728248397"] = "Konfigurieren Sie globale Einstellungen für jedes Werkzeug."
|
||||
|
||||
-- Tool Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Werkzeugeinstellungen"
|
||||
@@ -10269,6 +10290,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3907843187"]
|
||||
-- {0} Default: {1}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T403490413"] = "{0} Standard: {1}"
|
||||
|
||||
-- You switched off this tool. It appears in no tool selection, and the AI cannot use it. You can switch it on again in the tool settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T4131368882"] = "Sie haben dieses Werkzeug deaktiviert. Es erscheint in keiner Werkzeugauswahl, und die KI kann es nicht verwenden. Sie können es in den Werkzeugeinstellungen wieder aktivieren."
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T900713019"] = "Abbrechen"
|
||||
|
||||
|
||||
+24
@@ -4575,6 +4575,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1087321364"] = "Yes
|
||||
-- Yes, let the AI validate & filter the retrieved data.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1309929755"] = "Yes, let the AI validate & filter the retrieved data."
|
||||
|
||||
-- You switched Semantic Search off in the tool settings, so AI Studio uses classic RAG instead and searches your data sources with every message.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T147584079"] = "You switched Semantic Search off in the tool settings, so AI Studio uses classic RAG instead and searches your data sources with every message."
|
||||
|
||||
-- Data Source Selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T15302104"] = "Data Source Selection"
|
||||
|
||||
@@ -4797,6 +4800,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T2430645786"] = "Not
|
||||
-- Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3008114108"] = "Tools were selected for this run, but the chosen model cannot use tools. It runs without them. Please choose a model which supports tools."
|
||||
|
||||
-- Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEDTOOLSWARNING::T3803950041"] = "Some tools selected for this run are switched off in your tool settings and stay unused: {0}. Switch them on again in the app settings to use them."
|
||||
|
||||
-- Your Pandoc installation meets the requirements.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements."
|
||||
|
||||
@@ -5781,6 +5787,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T579100
|
||||
-- Open Dashboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T78223861"] = "Open Dashboard"
|
||||
|
||||
-- Switch this tool off if you never need it. It then appears in no tool selection, and the AI cannot use it.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1112962504"] = "Switch this tool off if you never need it. It then appears in no tool selection, and the AI cannot use it."
|
||||
|
||||
-- Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1258653480"] = "Settings"
|
||||
|
||||
@@ -5793,12 +5802,24 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1759955728
|
||||
-- This tool still needs to be configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "This tool still needs to be configured."
|
||||
|
||||
-- Switch off the tools you never need: they then appear in no tool selection, and the AI cannot use them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2160193517"] = "Switch off the tools you never need: they then appear in no tool selection, and the AI cannot use them."
|
||||
|
||||
-- Missing required settings: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579"] = "Missing required settings: {0}"
|
||||
|
||||
-- Enabled
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2626085950"] = "Enabled"
|
||||
|
||||
-- Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name"
|
||||
|
||||
-- Switch this tool on to use it again.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2805631051"] = "Switch this tool on to use it again."
|
||||
|
||||
-- You switched off this tool. It appears in no tool selection, and the AI cannot use it.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3035030488"] = "You switched off this tool. It appears in no tool selection, and the AI cannot use it."
|
||||
|
||||
-- Minimum provider confidence
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimum provider confidence"
|
||||
|
||||
@@ -10269,6 +10290,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3907843187"]
|
||||
-- {0} Default: {1}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T403490413"] = "{0} Default: {1}"
|
||||
|
||||
-- You switched off this tool. It appears in no tool selection, and the AI cannot use it. You can switch it on again in the tool settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T4131368882"] = "You switched off this tool. It appears in no tool selection, and the AI cannot use it. You can switch it on again in the tool settings."
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
|
||||
@@ -39,6 +39,19 @@ public sealed class DataTools(Expression<Func<Data, DataTools>>? configSelection
|
||||
x => x.DisabledToolIds,
|
||||
[]);
|
||||
|
||||
/// <summary>
|
||||
/// The tool collections the user switched off, because they never need them.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The counterpart to DisabledToolIds for the user, and deliberately not managed: switching off
|
||||
/// a tool only takes something away, which every user may do for themselves, while a tool the
|
||||
/// organization switched off stays off whatever this holds. A collection named here appears in
|
||||
/// no tool selection, and no model gets it. Selections which name it keep it, so switching the
|
||||
/// collection on again brings it back wherever it was selected. The IDs are those of tool
|
||||
/// collections, like those of DisabledToolIds.
|
||||
/// </remarks>
|
||||
public HashSet<string> UserDisabledToolIds { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The minimum provider confidence per tool collection, see DisabledToolIds for the IDs.
|
||||
/// </summary>
|
||||
|
||||
@@ -188,8 +188,9 @@ public static class ConfigurationSnippetImport
|
||||
if (toolIds.Count == 0)
|
||||
return [];
|
||||
|
||||
// A tool the user switched off exists here all the same, and switching it on again makes the import use it:
|
||||
var availableToolIds = (await toolRegistry.GetCatalogAsync(component)).Select(item => item.Id).ToHashSet(StringComparer.Ordinal);
|
||||
return toolIds.Where(id => !availableToolIds.Contains(id)).Order(StringComparer.Ordinal).ToList();
|
||||
return toolIds.Where(id => !availableToolIds.Contains(id) && !toolRegistry.IsSwitchedOffByUser(id)).Order(StringComparer.Ordinal).ToList();
|
||||
}
|
||||
|
||||
private static T MakeLocal<T>(T configObject) where T : ConfigurationBaseObject => (T)((ConfigurationBaseObject)configObject with
|
||||
|
||||
@@ -290,7 +290,8 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Tools an organization switched off are left out: an assistant naming one would run without
|
||||
/// it, and neither the model nor the user could tell from the plugin why. Whether a tool is
|
||||
/// it, and neither the model nor the user could tell from the plugin why. Tools the user switched
|
||||
/// off do not even reach the catalog, since the user never needs them. Whether a tool is
|
||||
/// fully configured is deliberately not part of this, because settings can be completed later
|
||||
/// and the assistant then works as written.
|
||||
/// </remarks>
|
||||
|
||||
@@ -194,7 +194,8 @@ public sealed class DataRestoreService(ILogger<DataRestoreService> logger, Setti
|
||||
/// A setting the organization fixed comes from its configuration plugin, and one the tool does
|
||||
/// not show for the saved settings means nothing for them. Which of the remaining ones held a
|
||||
/// value nobody knows: a backup holds no secrets, so the dialog asks to enter them again if they
|
||||
/// were used.
|
||||
/// were used. A tool the user switched off is left out, since they never need it; should they
|
||||
/// switch it on again, its settings say what is missing.
|
||||
/// </remarks>
|
||||
private List<DataRestoreToolSecret> FindToolSecrets()
|
||||
{
|
||||
@@ -204,6 +205,9 @@ public sealed class DataRestoreService(ILogger<DataRestoreService> logger, Setti
|
||||
if (toolRegistry.GetImplementation(definition.ImplementationKey) is not { } implementation)
|
||||
continue;
|
||||
|
||||
if (toolRegistry.IsSwitchedOffByUser(definition.Id))
|
||||
continue;
|
||||
|
||||
if (!settingsManager.ConfigurationData.Tools.Settings.TryGetValue(definition.Id, out var storedValues))
|
||||
continue;
|
||||
|
||||
|
||||
@@ -19,3 +19,5 @@ Tool implementations must treat model-provided arguments as untrusted input. Val
|
||||
A tool which belongs to a preview feature returns false from `IToolImplementation.IsAvailable` while the preview is switched off; the registry then leaves it out of every list, every request, and the token count, so no component has to check that preview for the tool. Every tool declares in `IToolImplementation.OutboundData` where its arguments go: a chat which holds internal content, such as mails, passages of a data source, a search of the wiki, or an internal web page, keeps the tools whose data goes further than its restriction allows from being offered and from running, see `ToolSelectionRules.IsOutboundDataAllowed`. A tool which brings internal content into the chat raises `ToolExecutionResult.RequiredOutboundDataRestriction`, next to `RequiredProviderConfidence` and `RequiredDataSecurity`, names the source in its `OutboundDataOrigin`, and tells the model beforehand that it may end web search, worded by `WebSearchFirstRule`. "Outbound Data Restrictions" in `documentation/Tools.md` explains the sources and the levels, and "Searching Mailboxes" there the mail tools.
|
||||
|
||||
A tool which offers itself from the context of a chat instead of being selected, such as `semantic_search`, sets `Activation = ToolActivation.CONTEXT` and tailors its function to each request in `ResolveFunctionAsync`.
|
||||
|
||||
A tool needs nothing of its own to be switched off by the user or by an organization: `ToolRegistry.IsToolActive` checks both `DataTools.UserDisabledToolIds` and `DataTools.DisabledToolIds` for every tool, and the organization comes first. Keep it that way; a check which asks only one of the two lists lets a switched-off tool through. "Switched-Off Tools" in `documentation/Tools.md` explains the rest.
|
||||
@@ -38,7 +38,20 @@ public sealed class ToolCatalogItem
|
||||
/// </summary>
|
||||
public required ToolConfigurationState ConfigurationState { get; init; }
|
||||
|
||||
public bool IsActive { get; init; }
|
||||
/// <summary>
|
||||
/// Who switched the collection off: tools altogether, the organization, or the user; NONE while it may be used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A reason rather than a yes or no, because the settings have to tell the user apart from the
|
||||
/// organization: the user can switch on again what they switched off themselves, but nothing
|
||||
/// their organization switched off.
|
||||
/// </remarks>
|
||||
public ToolOfferBlockReason SwitchOffReason { get; init; } = ToolOfferBlockReason.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the collection may be used at all, see ToolRegistry.IsToolActive.
|
||||
/// </summary>
|
||||
public bool IsActive => this.SwitchOffReason is ToolOfferBlockReason.NONE;
|
||||
|
||||
public ConfidenceLevel MinimumProviderConfidence { get; init; } = ConfidenceLevel.NONE;
|
||||
}
|
||||
@@ -37,6 +37,11 @@ public enum ToolOfferBlockReason
|
||||
/// </summary>
|
||||
TOOL_SWITCHED_OFF,
|
||||
|
||||
/// <summary>
|
||||
/// The user switched this tool off in the tool settings, because they never need it.
|
||||
/// </summary>
|
||||
TOOL_SWITCHED_OFF_BY_USER,
|
||||
|
||||
/// <summary>
|
||||
/// A setting the tool cannot work without is missing or invalid.
|
||||
/// </summary>
|
||||
|
||||
@@ -318,22 +318,75 @@ public sealed class ToolRegistry
|
||||
public string GetCollectionId(string toolOrCollectionId) => this.collectionIdsByToolId.GetValueOrDefault(toolOrCollectionId, toolOrCollectionId);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a tool may be used at all: tools are switched on, and the organization did not switch its collection off.
|
||||
/// Whether a tool may be used at all: tools are switched on, and neither the organization nor the user switched its collection off.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An organization switches a collection off by its ID or by the ID of any of its tools. A
|
||||
/// single tool of a collection cannot be switched off: the others would stop making sense, and
|
||||
/// an administrator who names one tool would rather lose the collection than keep the tool.
|
||||
/// The one gate for both lists of switched-off collections. Every list, the preparation of a
|
||||
/// request, and the token count ask here, so a collection the user switched off is left out
|
||||
/// everywhere without a check of its own.<br/><br/>
|
||||
/// A collection is switched off by its ID or by the ID of any of its tools. A single tool of a
|
||||
/// collection cannot be switched off: the others would stop making sense, and an administrator
|
||||
/// who names one tool would rather lose the collection than keep the tool.
|
||||
/// </remarks>
|
||||
/// <param name="toolOrCollectionId">The ID of a tool, or of a collection.</param>
|
||||
/// <returns>True when the tool may be used.</returns>
|
||||
public bool IsToolActive(string toolOrCollectionId)
|
||||
public bool IsToolActive(string toolOrCollectionId) => this.GetSwitchOffReason(toolOrCollectionId) is ToolOfferBlockReason.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user switched the collection of a tool off in the tool settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Answers for the user alone, whatever the organization decided: what the user switched off
|
||||
/// stays off once the organization lets go of the collection again.
|
||||
/// </remarks>
|
||||
/// <param name="toolOrCollectionId">The ID of a tool, or of a collection.</param>
|
||||
public bool IsSwitchedOffByUser(string toolOrCollectionId)
|
||||
{
|
||||
var userDisabledIds = this.settingsManager.ConfigurationData.Tools.UserDisabledToolIds;
|
||||
return this.GetSettingsIds(this.GetCollectionId(toolOrCollectionId)).Any(userDisabledIds.Contains);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches the collection of a tool off or on again, as the user chose it in the tool settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Whatever tool of a collection the user chose this for, it is stored for the collection.
|
||||
/// Switching it on removes the entries of its tools as well, since any of them would keep the
|
||||
/// collection switched off.
|
||||
/// </remarks>
|
||||
/// <param name="toolOrCollectionId">The ID of a tool, or of a collection.</param>
|
||||
/// <param name="isSwitchedOff">True to switch the collection off, false to switch it on again.</param>
|
||||
public void SetSwitchedOffByUser(string toolOrCollectionId, bool isSwitchedOff)
|
||||
{
|
||||
var userDisabledIds = this.settingsManager.ConfigurationData.Tools.UserDisabledToolIds;
|
||||
var collectionId = this.GetCollectionId(toolOrCollectionId);
|
||||
userDisabledIds.ExceptWith(this.GetSettingsIds(collectionId));
|
||||
if (isSwitchedOff)
|
||||
userDisabledIds.Add(collectionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Who switched the collection of a tool off, if anybody.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The organization comes before the user: the user cannot switch on what their organization
|
||||
/// switched off, so that is the reason which counts while both apply.
|
||||
/// </remarks>
|
||||
/// <param name="toolOrCollectionId">The ID of a tool, or of a collection.</param>
|
||||
/// <returns>TOOLS_SWITCHED_OFF, TOOL_SWITCHED_OFF, TOOL_SWITCHED_OFF_BY_USER, or NONE.</returns>
|
||||
private ToolOfferBlockReason GetSwitchOffReason(string toolOrCollectionId)
|
||||
{
|
||||
if (!this.settingsManager.AreToolsEnabled())
|
||||
return false;
|
||||
return ToolOfferBlockReason.TOOLS_SWITCHED_OFF;
|
||||
|
||||
var disabledIds = this.settingsManager.ConfigurationData.Tools.DisabledToolIds;
|
||||
return !this.GetSettingsIds(this.GetCollectionId(toolOrCollectionId)).Any(disabledIds.Contains);
|
||||
var settingsIds = this.GetSettingsIds(this.GetCollectionId(toolOrCollectionId));
|
||||
if (settingsIds.Any(this.settingsManager.ConfigurationData.Tools.DisabledToolIds.Contains))
|
||||
return ToolOfferBlockReason.TOOL_SWITCHED_OFF;
|
||||
|
||||
if (settingsIds.Any(this.settingsManager.ConfigurationData.Tools.UserDisabledToolIds.Contains))
|
||||
return ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER;
|
||||
|
||||
return ToolOfferBlockReason.NONE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -449,6 +502,41 @@ public sealed class ToolRegistry
|
||||
.SelectMany(this.GetToolIdsOfCollection)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// The part of a selection a selection field shows: every collection but those the user switched off.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A field lists no collection the user switched off, so it must not count one as selected
|
||||
/// either. Changing the selection afterward goes through KeepSwitchedOffByUser, which puts
|
||||
/// these collections back.
|
||||
/// </remarks>
|
||||
/// <param name="selectedIds">The IDs of the selected tools or collections.</param>
|
||||
/// <returns>The IDs of the collections to show as selected, as a new set.</returns>
|
||||
public HashSet<string> LeaveOutSwitchedOffByUser(IEnumerable<string> selectedIds)
|
||||
{
|
||||
var shown = this.NormalizeSelection(selectedIds);
|
||||
shown.RemoveWhere(this.IsSwitchedOffByUser);
|
||||
return shown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A selection as somebody changed it in a field, plus the collections of the previous selection which the user switched off.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Switching a collection off only hides it. A chat template, a policy, or the defaults of an
|
||||
/// assistant which named it keep naming it, even when somebody changes them while it is off,
|
||||
/// so switching it on again brings it back wherever it was selected.
|
||||
/// </remarks>
|
||||
/// <param name="previousIds">The selection before the change, as stored.</param>
|
||||
/// <param name="changedIds">The selection as the field reports it after the change.</param>
|
||||
/// <returns>The IDs of the collections to store, as a new set.</returns>
|
||||
public HashSet<string> KeepSwitchedOffByUser(IEnumerable<string> previousIds, IEnumerable<string> changedIds)
|
||||
{
|
||||
var kept = this.NormalizeSelection(changedIds);
|
||||
kept.UnionWith(this.NormalizeSelection(previousIds).Where(this.IsSwitchedOffByUser));
|
||||
return kept;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The collections preselected in a component, as the user chose them in the settings.
|
||||
/// </summary>
|
||||
@@ -515,13 +603,17 @@ public sealed class ToolRegistry
|
||||
/// Every selection in the app is built from this list: the one below the message field, the
|
||||
/// defaults, the templates, and the tools the AI picks for a new assistant. A tool which offers
|
||||
/// itself from the context of a chat is left out, because selecting it would change nothing.
|
||||
/// The tool list of the app settings asks for all definitions instead, so an organization can
|
||||
/// still switch such a tool off or set the trust it requires.
|
||||
/// So is a collection the user switched off: they never need it, so it would only take up room.
|
||||
/// One the organization switched off stays, locked, so the user learns why it does not work.
|
||||
/// The tool list of the app settings asks for all definitions instead, so the user can switch
|
||||
/// a collection on again, and an organization can still switch a tool which offers itself off
|
||||
/// or set the trust it requires.
|
||||
/// </remarks>
|
||||
public async Task<IReadOnlyList<ToolCatalogItem>> GetCatalogAsync(Components component)
|
||||
{
|
||||
var definitions = this.GetDefinitionsForComponent(component).Where(x => x.Activation is ToolActivation.SELECTION);
|
||||
return await this.GetCatalogAsync(definitions);
|
||||
var catalog = await this.GetCatalogAsync(definitions);
|
||||
return catalog.Where(item => item.SwitchOffReason is not ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -529,8 +621,8 @@ public sealed class ToolRegistry
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For preselecting tools on someone's behalf, such as when a launcher opens a chat. A tool
|
||||
/// this installation does not know, one an organization switched off, or one whose settings are
|
||||
/// incomplete cannot be enabled by hand either, so handing it over as enabled would show the
|
||||
/// this installation does not know, one an organization or the user switched off, or one whose
|
||||
/// settings are incomplete cannot be enabled by hand either, so handing it over as enabled would show the
|
||||
/// user a state they could not have produced and could not fix from where they are. The
|
||||
/// provider confidence stays out of this: it belongs to the moment a message is sent, not to
|
||||
/// the selection, and it may well be a different provider by then.
|
||||
@@ -612,7 +704,7 @@ public sealed class ToolRegistry
|
||||
DescriptionForLLM = declaredCollection?.Definition.DescriptionForLLM ?? firstTool.Definition.Function.DescriptionForLLM,
|
||||
Tools = tools,
|
||||
ConfigurationState = tools.FirstOrDefault(tool => !tool.ConfigurationState.IsConfigured)?.ConfigurationState ?? new() { IsConfigured = true },
|
||||
IsActive = this.IsToolActive(collectionId),
|
||||
SwitchOffReason = this.GetSwitchOffReason(collectionId),
|
||||
MinimumProviderConfidence = this.GetMinimumProviderConfidence(collectionId),
|
||||
};
|
||||
}
|
||||
@@ -688,6 +780,10 @@ public sealed class ToolRegistry
|
||||
this.logger.LogDebug("Skipping tool '{ToolId}' because it is disabled by managed configuration.", definition.Id);
|
||||
break;
|
||||
|
||||
case { BlockReason: ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER }:
|
||||
this.logger.LogDebug("Skipping tool '{ToolId}' because the user switched it off in the tool settings.", definition.Id);
|
||||
break;
|
||||
|
||||
case { BlockReason: ToolOfferBlockReason.NOT_CONFIGURED }:
|
||||
this.logger.LogDebug("Skipping tool '{ToolId}' because it is not configured.", definition.Id);
|
||||
break;
|
||||
@@ -797,8 +893,9 @@ public sealed class ToolRegistry
|
||||
/// </remarks>
|
||||
private async Task<ToolCheck> CheckToolAsync(ToolDefinition definition, ConfidenceLevel providerConfidence, OutboundDataRestriction outboundDataRestriction)
|
||||
{
|
||||
if (!this.IsToolActive(definition.Id))
|
||||
return new(ToolOfferBlockReason.TOOL_SWITCHED_OFF, null, null);
|
||||
var switchOffReason = this.GetSwitchOffReason(definition.Id);
|
||||
if (switchOffReason is not ToolOfferBlockReason.NONE)
|
||||
return new(switchOffReason, null, null);
|
||||
|
||||
if (!this.implementationsByKey.TryGetValue(definition.ImplementationKey, out var implementation) || !implementation.IsAvailable)
|
||||
return new(ToolOfferBlockReason.NOT_AVAILABLE_HERE, null, null);
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
- Added a way to read what the AI received for an answer. In the list of sources below an answer, the eye button next to a source shows the passages the AI got from it, for example, from your data sources, a web page, a mail, or your Confluence wiki, as they were after AI Studio removed suspicious instructions. They stay readable when you open the chat again later. Thanks, Paul Koudelka (`PaulKoudelka`), for this contribution.
|
||||
- Added organization-wide management for whether AI Studio reads a file attached to a chat again after it has changed. IT departments can set this for everybody, either fixed or as a default which users may change.
|
||||
- Added a way for IT departments to slow down the indexing of local data sources, so that a few people with large folders or mailboxes no longer keep a shared embedding server busy for hours. IT departments set the shortest and the longest pause between the indexing steps, either fixed or as a default which users may change.
|
||||
- Added a way to switch off tools you never need, for example, the Confluence search when you have no Confluence. Switched-off tools no longer appear in any tool selection, and the AI cannot use them. You find the switch for each tool in the app settings, under Tool Settings.
|
||||
- Improved the Read Web Page tool. When you have not allowed the AI to choose web addresses freely, the tool now refuses every address that does not appear in your chat or in what the tools returned, instead of only asking the AI not to read it.
|
||||
- Improved the installation on macOS: AI Studio is now signed and checked by Apple, so it opens like any other app. You no longer need the Terminal.
|
||||
- Improved the installation on Windows: AI Studio is now signed, so Windows shows who published it. For a while, Windows may still ask you to confirm the installation until it knows the app better.
|
||||
@@ -144,6 +145,7 @@
|
||||
- Fixed assistants doing nothing at all when you started them without selecting a provider. They now ask you to select one, together with anything else that is still missing.
|
||||
- Fixed an assistant undoing your changes every few seconds when you left it during a run and came back before it finished. Your changes now stay, and the result still appears when the assistant is done. In the Slide Assistant, this also ends short freezes while it plans.
|
||||
- Fixed exporting a briefing of the Visual Briefing assistant into a file which another program keeps open or which cannot be written. AI Studio now tells you that the export failed.
|
||||
- Fixed the German warning about tools which the chosen AI provider is not trusted enough to use. It named these tools as if they were the provider.
|
||||
- Updated the code contributions on the supporters page, which now thank everyone who has contributed code to AI Studio so far.
|
||||
- Upgraded several libraries to improve security.
|
||||
- Upgraded to Rust v1.99.1
|
||||
|
||||
@@ -540,6 +540,24 @@ public sealed class ConfigurationSnippetImportTests : ToolRegistryTestBase
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AToolYouSwitchedOffIsNotMissing()
|
||||
{
|
||||
var registry = this.CreateRegistry(new TestTool(Definition()));
|
||||
registry.SetSwitchedOffByUser(TOOL_ID, true);
|
||||
|
||||
var policy = await ConfigurationSnippetImport.ResolveReferencesAsync(Import(ExportPolicy(allowedToolIds: [TOOL_ID]), PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY), this.SettingsManager, registry);
|
||||
var template = await ConfigurationSnippetImport.ResolveReferencesAsync(Import(ExportChatTemplate([], [TOOL_ID]), PluginConfigurationObjectType.CHAT_TEMPLATE), this.SettingsManager, registry);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(policy.Hints, Is.Empty, "The tool exists on this computer; switching it on again makes the policy use it.");
|
||||
Assert.That(template.Hints, Is.Empty);
|
||||
Assert.That(((DataDocumentAnalysisPolicy)policy.ConfigurationObject).AllowedToolIds, Is.EquivalentTo(new[] { TOOL_ID }), "The import keeps the tool.");
|
||||
Assert.That(((ChatTemplate)template.ConfigurationObject).ToolIds, Is.EquivalentTo(new[] { TOOL_ID }));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AChatTemplateWhichSaysNothingAboutToolsKeepsSayingNothing()
|
||||
{
|
||||
|
||||
@@ -48,6 +48,14 @@ public sealed class ClassicRagStandsBackTests : ToolRegistryTestBase
|
||||
Assert.That(await this.IsSearchedByTheModelAsync(DataSourceRetrievalMode.SEMANTIC_SEARCH, ToolCapableProvider()), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ItSearchesWhenTheUserSwitchedTheToolOff()
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.Tools.UserDisabledToolIds.Add(ToolSelectionRules.SEMANTIC_SEARCH_TOOL_ID);
|
||||
|
||||
Assert.That(await this.IsSearchedByTheModelAsync(DataSourceRetrievalMode.SEMANTIC_SEARCH, ToolCapableProvider()), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ItSearchesWithoutARegistry()
|
||||
{
|
||||
|
||||
@@ -66,6 +66,16 @@ public sealed class EffectiveRetrievalModeTests : ToolRegistryTestBase
|
||||
Assert.That(mode, Is.EqualTo(new EffectiveRetrievalMode(DataSourceRetrievalMode.EVERY_MESSAGE, ToolOfferBlockReason.TOOL_SWITCHED_OFF)), "An organization which switches the tool off must not switch off the data sources along with it.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SwitchingTheToolOffYourselfGetsEveryMessageSearched()
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.Tools.UserDisabledToolIds.Add(ToolSelectionRules.SEMANTIC_SEARCH_TOOL_ID);
|
||||
|
||||
var mode = await this.GetModeAsync(DataSourceRetrievalMode.SEMANTIC_SEARCH, ToolCapableProvider());
|
||||
|
||||
Assert.That(mode, Is.EqualTo(new EffectiveRetrievalMode(DataSourceRetrievalMode.EVERY_MESSAGE, ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER)), "A user who switches the tool off still wants their data sources searched.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OutsideTheChatEveryMessageIsSearched()
|
||||
{
|
||||
|
||||
@@ -72,6 +72,23 @@ public sealed class ToolRegistryOfferTests : ToolRegistryTestBase
|
||||
await this.AssertBothAgree(this.CreateRegistry(new TestTool(Definition())), ToolCapableProvider(), ToolOfferBlockReason.TOOL_SWITCHED_OFF, "The organization turned this one tool off.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AToolSwitchedOffByTheUser()
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.Tools.UserDisabledToolIds.Add(TOOL_ID);
|
||||
|
||||
await this.AssertBothAgree(this.CreateRegistry(new TestTool(Definition())), ToolCapableProvider(), ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER, "The user never needs this tool and switched it off.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TheOrganizationComesBeforeTheUser()
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.Tools.DisabledToolIds.Add(TOOL_ID);
|
||||
this.SettingsManager.ConfigurationData.Tools.UserDisabledToolIds.Add(TOOL_ID);
|
||||
|
||||
await this.AssertBothAgree(this.CreateRegistry(new TestTool(Definition())), ToolCapableProvider(), ToolOfferBlockReason.TOOL_SWITCHED_OFF, "Switching the tool on again would not help the user, so the organization is the reason which counts.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AToolMissingASetting()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.ToolCallingSystem;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace AIStudio.Tests.Tools.ToolCalling;
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a tool collection the user switched off disappears from every selection and from every request, and comes back when switched on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Somebody without a Confluence wiki never needs its search, and a tool that stays in every
|
||||
/// selection with a warning about its missing settings only gets in the way. Switching it off has
|
||||
/// to hide it everywhere a tool can be picked, and the model must neither get nor call it. The
|
||||
/// settings still list it, or the user could never switch it on again, and selections keep naming
|
||||
/// it, so switching it on brings it back wherever it was selected.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
[NonParallelizable]
|
||||
public sealed class ToolSwitchedOffByUserTests : ToolRegistryTestBase
|
||||
{
|
||||
private const string OTHER_TOOL_ID = "other_tool";
|
||||
private const string COLLECTION_ID = "test_collection";
|
||||
private const string SEARCH_TOOL_ID = "test_search";
|
||||
private const string READ_TOOL_ID = "test_read";
|
||||
|
||||
[Test]
|
||||
public async Task ASwitchedOffToolAppearsInNoSelectionButInTheSettings()
|
||||
{
|
||||
var registry = this.CreateRegistry(new TestTool(Definition()), new TestTool(Definition(OTHER_TOOL_ID)));
|
||||
registry.SetSwitchedOffByUser(TOOL_ID, true);
|
||||
|
||||
var forTheChat = await registry.GetCatalogAsync(AIStudio.Tools.Components.CHAT);
|
||||
var forTheSettings = await registry.GetCatalogAsync(registry.GetAllDefinitions());
|
||||
var forItsDialog = await registry.GetCatalogItemAsync(TOOL_ID);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(forTheChat.Select(item => item.Id), Is.EqualTo(new[] { OTHER_TOOL_ID }), "Every selection is built from this list.");
|
||||
Assert.That(forTheSettings.Single(item => item.Id == TOOL_ID).SwitchOffReason, Is.EqualTo(ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER), "Otherwise, the user could never switch it on again.");
|
||||
Assert.That(forItsDialog?.IsActive, Is.False, "The settings dialog of the tool still opens.");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AToolTheOrganizationSwitchedOffStaysInTheSelection()
|
||||
{
|
||||
var registry = this.CreateRegistry(new TestTool(Definition()));
|
||||
this.SettingsManager.ConfigurationData.Tools.DisabledToolIds.Add(TOOL_ID);
|
||||
|
||||
var forTheChat = await registry.GetCatalogAsync(AIStudio.Tools.Components.CHAT);
|
||||
|
||||
Assert.That(forTheChat.Single().SwitchOffReason, Is.EqualTo(ToolOfferBlockReason.TOOL_SWITCHED_OFF), "Shown locked, so the user learns why the tool does not work.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ASwitchedOffToolIsNeitherCountedNorPreselected()
|
||||
{
|
||||
var registry = this.CreateRegistry(new TestTool(Definition()), new TestTool(Definition(OTHER_TOOL_ID)));
|
||||
registry.SetSwitchedOffByUser(TOOL_ID, true);
|
||||
|
||||
var counted = registry.FilterToolIdsForProvider(ToolCapableProvider(), [TOOL_ID, OTHER_TOOL_ID], OutboundDataRestriction.UNRESTRICTED);
|
||||
var preselected = await registry.FilterSelectableToolIdsAsync(AIStudio.Tools.Components.CHAT, [TOOL_ID, OTHER_TOOL_ID]);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(counted, Is.EquivalentTo(new[] { OTHER_TOOL_ID }), "The token count below the message field.");
|
||||
Assert.That(preselected, Is.EquivalentTo(new[] { OTHER_TOOL_ID }), "A chat template or a launcher which names the tool.");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AModelCannotCallASwitchedOffTool()
|
||||
{
|
||||
var wasCalled = false;
|
||||
var registry = this.CreateRegistry(new TestTool(Definition(), execute: _ =>
|
||||
{
|
||||
wasCalled = true;
|
||||
return new ToolExecutionResult();
|
||||
}));
|
||||
|
||||
registry.SetSwitchedOffByUser(TOOL_ID, true);
|
||||
var runnableTools = await registry.GetRunnableToolsAsync(this.ContextFor(ToolCapableProvider()), [TOOL_ID], mayRunTools: true);
|
||||
|
||||
// A model which calls the tool anyway, e.g., because an earlier answer in the chat named it:
|
||||
var executor = new ToolExecutor(this.CreateToolSettingsService(), NullLogger<ToolExecutor>.Instance);
|
||||
var outcome = await executor.ExecuteAsync("call-1", TOOL_ID, "{}", runnableTools, new NoProvider(), new ChatThread(), order: 1);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(runnableTools, Is.Empty, "The request does not offer the tool.");
|
||||
Assert.That(outcome.Trace.Status, Is.EqualTo(ToolInvocationTraceStatus.BLOCKED));
|
||||
Assert.That(wasCalled, Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ASelectedToolComesBackWhenSwitchedOnAgain()
|
||||
{
|
||||
var registry = this.CreateRegistry(new TestTool(Definition()));
|
||||
string[] selection = [TOOL_ID];
|
||||
|
||||
registry.SetSwitchedOffByUser(TOOL_ID, true);
|
||||
var whileSwitchedOff = await registry.GetRunnableToolsAsync(this.ContextFor(ToolCapableProvider()), selection, mayRunTools: true);
|
||||
registry.SetSwitchedOffByUser(TOOL_ID, false);
|
||||
var afterSwitchingOn = await registry.GetRunnableToolsAsync(this.ContextFor(ToolCapableProvider()), selection, mayRunTools: true);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(whileSwitchedOff, Is.Empty);
|
||||
Assert.That(afterSwitchingOn.Select(tool => tool.Definition.Id), Is.EqualTo(new[] { TOOL_ID }), "The selection kept the tool all along.");
|
||||
Assert.That(this.StoredIds, Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SwitchingOffOneToolSwitchesOffItsWholeCollection()
|
||||
{
|
||||
var registry = this.CreateCollection();
|
||||
registry.SetSwitchedOffByUser(READ_TOOL_ID, true);
|
||||
|
||||
var reason = await registry.GetOfferBlockReasonAsync(SEARCH_TOOL_ID, ToolCapableProvider(), AIStudio.Tools.Components.CHAT);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(this.StoredIds, Is.EquivalentTo(new[] { COLLECTION_ID }), "The choice belongs to the collection, not to the tool it was made for.");
|
||||
Assert.That(registry.IsSwitchedOffByUser(SEARCH_TOOL_ID), Is.True);
|
||||
Assert.That(reason, Is.EqualTo(ToolOfferBlockReason.TOOL_SWITCHED_OFF_BY_USER));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SwitchingACollectionOnRemovesTheEntriesOfItsTools()
|
||||
{
|
||||
var registry = this.CreateCollection();
|
||||
this.StoredIds.Add(READ_TOOL_ID);
|
||||
|
||||
var whileSwitchedOff = registry.IsToolActive(SEARCH_TOOL_ID);
|
||||
registry.SetSwitchedOffByUser(COLLECTION_ID, false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(whileSwitchedOff, Is.False, "An entry for one tool counts for its whole collection.");
|
||||
Assert.That(registry.IsToolActive(SEARCH_TOOL_ID), Is.True, "The old entry of the reader would keep the collection switched off.");
|
||||
Assert.That(this.StoredIds, Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhatTheUserSwitchedOffStaysOffWhenTheOrganizationLetsGo()
|
||||
{
|
||||
var registry = this.CreateRegistry(new TestTool(Definition()));
|
||||
registry.SetSwitchedOffByUser(TOOL_ID, true);
|
||||
this.SettingsManager.ConfigurationData.Tools.DisabledToolIds.Add(TOOL_ID);
|
||||
|
||||
var whileBothApply = registry.IsSwitchedOffByUser(TOOL_ID);
|
||||
this.SettingsManager.ConfigurationData.Tools.DisabledToolIds.Remove(TOOL_ID);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(whileBothApply, Is.True);
|
||||
Assert.That(registry.IsToolActive(TOOL_ID), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ASelectionFieldShowsNoSwitchedOffTool()
|
||||
{
|
||||
var registry = this.CreateCollection();
|
||||
registry.SetSwitchedOffByUser(COLLECTION_ID, true);
|
||||
|
||||
var shown = registry.LeaveOutSwitchedOffByUser([READ_TOOL_ID, TOOL_ID]);
|
||||
|
||||
Assert.That(shown, Is.EquivalentTo(new[] { TOOL_ID }), "The field lists no switched-off tool, so it must not count one as selected either.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangingASelectionKeepsTheToolsSwitchedOff()
|
||||
{
|
||||
var registry = this.CreateCollection();
|
||||
registry.SetSwitchedOffByUser(COLLECTION_ID, true);
|
||||
|
||||
// The field showed only the other tool, and the user took it out:
|
||||
var stored = registry.KeepSwitchedOffByUser([READ_TOOL_ID, TOOL_ID], []);
|
||||
|
||||
Assert.That(stored, Is.EquivalentTo(new[] { COLLECTION_ID }), "Switching the collection on again brings it back in the template.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangingASelectionDropsWhatTheUserDeselected()
|
||||
{
|
||||
var registry = this.CreateCollection();
|
||||
|
||||
var stored = registry.KeepSwitchedOffByUser([READ_TOOL_ID, TOOL_ID], [TOOL_ID]);
|
||||
|
||||
Assert.That(stored, Is.EquivalentTo(new[] { TOOL_ID }), "Nothing is switched off, so nothing comes back on its own.");
|
||||
}
|
||||
|
||||
private HashSet<string> StoredIds => this.SettingsManager.ConfigurationData.Tools.UserDisabledToolIds;
|
||||
|
||||
/// <summary>
|
||||
/// A search and a reader in one collection, next to a tool of its own.
|
||||
/// </summary>
|
||||
private ToolRegistry CreateCollection() => this.CreateRegistry(
|
||||
[new TestCollection(COLLECTION_ID, ConfidenceLevel.NONE, SEARCH_TOOL_ID, READ_TOOL_ID)],
|
||||
new TestTool(Definition(SEARCH_TOOL_ID)),
|
||||
new TestTool(Definition(READ_TOOL_ID)),
|
||||
new TestTool(Definition()));
|
||||
}
|
||||
@@ -741,6 +741,8 @@ The export reads saved, effective settings, including organization-managed value
|
||||
|
||||
Some tools only make sense together and form a tool collection, such as **Mailboxes** with Search Mails, Read Mail, and Count Mails. **Tool Settings** shows such a collection as one row, and its export covers all of its tools: each area names its tool, and the minimum provider confidence goes under the ID of the collection, e.g., `mailboxes`. Wherever your configuration names tools — `DataTools.DisabledToolIds`, `DataTools.MinimumProviderConfidenceByToolId`, chat templates, and document analysis policies — use the ID of the collection for them. The ID of one of its tools counts for the whole collection, so naming it switches off the whole collection or sets its confidence; of several levels set for a collection and its tools, the highest applies. Tool settings stay with each tool and keep the `"<toolId>.<fieldName>"` keys.
|
||||
|
||||
Users can switch off tools themselves in **Tool Settings**, for example, the Confluence search when they have no Confluence. A tool they switched off appears in no tool selection, and the AI cannot use it. This choice belongs to each user and is not part of a configuration. It can only take tools away: a tool you switch off with `DataTools.DisabledToolIds` stays off, and its switch is locked.
|
||||
|
||||
### Complete tool export
|
||||
|
||||
For example, save a timeout of `30`, a content limit of `12000`, an empty private-host list, and free address choice switched off for **Read Web Page**. Select its General area, **Locked settings**, and **Include minimum provider confidence**. With its default confidence requirement of `VERY_LOW`, the export is:
|
||||
@@ -795,7 +797,7 @@ Writing the `DataSourceOptions` table at all is already the statement that this
|
||||
| `SEMANTIC_SEARCH` (default) | The AI searches the data sources itself, through the tool `semantic_search`, whenever a question calls for it. No agent takes part: `AutomaticDataSourceSelection` lets the AI itself choose among all data sources it may use, and `AutomaticValidation` has no effect. |
|
||||
| `EVERY_MESSAGE` | AI Studio searches the data sources with every message, before the AI answers, with the agents for selection and validation as configured. |
|
||||
|
||||
Semantic search only works where `semantic_search` can be offered: the model has to be able to call tools, neither `DataTools.EnableTools` nor `DataTools.DisabledToolIds` may switch it off, and the provider has to meet a minimum confidence you set for it in `DataTools.MinimumProviderConfidenceByToolId`. Otherwise, the chat searches with every message instead. A template that leaves `RetrievalMode` out gets `SEMANTIC_SEARCH`, not the chat default. That chat default is the setting `DataChat.PreselectedDataSourcesRetrievalMode`, with the same two values.
|
||||
Semantic search only works where `semantic_search` can be offered: the model has to be able to call tools, neither `DataTools.EnableTools` nor `DataTools.DisabledToolIds` may switch it off, nor may the user in **Tool Settings**, and the provider has to meet a minimum confidence you set for it in `DataTools.MinimumProviderConfidenceByToolId`. Otherwise, the chat searches with every message instead. A template that leaves `RetrievalMode` out gets `SEMANTIC_SEARCH`, not the chat default. That chat default is the setting `DataChat.PreselectedDataSourcesRetrievalMode`, with the same two values.
|
||||
|
||||
When an [assistant plugin](../app/MindWork%20AI%20Studio/Plugins/assistants/README.md) opens a chat directly and its chat template names tools, data sources, a provider, or a profile, that template decides them alone; what the launcher names is dropped with a warning in the log. Its README explains the rule and how such sources are checked.
|
||||
|
||||
|
||||
+13
-2
@@ -10,6 +10,7 @@ The provider only sees local tools that are
|
||||
|
||||
- present in this installation, which a tool of a preview feature only is while the preview is switched on, and
|
||||
- available for the current component and
|
||||
- switched off neither by the organization nor by the user and
|
||||
- selected by the user or defaults, or offering themselves from the context of the chat, and
|
||||
- supported by the model and
|
||||
- configured correctly and
|
||||
@@ -84,7 +85,7 @@ A result in `JsonContent` reaches the model the way `ToolExecutionResult.ToModel
|
||||
|
||||
## Tools Which Offer Themselves
|
||||
|
||||
Most tools are selected: by the user, by the defaults of a component, by a chat template, or by the rules of an assistant. A tool whose use follows from the chat instead sets `Activation = ToolActivation.CONTEXT`. Nobody can select such a tool, so it appears in no selection. `ToolRegistry.GetCatalogAsync` leaves it out of every list built for a component, and `ToolRegistry.NormalizeSelection` drops it from a selection which names it anyway, such as the one of a chat template. The tool list of the app settings still shows it, so that an organization can switch it off or raise the confidence it requires. `semantic_search` is the only such tool so far.
|
||||
Most tools are selected: by the user, by the defaults of a component, by a chat template, or by the rules of an assistant. A tool whose use follows from the chat instead sets `Activation = ToolActivation.CONTEXT`. Nobody can select such a tool, so it appears in no selection. `ToolRegistry.GetCatalogAsync` leaves it out of every list built for a component, and `ToolRegistry.NormalizeSelection` drops it from a selection which names it anyway, such as the one of a chat template. The tool list of the app settings still shows it, so that the user or an organization can switch it off or raise the confidence it requires. `semantic_search` is the only such tool so far.
|
||||
|
||||
The registry takes every context tool of the component as a candidate and runs it through the same checks as a selected one. A tool which passes them is then asked what it offers in this request, through `IToolImplementation.ResolveFunctionAsync`. Most tools leave that method alone and offer the function they registered. A tool which has to know the chat first returns a function tailored to it, or null when it has nothing to offer, and is then left out of the request. Only the description and the parameters of the answer count; the name and the strict mode stay as registered, because the calls of the model find their tool by its name. A resolution which throws costs that one tool and no other.
|
||||
|
||||
@@ -103,7 +104,7 @@ Paging stays stateless: every call brings its query and its page again. It has t
|
||||
|
||||
## Tool Collections
|
||||
|
||||
Some tools only make sense together. Searching mails without being able to read the ones found gets in the way, and reading them with less trust than the search asks for would protect nothing. Such tools form a collection: people select it as one entry, it needs one minimum provider confidence, and an organization switches it off as one. The model still sees each of its tools on its own and calls each by its name.
|
||||
Some tools only make sense together. Searching mails without being able to read the ones found gets in the way, and reading them with less trust than the search asks for would protect nothing. Such tools form a collection: people select it as one entry, it needs one minimum provider confidence, and the user or an organization switches it off as one. The model still sees each of its tools on its own and calls each by its name.
|
||||
|
||||
A collection is an `IToolCollection` class next to its tools, registered in `Program.cs`. It states its `ToolCollectionDefinition`: its ID, the IDs of its tools in the order they are listed, its minimum provider confidence, and a description for a model which picks the tools of an assistant. Its name, description, and icon come from its own members, so they can be translated. Whether it exists right now follows from its tools: it disappears with the last of them, e.g., while their preview is switched off. `ToolRegistry` registers collections after the tools and leaves out what it cannot accept: a collection taking the ID of a tool, a tool which is not registered or which offers itself from the context of a chat, and a tool another collection claimed first.
|
||||
|
||||
@@ -117,6 +118,16 @@ Three methods of the registry translate between both views:
|
||||
|
||||
The settings of a tool stay with the tool, also inside a collection. An organization addresses them by `"<toolId>.<fieldName>"`, and the settings dialog of a collection shows one section per tool. `mailboxes` is the only declared collection so far, with `search_mails`, `read_mail`, and `count_mails`.
|
||||
|
||||
## Switched-Off Tools
|
||||
|
||||
A collection can be switched off twice: by an organization in `DataTools.DisabledToolIds`, and by the user in `DataTools.UserDisabledToolIds`, through the switch of its row in the tool settings. Somebody without a Confluence wiki never needs its search, and a tool which stays in every selection with a warning about its missing settings only gets in the way. The list of the user is not managed: it only takes tools away, and what the organization switched off stays off whatever it holds. Both lists name collections, like every other setting about tools.
|
||||
|
||||
`ToolRegistry.IsToolActive` is the one gate for both lists. Every list, the preparation of a request, the token count, and `ToolRegistry.GetOfferBlockReasonAsync` ask it, so a switched-off tool is neither offered to the model nor run, without a check of its own; `ToolExecutor` runs only what the request offered. When both lists name a collection, the organization is the reason which counts, because switching the tool on again would not help the user: `TOOL_SWITCHED_OFF` comes before `TOOL_SWITCHED_OFF_BY_USER`. `ToolCatalogItem.SwitchOffReason` carries the reason, so the user interface can tell both apart.
|
||||
|
||||
What the user switched off disappears; what the organization switched off stays visible, locked, because the user has to learn why the tool does not work. `ToolRegistry.GetCatalogAsync` for a component therefore leaves out the collections the user switched off, and every selection is built from it. The catalog of all definitions, which the tool settings and the settings dialog of a tool use, keeps them, or nobody could switch them on again.
|
||||
|
||||
Switching a collection off only hides it. A chat, a chat template, a document analysis policy, the defaults of a component, or an assistant plugin which names it keeps naming it, and switching it on again brings it back there. A selection field therefore shows a selection through `ToolRegistry.LeaveOutSwitchedOffByUser` and stores a change through `ToolRegistry.KeepSwitchedOffByUser`, which puts the switched-off collections back. Where the rules of an assistant name a switched-off tool, `ManagedToolsWarning` says that the run goes without it. Switching off `semantic_search` has the data sources searched with every message, as when an organization does it.
|
||||
|
||||
## Security
|
||||
|
||||
Treat model-provided tool arguments as untrusted input. Refuse a wrong one rather than guessing what it meant: a placeholder such as `0` is not a page, and reading it as "no page" does something the model did not ask for. The model reads the refusal and tries again, so the message has to name the argument and the value that arrived, say what would be valid, and, for an optional argument, that leaving it out is always possible. `ToolArgumentReader` reads strings, positive integers, and values out of a fixed choice, alone or as a list, and words the refusals so; `WebSearchTool` shows how a tool uses it.
|
||||
|
||||
Reference in new issue
Block a user