Added tool calling support (#731)

Co-authored-by: krut_ni <nils.kruthoff@dlr.de>
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-09-04 15:48:07 +02:00
1 parent b00c3f9ab3
commit 4d8d30e15e
266 files changed
+11186 -740

No files matched your search

@@ -35,6 +35,19 @@ else
</MudPaper>
}
@*
The plugin names the tools, so there is nothing for the user to switch on or off. What is
left is to say which tools run here, and to warn when the selected provider keeps one of
them out of reach.
*@
@if (this.assistantToolIds is { Count: > 0 } toolIds && this.SettingsManager.AreToolsEnabled())
{
<MudPaper Class="pa-4 ma-4" Elevation="0">
<ManagedToolsWarning Component="@this.Component" ToolIds="@toolIds" ProviderSettings="@this.ProviderSettings"/>
<ToolSelectionField Component="@this.Component" SelectedToolIds="@toolIds" ReadOnly="@true" Label="@T("Tools of this assistant")" Help="@T("The author of this assistant chose these tools, so they cannot be changed here. Every tool has to meet the confidence requirements of the selected provider, so a tool may stay unavailable even when this assistant names it.")"/>
</MudPaper>
}
@foreach (var component in this.RootComponent.Children)
{
@this.RenderComponent(component)
@@ -9,6 +9,7 @@ using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using AIStudio.Tools.Services;
using AIStudio.Tools.ToolCallingSystem;
using Lua;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
@@ -34,6 +35,13 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
protected override bool ShowProfileSelection => this.showFooterProfileSelection;
protected override string SubmitText => this.submitText;
protected override Func<Task> SubmitAction => this.Submit;
/// <remarks>
/// A plugin that names its tools has decided for the user: its author wrote and tested the
/// assistant with exactly these. Null keeps the footer selection for every other plugin.
/// </remarks>
protected override IReadOnlySet<string>? AssistantManagedToolIds => this.assistantToolIds;
protected override bool SubmitDisabled => this.isSecurityBlocked;
// Dynamic assistants do not have dedicated settings yet. Their internal identity keeps their
// session and media state separate while ComponentsExtensions derives their defaults from chat.
@@ -50,6 +58,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
private bool allowProfiles = true;
private string submitText = string.Empty;
private bool showFooterProfileSelection = true;
private HashSet<string>? assistantToolIds;
private PluginAssistants? assistantPlugin;
private readonly AssistantState assistantState = new();
@@ -69,6 +78,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
private static readonly AssistantSessionStateKey<bool> ALLOW_PROFILES_STATE_KEY = new(nameof(allowProfiles));
private static readonly AssistantSessionStateKey<string> SUBMIT_TEXT_STATE_KEY = new(nameof(submitText));
private static readonly AssistantSessionStateKey<bool> SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY = new(nameof(showFooterProfileSelection));
private static readonly AssistantSessionStateKey<HashSet<string>?> ASSISTANT_TOOL_IDS_STATE_KEY = new(nameof(assistantToolIds));
private static readonly AssistantSessionStateKey<PluginAssistants?> ASSISTANT_PLUGIN_STATE_KEY = new(nameof(assistantPlugin));
private static readonly AssistantSessionStateKey<AssistantState> ASSISTANT_STATE_STATE_KEY = new(nameof(assistantState));
private static readonly AssistantSessionStateKey<Dictionary<string, string>> IMAGE_CACHE_STATE_KEY = new(nameof(imageCache));
@@ -90,6 +100,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
state.Set(ALLOW_PROFILES_STATE_KEY, this.allowProfiles);
state.Set(SUBMIT_TEXT_STATE_KEY, this.submitText);
state.Set(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, this.showFooterProfileSelection);
state.Set(ASSISTANT_TOOL_IDS_STATE_KEY, this.assistantToolIds);
state.Set(ASSISTANT_PLUGIN_STATE_KEY, this.assistantPlugin);
state.Set(ASSISTANT_STATE_STATE_KEY, this.assistantState.Clone());
state.SetDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
@@ -110,6 +121,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
state.Restore(ALLOW_PROFILES_STATE_KEY, value => this.allowProfiles = value);
state.Restore(SUBMIT_TEXT_STATE_KEY, value => this.submitText = value);
state.Restore(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, value => this.showFooterProfileSelection = value);
state.Restore(ASSISTANT_TOOL_IDS_STATE_KEY, value => this.assistantToolIds = value);
state.Restore(ASSISTANT_PLUGIN_STATE_KEY, value => this.assistantPlugin = value);
state.Restore(ASSISTANT_STATE_STATE_KEY, value => this.assistantState.CopyFrom(value));
state.RestoreDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
@@ -159,6 +171,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
this.systemPrompt = pluginAssistant.SystemPrompt;
this.submitText = pluginAssistant.SubmitText;
this.allowProfiles = pluginAssistant.AllowProfiles;
this.assistantToolIds = ReadPluginToolIds(pluginAssistant);
this.showFooterProfileSelection = !pluginAssistant.HasEmbeddedProfileSelection;
this.pluginPath = pluginAssistant.PluginPath;
var pluginHash = pluginAssistant.ComputeAuditHash();
@@ -353,6 +366,7 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
this.systemPrompt = updatedPlugin.SystemPrompt;
this.submitText = updatedPlugin.SubmitText;
this.allowProfiles = updatedPlugin.AllowProfiles;
this.assistantToolIds = ReadPluginToolIds(updatedPlugin);
this.showFooterProfileSelection = !updatedPlugin.HasEmbeddedProfileSelection;
this.pluginPath = updatedPlugin.PluginPath;
var pluginHash = updatedPlugin.ComputeAuditHash();
@@ -369,6 +383,16 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
#endregion
/// <summary>
/// Reads the tools this plugin names for its assistant.
/// </summary>
/// <remarks>
/// An ID this installation does not know stays in the set on purpose: the tool may arrive with
/// a plugin installed later, and dropping it here would silently turn a plugin that names tools
/// into one that lets the user choose.
/// </remarks>
private static HashSet<string>? ReadPluginToolIds(PluginAssistants plugin) => plugin.AssistantToolIds is { } toolIds ? ToolSelectionRules.NormalizeSelection(toolIds) : null;
private string ResolveImageSource(AssistantImage image)
{
if (string.IsNullOrWhiteSpace(image.Src))