From df257714f234f5c7fce86cd8f2744200f9c4eb86 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 24 Sep 2026 14:32:43 +0200 Subject: [PATCH] Keep context-activated tools out of every tool selection --- .../Tools/ToolCallingSystem/ToolActivation.cs | 23 +++++++++++++++++++ .../Tools/ToolCallingSystem/ToolDefinition.cs | 5 ++++ .../Tools/ToolCallingSystem/ToolRegistry.cs | 12 +++++++++- .../ToolCallingSystem/ToolSelectionRules.cs | 6 +++++ .../ToolCalling/ToolSelectionRulesTests.cs | 7 ++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 app/MindWork AI Studio/Tools/ToolCallingSystem/ToolActivation.cs diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolActivation.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolActivation.cs new file mode 100644 index 00000000..1a58a487 --- /dev/null +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolActivation.cs @@ -0,0 +1,23 @@ +namespace AIStudio.Tools.ToolCallingSystem; + +/// +/// How a tool comes to be offered to a model. +/// +public enum ToolActivation +{ + /// + /// Offered when it was selected: by the user, a chat template, a policy, or an assistant. + /// + SELECTION, + + /// + /// Offered whenever the chat calls for it, without anybody selecting it. + /// + /// + /// For a tool whose use is already decided somewhere else. Semantic Search is such a tool: the + /// user picks the data sources of a chat, and a second switch for searching them would only be + /// a way to contradict the first one. Such a tool never appears in a selection, and it decides + /// on each request whether it has anything to offer. + /// + CONTEXT, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolDefinition.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolDefinition.cs index bee42325..c539fd4d 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolDefinition.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolDefinition.cs @@ -12,6 +12,11 @@ public sealed class ToolDefinition public ToolVisibilityDefinition VisibleIn { get; init; } = new(); + /// + /// Whether the tool waits to be selected, or offers itself whenever the chat calls for it. + /// + public ToolActivation Activation { get; init; } = ToolActivation.SELECTION; + public ToolSettingsSchema SettingsSchema { get; init; } = new(); public string SystemPromptInstructions { get; init; } = string.Empty; diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs index c671508b..b1aab738 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolRegistry.cs @@ -269,9 +269,19 @@ public sealed class ToolRegistry return filtered; } + /// + /// The tools somebody can select in this component. + /// + /// + /// 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. + /// public async Task> GetCatalogAsync(Components component) { - var definitions = this.GetDefinitionsForComponent(component); + var definitions = this.GetDefinitionsForComponent(component).Where(x => x.Activation is ToolActivation.SELECTION); return await this.GetCatalogAsync(definitions); } diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs index 79022924..47ebee72 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs @@ -9,6 +9,7 @@ public static class ToolSelectionRules public const string WEB_SEARCH_TOOL_ID = "web_search"; public const string READ_WEB_PAGE_TOOL_ID = "read_web_page"; public const string SEARCH_CONFLUENCE_TOOL_ID = "search_confluence"; + public const string SEMANTIC_SEARCH_TOOL_ID = "semantic_search"; /// /// Turns a set of selected tool IDs into the set which actually runs. @@ -19,6 +20,10 @@ public static class ToolSelectionRules /// ToolRegistry still drops it when it is switched off or the provider's confidence is too /// low, and Read Web Page reaches a wiki on a private or VPN address only when its host is /// allowed there.

+ /// It also removes the tools nobody selects. Semantic Search offers itself whenever the data + /// sources of a chat call for it, see ToolActivation.CONTEXT; kept in a selection, it would + /// appear on the security card of a plugin and in its audit without the selection having any + /// say in whether it runs.

/// Every place which shows or stores a selection normalizes it, the tool selection fields /// included. That way a chat, a template, a policy, or an assistant plugin shows the tools /// which will actually run, and the audit of a plugin judges exactly those. @@ -29,6 +34,7 @@ public static class ToolSelectionRules if (normalized.Contains(SEARCH_CONFLUENCE_TOOL_ID)) normalized.Add(READ_WEB_PAGE_TOOL_ID); + normalized.Remove(SEMANTIC_SEARCH_TOOL_ID); return normalized; } diff --git a/app/Tests/Tools/ToolCalling/ToolSelectionRulesTests.cs b/app/Tests/Tools/ToolCalling/ToolSelectionRulesTests.cs index 5f5209f5..4bea5b4f 100644 --- a/app/Tests/Tools/ToolCalling/ToolSelectionRulesTests.cs +++ b/app/Tests/Tools/ToolCalling/ToolSelectionRulesTests.cs @@ -16,6 +16,7 @@ public sealed class ToolSelectionRulesTests private const string SEARCH_CONFLUENCE = ToolSelectionRules.SEARCH_CONFLUENCE_TOOL_ID; private const string READ_WEB_PAGE = ToolSelectionRules.READ_WEB_PAGE_TOOL_ID; private const string WEB_SEARCH = ToolSelectionRules.WEB_SEARCH_TOOL_ID; + private const string SEMANTIC_SEARCH = ToolSelectionRules.SEMANTIC_SEARCH_TOOL_ID; [Test] public void SearchConfluenceBringsReadWebPageAlong() @@ -30,6 +31,12 @@ public sealed class ToolSelectionRulesTests Assert.That(ToolSelectionRules.NormalizeSelection([toolId]), Is.EquivalentTo(new[] { toolId }), "Only Search Confluence depends on another tool. Read Web Page in particular does not pull the search in."); } + [Test] + public void SemanticSearchIsNeverPartOfASelection() + { + Assert.That(ToolSelectionRules.NormalizeSelection([SEMANTIC_SEARCH, WEB_SEARCH]), Is.EquivalentTo(new[] { WEB_SEARCH }), "Semantic Search offers itself from the data sources of a chat. A template or a plugin naming it would put a tool on the security card that the selection has no say over."); + } + [Test] public void NormalizingTwiceChangesNothing() {