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

@@ -1,6 +1,7 @@
using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.ToolCallingSystem;
namespace AIStudio.Assistants.BatchProcessing;
@@ -86,18 +87,34 @@ public partial class AssistantBatchProcessing
""";
}
private async Task<string> CallAIAsync(string fileName, string fileContent, CancellationToken token)
/// <param name="fileName">The name of the document being processed.</param>
/// <param name="fileContent">The content handed to the model.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The answer of the model, and which tools it used to get there.</returns>
private async Task<(string Answer, string UsedTools)> CallAIAsync(string fileName, string fileContent, CancellationToken token)
{
//
// Every file of the batch gets the tools the user picked for the job. The batch builds its
// own throwaway thread per file instead of going through the assistant's own thread, so it
// has to hand the tools over itself.
//
var chatThread = new ChatThread
{
IncludeDateTime = false,
SelectedProvider = this.ProviderSettings.Id,
SelectedProfile = Profile.NO_PROFILE.Id,
SelectedToolIds = [..this.SelectedToolIds],
SystemPrompt = this.SystemPrompt,
WorkspaceId = Guid.Empty,
ChatId = Guid.NewGuid(),
Name = this.Title,
Blocks = [],
RuntimeComponent = this.Component,
RuntimeSelectedToolIds = this.GetRunnableToolIds(),
// Always true here, unlike in the assistant base: a batch run takes its tools from the
// selected policy or from its own field, never from the tool selection in the footer.
RuntimeToolsAreAssistantManaged = true,
};
var userPrompt = new ContentText
@@ -123,6 +140,32 @@ public partial class AssistantBatchProcessing
});
await aiText.CreateFromProviderAsync(this.ProviderSettings.CreateProvider(), this.ProviderSettings.Model, userPrompt, chatThread, token);
return aiText.Text.RemoveThinkTags().Trim();
return (aiText.Text.RemoveThinkTags().Trim(), this.SummarizeToolUsage(aiText));
}
/// <summary>
/// Sums up the tool calls of one document for the log.
/// </summary>
/// <remarks>
/// Names each tool once with how often it ran, because a model may search
/// several times for the same document. A call that failed or was blocked
/// is named with its outcome: for judging an answer it matters whether a
/// tool delivered or came back empty-handed.
/// </remarks>
private string SummarizeToolUsage(ContentText aiText) => string.Join(", ", aiText.ToolInvocations
.GroupBy(invocation => (invocation.ToolName, invocation.Status))
.OrderBy(group => group.Key.ToolName, StringComparer.OrdinalIgnoreCase)
.Select(group => this.FormatToolUsage(group.Key.ToolName, group.Key.Status, group.Count())));
private string FormatToolUsage(string toolName, ToolInvocationTraceStatus status, int count)
{
var nameWithCount = count > 1 ? $"{toolName} ({count}x)" : toolName;
return status switch
{
ToolInvocationTraceStatus.ERROR => $"{nameWithCount} [{this.T("failed")}]",
ToolInvocationTraceStatus.BLOCKED => $"{nameWithCount} [{this.T("blocked")}]",
_ => nameWithCount,
};
}
}