values) => this.SettingsManager.ConfigurationData.Tools.DefaultToolIdsByComponent[this.Component.ToString()] = [..ToolSelectionRules.NormalizeSelection(values)];
+}
diff --git a/app/MindWork AI Studio/Components/ToolSelection.razor b/app/MindWork AI Studio/Components/ToolSelection.razor
new file mode 100644
index 000000000..a98f02e27
--- /dev/null
+++ b/app/MindWork AI Studio/Components/ToolSelection.razor
@@ -0,0 +1,72 @@
+@using AIStudio.Settings
+@using AIStudio.Tools.ToolCallingSystem
+@inherits MSGComponentBase
+
+
+
+
+
+
+
+
+
+
+
+ @T("Tool Selection")
+
+
+
+
+
+ @if (!this.SupportsTools)
+ {
+ @T("The selected provider or model does not support tool calling.")
+ }
+ else if (this.Disabled)
+ {
+
+ @T("Tool changes are locked while a response is running. Your current selection is shown below and applies again from the next message once the run is finished.")
+
+ }
+ else if (this.catalog.Count == 0)
+ {
+ @T("No tools are available in this context.")
+ }
+
+ @if (this.SupportsTools && this.catalog.Count > 0)
+ {
+ @foreach (var item in this.catalog)
+ {
+ var isSelected = this.SelectedToolIds.Contains(item.Definition.Id);
+ var isConfigured = item.ConfigurationState.IsConfigured;
+ var dependencyHint = this.GetDependencyHint(item.Definition.Id);
+
+
+
+
+
+
+ @item.Implementation.GetDisplayName()
+
+
+
+
+ @if (!isConfigured)
+ {
+ @(string.IsNullOrWhiteSpace(item.ConfigurationState.Message) ? T("Required settings are missing. Configure this tool before enabling it.") : item.ConfigurationState.Message)
+ }
+ @if (!string.IsNullOrWhiteSpace(dependencyHint))
+ {
+ @dependencyHint
+ }
+
+ }
+ }
+
+
+
+ @T("Close")
+
+
+
+
diff --git a/app/MindWork AI Studio/Components/ToolSelection.razor.cs b/app/MindWork AI Studio/Components/ToolSelection.razor.cs
new file mode 100644
index 000000000..7f96fb069
--- /dev/null
+++ b/app/MindWork AI Studio/Components/ToolSelection.razor.cs
@@ -0,0 +1,98 @@
+using AIStudio.Dialogs.Settings;
+using AIStudio.Provider;
+using AIStudio.Settings;
+using AIStudio.Tools;
+using AIStudio.Tools.ToolCallingSystem;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Components;
+
+public partial class ToolSelection : MSGComponentBase
+{
+ [Parameter]
+ public AIStudio.Tools.Components Component { get; set; } = AIStudio.Tools.Components.CHAT;
+
+ [Parameter]
+ public required AIStudio.Settings.Provider LLMProvider { get; set; }
+
+ [Parameter]
+ public HashSet SelectedToolIds { get; set; } = [];
+
+ [Parameter]
+ public EventCallback> SelectedToolIdsChanged { get; set; }
+
+ [Parameter]
+ public bool Disabled { get; set; }
+
+ [Parameter]
+ public string PopoverButtonClasses { get; set; } = string.Empty;
+
+ [Inject]
+ private ToolRegistry ToolRegistry { get; init; } = null!;
+
+ [Inject]
+ private IDialogService DialogService { get; init; } = null!;
+
+ private bool showSelection;
+ private IReadOnlyList catalog = [];
+
+ protected override void OnParametersSet()
+ {
+ this.SelectedToolIds = ToolSelectionRules.NormalizeSelection(this.SelectedToolIds);
+ base.OnParametersSet();
+ }
+
+ private bool SupportsTools =>
+ this.LLMProvider != AIStudio.Settings.Provider.NONE &&
+ this.LLMProvider.GetModelCapabilities().Contains(Capability.CHAT_COMPLETION_API) &&
+ this.LLMProvider.GetModelCapabilities().Contains(Capability.FUNCTION_CALLING);
+
+ private async Task ToggleSelection()
+ {
+ this.showSelection = !this.showSelection;
+ if (this.showSelection)
+ this.catalog = await this.ToolRegistry.GetCatalogAsync(this.Component);
+ }
+
+ private void Hide() => this.showSelection = false;
+
+ private async Task ChangeSelection(string toolId, bool isSelected)
+ {
+ var updated = new HashSet(this.SelectedToolIds, StringComparer.Ordinal);
+ if (isSelected)
+ updated.Add(toolId);
+ else
+ updated.Remove(toolId);
+
+ updated = ToolSelectionRules.NormalizeSelection(updated);
+ this.SelectedToolIds = updated;
+ await this.SelectedToolIdsChanged.InvokeAsync(updated);
+ }
+
+ private bool IsSelectionLockedByDependency(string toolId) => ToolSelectionRules.IsRequiredBySelectedTools(toolId, this.SelectedToolIds);
+
+ private string? GetDependencyHint(string toolId)
+ {
+ if (toolId == ToolSelectionRules.WEB_SEARCH_TOOL_ID)
+ return this.T("Enabling this tool also enables Read Web Page.");
+
+ if (this.IsSelectionLockedByDependency(toolId))
+ return this.T("This tool is currently required because Web Search is enabled.");
+
+ return null;
+ }
+
+ private async Task OpenSettings(string toolId)
+ {
+ var parameters = new DialogParameters
+ {
+ { x => x.ToolId, toolId },
+ };
+
+ var dialog = await this.DialogService.ShowAsync(null, parameters, Dialogs.DialogOptions.FULLSCREEN);
+ await dialog.Result;
+ this.catalog = await this.ToolRegistry.GetCatalogAsync(this.Component);
+ this.StateHasChanged();
+ }
+}
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAgenda.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAgenda.razor
index dcaf18ff7..789d7a016 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAgenda.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAgenda.razor
@@ -36,6 +36,7 @@
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor
index 40f3331f0..c00fe8d40 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor
@@ -32,6 +32,7 @@
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor
index d9ed5a90a..94f9b0211 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor
@@ -22,6 +22,8 @@
+
+
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor
index 6cfed1ac7..2dd954f65 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogCoding.razor
@@ -22,6 +22,7 @@
+
Close
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor
index 7130f3cf0..87a37a4f1 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor
@@ -19,10 +19,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor
index a64528d09..6f7d87984 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor
@@ -19,10 +19,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor
index 187e0523e..d4cb90fce 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor
@@ -15,10 +15,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor
index 9d2c47bcd..563a7c347 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor
@@ -26,10 +26,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor
index 71947b14f..817774ab4 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor
@@ -17,6 +17,7 @@
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor
index 1fed1f083..dacffa8cc 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor
@@ -20,6 +20,7 @@
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor
index 6cdfc96f6..5849256b4 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor
@@ -21,10 +21,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSlideBuilder.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSlideBuilder.razor
index 18d512803..6d019598f 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSlideBuilder.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSlideBuilder.razor
@@ -25,6 +25,7 @@
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor
index 0a78e616f..9dc1fdf2b 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor
@@ -19,10 +19,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor
index 9e1e183b2..f17e57ad3 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor
@@ -29,10 +29,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor
index f3db4a3c0..61187dc86 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor
@@ -23,10 +23,11 @@
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor
index ff96ced6b..2ff22788d 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor
@@ -23,6 +23,7 @@
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor
new file mode 100644
index 000000000..b41ee6161
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor
@@ -0,0 +1,52 @@
+@using AIStudio.Tools.ToolCallingSystem
+@inherits SettingsDialogBase
+
+
+
+
+
+ @(this.implementation?.GetDisplayName() ?? T("Tool Settings"))
+
+
+
+ @if (this.toolDefinition is null)
+ {
+ @T("The selected tool could not be loaded.")
+ }
+ else
+ {
+
+ @this.implementation?.GetDescription()
+
+
+
+ @foreach (var property in this.toolDefinition.SettingsSchema.Properties)
+ {
+ var fieldName = property.Key;
+ var field = property.Value;
+ if (field.EnumValues.Count > 0)
+ {
+
+ @foreach (var option in field.EnumValues)
+ {
+ @option
+ }
+
+ }
+ else
+ {
+
+ }
+ }
+
+ }
+
+
+
+ @T("Cancel")
+
+
+ @T("Save")
+
+
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs
new file mode 100644
index 000000000..e4cf432c5
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs
@@ -0,0 +1,51 @@
+using AIStudio.Tools.ToolCallingSystem;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Dialogs.Settings;
+
+public partial class ToolSettingsDialog : SettingsDialogBase
+{
+ [Parameter]
+ public string ToolId { get; set; } = string.Empty;
+
+ [Inject]
+ private ToolRegistry ToolRegistry { get; init; } = null!;
+
+ [Inject]
+ private ToolSettingsService ToolSettingsService { get; init; } = null!;
+
+ private ToolDefinition? toolDefinition;
+ private IToolImplementation? implementation;
+ private Dictionary values = new(StringComparer.Ordinal);
+
+ protected override async Task OnInitializedAsync()
+ {
+ await base.OnInitializedAsync();
+ this.toolDefinition = this.ToolRegistry.GetDefinition(this.ToolId);
+ if (this.toolDefinition is not null)
+ {
+ this.implementation = this.ToolRegistry.GetImplementation(this.toolDefinition.ImplementationKey);
+ this.values = await this.ToolSettingsService.GetSettingsAsync(this.toolDefinition);
+ }
+ }
+
+ private string GetValue(string fieldName) => this.values.GetValueOrDefault(fieldName, string.Empty);
+
+ private string GetFieldLabel(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
+ this.implementation?.GetSettingsFieldLabel(fieldName, fieldDefinition) ?? fieldDefinition.Title;
+
+ private string GetFieldDescription(string fieldName, ToolSettingsFieldDefinition fieldDefinition) =>
+ this.implementation?.GetSettingsFieldDescription(fieldName, fieldDefinition) ?? fieldDefinition.Description;
+
+ private void UpdateValue(string fieldName, string? value) => this.values[fieldName] = value ?? string.Empty;
+
+ private async Task Save()
+ {
+ if (this.toolDefinition is null)
+ return;
+
+ await this.ToolSettingsService.SaveSettingsAsync(this.toolDefinition, this.values);
+ this.MudDialog.Close();
+ }
+}
diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor
index 702018075..56ec6e990 100644
--- a/app/MindWork AI Studio/Pages/Settings.razor
+++ b/app/MindWork AI Studio/Pages/Settings.razor
@@ -21,6 +21,8 @@
}
+
+
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
@@ -31,4 +33,4 @@
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs
index f19344d6f..0effbcc5c 100644
--- a/app/MindWork AI Studio/Program.cs
+++ b/app/MindWork AI Studio/Program.cs
@@ -1,10 +1,11 @@
using AIStudio.Agents;
using AIStudio.Settings;
+using AIStudio.Tools.ToolCallingSystem;
using AIStudio.Tools.Databases;
using AIStudio.Tools.Databases.Qdrant;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
-
+using AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Logging.Console;
@@ -168,6 +169,12 @@ public static async Task Main()
builder.Services.AddSingleton(rust);
builder.Services.AddMudMarkdownClipboardService();
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
diff --git a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs
index 3535809d0..24a6f4961 100644
--- a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs
+++ b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs
@@ -1,7 +1,5 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Provider.OpenAI;
@@ -24,52 +22,23 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
///
public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{
- // Get the API key:
- var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
- if(!requestedSecret.Success)
- yield break;
-
- // Prepare the system prompt:
- var systemPrompt = new TextMessage
- {
- Role = "system",
- Content = chatThread.PrepareSystemPrompt(settingsManager),
- };
-
- // Parse the API parameters:
- var apiParameters = this.ParseAdditionalApiParameters();
-
- // Build the list of messages:
- var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel);
-
- // Prepare the AlibabaCloud HTTP chat request:
- var alibabaCloudChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
- {
- Model = chatModel.Id,
-
- // Build the messages:
- // - First of all the system prompt
- // - Then none-empty user and AI messages
- Messages = [systemPrompt, ..messages],
-
- Stream = true,
- AdditionalApiParameters = apiParameters
- }, JSON_SERIALIZER_OPTIONS);
-
- async Task RequestBuilder()
- {
- // Build the HTTP post request:
- var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
-
- // Set the authorization header:
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
-
- // Set the content:
- request.Content = new StringContent(alibabaCloudChatRequest, Encoding.UTF8, "application/json");
- return request;
- }
-
- await foreach (var content in this.StreamChatCompletionInternal("AlibabaCloud", RequestBuilder, token))
+ await foreach (var content in this.StreamOpenAICompatibleChatCompletion(
+ "AlibabaCloud",
+ chatModel,
+ chatThread,
+ settingsManager,
+ () => chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel),
+ (systemPrompt, messages, apiParameters, stream, tools) =>
+ Task.FromResult(new ChatCompletionAPIRequest
+ {
+ Model = chatModel.Id,
+ Messages = [systemPrompt, ..messages],
+ Stream = stream,
+ Tools = tools,
+ ParallelToolCalls = tools is null ? null : true,
+ AdditionalApiParameters = apiParameters
+ }),
+ token: token))
yield return content;
}
@@ -183,4 +152,4 @@ private async Task> LoadModels(string[] prefixes, SecretStore
return modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture)));
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs
index 9b7298241..2488db637 100644
--- a/app/MindWork AI Studio/Provider/BaseProvider.cs
+++ b/app/MindWork AI Studio/Provider/BaseProvider.cs
@@ -10,11 +10,14 @@
using AIStudio.Provider.OpenAI;
using AIStudio.Provider.SelfHosted;
using AIStudio.Settings;
+using AIStudio.Tools.ToolCallingSystem;
using AIStudio.Tools.MIME;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
+using Microsoft.Extensions.DependencyInjection;
+
using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Provider;
@@ -565,6 +568,206 @@ protected async IAsyncEnumerable StreamResponsesInternal
+ /// Streams the chat completion from an OpenAI-compatible provider using the Chat Completion API.
+ ///
+ ///