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

@@ -6,6 +6,7 @@ using AIStudio.Tools.AIJobs;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Media;
using AIStudio.Tools.Services;
using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
@@ -27,6 +28,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
[Inject]
protected RustService RustService { get; init; } = null!;
[Inject]
protected ToolRegistry ToolRegistry { get; init; } = null!;
[Inject]
protected NavigationManager NavigationManager { get; init; } = null!;
@@ -127,8 +131,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected virtual bool HasSettingsPanel => typeof(TSettings) != typeof(NoSettingsPanel);
protected HashSet<string> SelectedToolIds = [];
private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));
protected MudForm? Form;
protected CancellationTokenSource? CancellationTokenSource;
private bool isDisposed;
@@ -185,6 +191,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
this.CurrentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.CurrentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
this.SelectedToolIds = this.SettingsManager.GetDefaultToolIds(this.Component);
await this.OnDefaultsAppliedAsync();
this.assistantSessionKey = new(this.Component, this.AssistantSessionInstanceId);
await this.AttachAssistantSessionIfAvailable();
@@ -236,6 +243,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
private async Task Start()
{
await this.RefreshProviderSelectionFromConfigurationAsync();
if (this.ProviderSettings == Settings.Provider.NONE)
return;
if (this.MediaTranscriptionService.IsBusy(this.CurrentMediaImportOwner))
return;
@@ -357,6 +368,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
ChatId = Guid.NewGuid(),
Name = string.Format(this.TB("Assistant - {0}"), this.Title),
Blocks = [],
RuntimeComponent = this.Component,
};
}
@@ -373,16 +385,71 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
ChatId = chatId,
Name = name,
Blocks = [],
RuntimeComponent = this.Component,
};
return chatId;
}
private Task RefreshProviderSelectionFromConfigurationAsync()
{
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(this.Component, this.ProviderSettings.Id);
return Task.CompletedTask;
}
protected virtual void ResetProviderAndProfileSelection()
{
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
this.CurrentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.CurrentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
this.SelectedToolIds = this.SettingsManager.GetDefaultToolIds(this.Component);
}
/// <summary>
/// The tools this assistant runs with when its own rules name them, instead of asking the user.
/// </summary>
/// <remarks>
/// Null is the normal case: the user picks the tools. An assistant whose configuration already
/// says which tools belong to a run — a document analysis policy, for instance — returns them
/// here. Its tool selection then disappears from the footer, because there is nothing left to
/// choose: whoever wrote the policy has decided, and a user working with a policy rolled out by
/// their organization gets it as configured.
/// </remarks>
protected virtual IReadOnlySet<string>? AssistantManagedToolIds => null;
/// <summary>
/// The tools this assistant may hand to a model with the provider it currently uses.
/// </summary>
/// <remarks>
/// Whether the tools come from the assistant's own rules or from the user, the provider filter
/// always has the last word: a tool asking for more confidence than the selected provider has
/// never reaches the model, no matter who put it on the list. That filter belongs here rather
/// than into the stored selection, because a provider with too little confidence must not cost
/// the user a tool for good.
/// </remarks>
protected HashSet<string> GetRunnableToolIds()
{
if (this.AssistantManagedToolIds is not null)
return this.ToolRegistry.FilterToolIdsForProvider(this.ProviderSettings, this.AssistantManagedToolIds);
// What the user cannot see, the assistant does not use:
if (!this.SettingsManager.IsToolSelectionVisible(this.Component))
return [];
return this.ToolRegistry.FilterToolIdsForProvider(this.ProviderSettings, this.SelectedToolIds);
}
/// <summary>
/// Takes over a changed tool selection, no matter where the user made it.
/// </summary>
/// <remarks>
/// The footer offers one; an assistant may instead put the tools next to the setting they
/// belong to, as the batch processing does with its instructions. Both end up here.
/// </remarks>
protected Task SelectedToolIdsChanged(HashSet<string> updatedToolIds)
{
this.SelectedToolIds = ToolSelectionRules.NormalizeSelection(updatedToolIds);
return Task.CompletedTask;
}
protected DateTimeOffset AddUserRequest(string request, bool hideContentFromUser = false, params List<FileAttachment> attachments)
@@ -443,6 +510,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
{
this.ChatThread.Blocks.Add(this.ResultingContentBlock);
this.ChatThread.SelectedProvider = this.ProviderSettings.Id;
this.ChatThread.RuntimeComponent = this.Component;
this.ChatThread.SelectedToolIds = [..this.SelectedToolIds];
this.ChatThread.RuntimeSelectedToolIds = this.GetRunnableToolIds();
this.ChatThread.RuntimeToolsAreAssistantManaged = this.AssistantManagedToolIds is not null;
}
this.IsProcessing = true;
@@ -911,6 +982,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
state.Set(RESULTING_CONTENT_BLOCK_STATE_KEY, this.ResultingContentBlock);
state.Set(INPUT_ISSUES_STATE_KEY, this.InputIssues);
state.Set(IS_PROCESSING_STATE_KEY, this.IsProcessing);
state.Set(SELECTED_TOOL_IDS_STATE_KEY, this.SelectedToolIds);
this.CaptureCustomAssistantSessionState(state);
return state.ToDictionary();
@@ -938,6 +1010,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
reader.Restore(RESULTING_CONTENT_BLOCK_STATE_KEY, value => this.ResultingContentBlock = value);
reader.Restore(INPUT_ISSUES_STATE_KEY, value => this.InputIssues = value);
reader.Restore(IS_PROCESSING_STATE_KEY, value => this.IsProcessing = value);
reader.Restore(SELECTED_TOOL_IDS_STATE_KEY, value => this.SelectedToolIds = ToolSelectionRules.NormalizeSelection(value));
this.RestoreCustomAssistantSessionState(reader);
}
@@ -948,4 +1021,4 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected virtual void RestoreCustomAssistantSessionState(AssistantSessionStateReader state) { }
#endregion
}
}