diff --git a/AGENTS.md b/AGENTS.md
index 6bf4eb5f8..528236f56 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -119,6 +119,19 @@ When adding configuration options, update:
- `app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs` for parsing logic of complex configuration objects.
- `app/MindWork AI Studio/Plugins/configuration/plugin.lua` to document the new configuration option.
+## Tool Calling System
+
+**Documentation:** `documentation/Tools.md`
+
+When adding, changing, or removing model-driven tools, keep these parts in sync:
+- `app/MindWork AI Studio/wwwroot/tool_definitions/` for the tool JSON definition.
+- `app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/` for the `IToolImplementation` class.
+- `app/MindWork AI Studio/Program.cs` for DI registration of the implementation.
+- `app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs` when default tool dependencies or minimum provider confidence rules change.
+- `app/MindWork AI Studio/Plugins/configuration/plugin.lua` when administrators can configure or manage the tool or its settings.
+
+Tool implementations must treat model-provided arguments as untrusted input. Validate settings and arguments, protect secrets with `SensitiveTraceArgumentNames`, use `ToolExecutionBlockedException` for intentional policy blocks, and check provider confidence before returning sensitive data to the model.
+
## RAG (Retrieval-Augmented Generation)
RAG integration is currently in development (preview feature). Architecture:
@@ -214,4 +227,4 @@ following words:
- Downgraded
- Upgraded
-The entire changelog is sorted by these categories in the order shown above. The language used for the changelog is US English.
\ No newline at end of file
+The entire changelog is sorted by these categories in the order shown above. The language used for the changelog is US English.
diff --git a/README.md b/README.md
index a594ff410..7a097881d 100644
--- a/README.md
+++ b/README.md
@@ -185,6 +185,8 @@ If you're interested in learning more about future plans, check out our [roadmap
You want to know how to build MindWork AI Studio from source? [Check out the instructions here](documentation/Build.md).
+Do you want to add or maintain model-driven tools? [Read the tool development guide here](documentation/Tools.md).
+
@@ -213,4 +215,4 @@ MindWork AI Studio is licensed under the `FSL-1.1-MIT` license (functional sourc
For more details, refer to the [LICENSE](LICENSE.md) file. This license structure ensures you have plenty of freedom to use and enjoy the software while protecting our work.
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor b/app/MindWork AI Studio/Assistants/AssistantBase.razor
index 3268612d7..c45ac7d95 100644
--- a/app/MindWork AI Studio/Assistants/AssistantBase.razor
+++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor
@@ -151,6 +151,11 @@
}
+ @if (this.SettingsManager.IsToolSelectionVisible(this.Component))
+ {
+
+ }
+
diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
index 632722ab8..bc1a7387c 100644
--- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
+++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
@@ -3,6 +3,7 @@
using AIStudio.Settings;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.Services;
+using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
@@ -93,6 +94,7 @@ public abstract partial class AssistantBase : AssistantLowerBase wher
protected ChatThread? chatThread;
protected IContent? lastUserPrompt;
protected CancellationTokenSource? cancellationTokenSource;
+ protected HashSet selectedToolIds = [];
private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));
@@ -124,6 +126,7 @@ protected override async Task OnInitializedAsync()
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);
}
protected override async Task OnParametersSetAsync()
@@ -223,6 +226,7 @@ protected void CreateChatThread()
ChatId = Guid.NewGuid(),
Name = string.Format(this.TB("Assistant - {0}"), this.Title),
Blocks = [],
+ RuntimeComponent = this.Component,
};
}
@@ -239,6 +243,7 @@ protected Guid CreateChatThread(Guid workspaceId, string name)
ChatId = chatId,
Name = name,
Blocks = [],
+ RuntimeComponent = this.Component,
};
return chatId;
@@ -250,6 +255,12 @@ protected virtual void ResetProviderAndProfileSelection()
this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
}
+
+ protected Task SelectedToolIdsChanged(HashSet updatedToolIds)
+ {
+ this.selectedToolIds = ToolSelectionRules.NormalizeSelection(updatedToolIds);
+ return Task.CompletedTask;
+ }
protected DateTimeOffset AddUserRequest(string request, bool hideContentFromUser = false, params List attachments)
{
@@ -297,6 +308,10 @@ protected async Task AddAIResponseAsync(DateTimeOffset time, bool hideCo
{
this.chatThread.Blocks.Add(this.resultingContentBlock);
this.chatThread.SelectedProvider = this.providerSettings.Id;
+ this.chatThread.RuntimeComponent = this.Component;
+ this.chatThread.RuntimeSelectedToolIds = this.SettingsManager.IsToolSelectionVisible(this.Component)
+ ? ToolSelectionRules.NormalizeSelection(this.selectedToolIds)
+ : [];
}
this.isProcessing = true;
diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index c94b4b7ae..ce1be4581 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -1684,21 +1684,42 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CHATROLEEXTENSIONS::T601166687"] = "AI"
-- Edit Message
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1183581066"] = "Edit Message"
+-- Result
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347088452"] = "Result"
+
-- Do you really want to remove this message?
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you really want to remove this message?"
-- Yes, remove the AI response and edit it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it"
+-- Failed
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1434043348"] = "Failed"
+
+-- Tool Calls ({0})
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1493057571"] = "Tool Calls ({0})"
+
+-- Executed
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1564757972"] = "Executed"
+
-- Yes, regenerate it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it"
+-- No result
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1684269223"] = "No result"
+
-- Yes, remove it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it"
-- Number of sources
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1848978959"] = "Number of sources"
+-- Show {0} tool calls
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1981771421"] = "Show {0} tool calls"
+
+-- Show tool call for {0}
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2004842583"] = "Show tool call for {0}"
+
-- Do you really want to edit this message? In order to edit this message, the AI response will be deleted.
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you really want to edit this message? In order to edit this message, the AI response will be deleted."
@@ -1708,6 +1729,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes
-- Regenerate Message
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message"
+-- Arguments
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2738624831"] = "Arguments"
+
-- Number of attachments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3018847255"] = "Number of attachments"
@@ -1717,9 +1741,15 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot
-- Edit
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit"
+-- Unknown
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3424652889"] = "Unknown"
+
-- Regenerate
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate"
+-- Blocked
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3816336467"] = "Blocked"
+
-- Do you really want to regenerate this message?
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3878878761"] = "Do you really want to regenerate this message?"
@@ -1729,9 +1759,15 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove
-- No, keep it
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it"
+-- No tool calls
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4224149521"] = "No tool calls"
+
-- Export Chat to Microsoft Word
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word"
+-- No arguments
+UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T931993614"] = "No arguments"
+
-- The local image file does not exist. Skipping the image.
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T255679918"] = "The local image file does not exist. Skipping the image."
@@ -1882,15 +1918,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T252
-- Select a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2579793544"] = "Select a minimum confidence level"
--- You have selected 1 preview feature.
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T1384241824"] = "You have selected 1 preview feature."
-
--- No preview features selected.
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] = "No preview features selected."
-
--- You have selected {0} preview features.
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features."
-
-- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider"
@@ -2590,6 +2617,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237
-- Export configuration
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Export configuration"
+-- Settings
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1258653480"] = "Settings"
+
+-- Description
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1725856265"] = "Description"
+
+-- Icon
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1759955728"] = "Icon"
+
+-- Configure global settings for each tool. Tool defaults for chat and assistants are configured in the corresponding feature settings.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T176751696"] = "Configure global settings for each tool. Tool defaults for chat and assistants are configured in the corresponding feature settings."
+
+-- This tool still needs to be configured.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T1958939818"] = "This tool still needs to be configured."
+
+-- Missing required settings: {0}
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2588115579"] = "Missing required settings: {0}"
+
+-- Name
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T266367750"] = "Name"
+
+-- No minimum confidence level chosen
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T2828607242"] = "No minimum confidence level chosen"
+
+-- Minimum provider confidence
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3461070436"] = "Minimum provider confidence"
+
+-- Tool Settings
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T3730473128"] = "Tool Settings"
+
+-- State
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTOOLS::T502047894"] = "State"
+
-- No transcription provider configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet."
@@ -2659,6 +2719,66 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1392042694"] = "Ope
-- License:
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1908172666"] = "License:"
+-- Tool selection is hidden
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2096103917"] = "Tool selection is hidden"
+
+-- You have selected 1 tool.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2493128368"] = "You have selected 1 tool."
+
+-- Choose which tools should be preselected for new runs of this assistant.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T2696618758"] = "Choose which tools should be preselected for new runs of this assistant."
+
+-- Default tools for this assistant
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3253667950"] = "Default tools for this assistant"
+
+-- Tool selection is visible
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3384582069"] = "Tool selection is visible"
+
+-- Show tool selection in this assistant?
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3494508870"] = "Show tool selection in this assistant?"
+
+-- You have selected {0} tools.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3729156356"] = "You have selected {0} tools."
+
+-- No tools selected.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T3934845540"] = "No tools selected."
+
+-- Default tools for chat
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T907403808"] = "Default tools for chat"
+
+-- Choose which tools should be preselected for new chats.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLDEFAULTSCONFIGURATION::T948842182"] = "Choose which tools should be preselected for new chats."
+
+-- This tool is currently required because Web Search is enabled.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1351725609"] = "This tool is currently required because Web Search is enabled."
+
+-- 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.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T1688023907"] = "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."
+
+-- Enabling this tool also enables Read Web Page.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3023833839"] = "Enabling this tool also enables Read Web Page."
+
+-- Required settings are missing. Configure this tool before enabling it.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3119156561"] = "Required settings are missing. Configure this tool before enabling it."
+
+-- The selected provider or model does not support tool calling.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3364063757"] = "The selected provider or model does not support tool calling."
+
+-- Close
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Close"
+
+-- No tools are available in this context.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "No tools are available in this context."
+
+-- This tool requires provider confidence {0}. The selected provider has {1}.
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T4097602620"] = "This tool requires provider confidence {0}. The selected provider has {1}."
+
+-- Tool Selection
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T749664565"] = "Tool Selection"
+
+-- Select tools
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T998515990"] = "Select tools"
+
-- You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1015366320"] = "You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation."
@@ -4696,6 +4816,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T13933
-- Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1528169602"] = "Preselect aspects for the LLM to focus on when generating slides, such as bullet points or specific topics to emphasize."
+-- Slide Planner Assistant options are preselected
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1549358578"] = "Slide Planner Assistant options are preselected"
+
+-- No Slide Planner Assistant options are preselected
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1694374279"] = "No Slide Planner Assistant options are preselected"
+
-- Choose whether the assistant should use the app default profile, no profile, or a specific profile.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T1766361623"] = "Choose whether the assistant should use the app default profile, no profile, or a specific profile."
@@ -4705,9 +4831,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T20146
-- Which audience organizational level should be preselected?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T216511105"] = "Which audience organizational level should be preselected?"
--- Preselect Slide Planner Assistant options?
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T227645894"] = "Preselect Slide Planner Assistant options?"
-
-- Preselect a profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2322771068"] = "Preselect a profile"
@@ -4724,26 +4847,23 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T25714
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T2645589441"] = "Preselect the audience age group"
-- Assistant: Slide Planner Assistant Options
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3215549988"] = "Assistant: Slide Planner Assistant Options"
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3226042276"] = "Assistant: Slide Planner Assistant Options"
-- Which audience expertise should be preselected?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3228597992"] = "Which audience expertise should be preselected?"
+-- Preselect Slide Planner Assistant options?
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T339924858"] = "Preselect Slide Planner Assistant options?"
+
-- Close
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3448155331"] = "Close"
-- Preselect important aspects
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T3705987833"] = "Preselect important aspects"
--- No Slide Planner Assistant options are preselected
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T4214398691"] = "No Slide Planner Assistant options are preselected"
-
-- Preselect the audience profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T861397972"] = "Preselect the audience profile"
--- Slide Planner Assistant options are preselected
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T93124146"] = "Slide Planner Assistant options are preselected"
-
-- Which audience age group should be preselected?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSLIDEBUILDER::T956845877"] = "Which audience age group should be preselected?"
@@ -5002,6 +5122,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3547
-- Preselect e-mail options?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832719342"] = "Preselect e-mail options?"
+-- Save
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T1294818664"] = "Save"
+
+-- Tool Settings
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3730473128"] = "Tool Settings"
+
+-- The selected tool could not be loaded.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T3907843187"] = "The selected tool could not be loaded."
+
+-- Cancel
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::TOOLSETTINGSDIALOG::T900713019"] = "Cancel"
+
-- Save
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Save"
@@ -5803,6 +5935,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to
-- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'."
+-- The tool calling request failed with status code {0}. See the logs for details.
+UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details."
+
-- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'"
@@ -5869,6 +6004,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un
-- no model selected
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected"
+-- The tool calling request failed with status code {0}. See the logs for details.
+UI_TEXT_CONTENT["AISTUDIO::PROVIDER::OPENAI::PROVIDEROPENAI::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details."
+
-- Model as configured by whisper.cpp
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp"
@@ -6724,6 +6862,108 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4174900468"] = "Sources pro
-- Sources provided by the AI
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SOURCEEXTENSIONS::T4261248356"] = "Sources provided by the AI"
+-- Tool
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T3517012711"] = "Tool"
+
+-- Tool description
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::ITOOLIMPLEMENTATION::T4056470505"] = "Tool description"
+
+-- Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1823236891"] = "Load a single web page, extract its main HTML content, and return structured working material for the model. Use the result to synthesize a natural-language answer instead of exposing the raw payload to the user."
+
+-- Optional global truncation limit for extracted Markdown returned to the model.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2066580916"] = "Optional global truncation limit for extracted Markdown returned to the model."
+
+-- Allowed private hosts must be host names only, without scheme or path.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2196457612"] = "Allowed private hosts must be host names only, without scheme or path."
+
+-- Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T237631450"] = "Optional host allowlist for private or VPN web pages. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider."
+
+-- Maximum Content Characters
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters"
+
+-- Optional HTTP timeout for loading a web page in seconds.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optional HTTP timeout for loading a web page in seconds."
+
+-- Allowed private host '{0}' is not valid.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3089707139"] = "Allowed private host '{0}' is not valid."
+
+-- Allowed Private Hosts
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3415515539"] = "Allowed Private Hosts"
+
+-- Timeout Seconds
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3567699845"] = "Timeout Seconds"
+
+-- Read Web Page
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3612587998"] = "Read Web Page"
+
+-- The web page was not loaded because private or VPN web pages require a High-confidence provider.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3856267430"] = "The web page was not loaded because private or VPN web pages require a High-confidence provider."
+
+-- Maximum Results
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximum Results"
+
+-- Optional comma-separated default categories. Do not set this together with default engines.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1342681591"] = "Optional comma-separated default categories. Do not set this together with default engines."
+
+-- Default Safe Search
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1343180281"] = "Default Safe Search"
+
+-- Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1739312423"] = "Base URL of the SearXNG instance. You can enter either the instance root URL or the /search endpoint."
+
+-- A SearXNG URL is required.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1746583720"] = "A SearXNG URL is required."
+
+-- Default Engines
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1865580137"] = "Default Engines"
+
+-- Optional fallback language code when the model does not provide a language.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1868101906"] = "Optional fallback language code when the model does not provide a language."
+
+-- Default Categories
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2053347010"] = "Default Categories"
+
+-- Default Language
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T2526826120"] = "Default Language"
+
+-- The configured SearXNG URL is not a valid absolute URL.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3038368943"] = "The configured SearXNG URL is not a valid absolute URL."
+
+-- Optional HTTP timeout for the search request in seconds.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3078115445"] = "Optional HTTP timeout for the search request in seconds."
+
+-- Timeout Seconds
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3567699845"] = "Timeout Seconds"
+
+-- Optional default maximum number of results returned to the model when the model does not provide a limit.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3603838271"] = "Optional default maximum number of results returned to the model when the model does not provide a limit."
+
+-- Web Search
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3815068443"] = "Web Search"
+
+-- Optional safe search policy sent to SearXNG when configured.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T3967748757"] = "Optional safe search policy sent to SearXNG when configured."
+
+-- Default categories and default engines cannot both be set for the web search tool.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4009446158"] = "Default categories and default engines cannot both be set for the web search tool."
+
+-- Optional comma-separated default engines. Do not set this together with default categories.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4108908537"] = "Optional comma-separated default engines. Do not set this together with default categories."
+
+-- The setting '{0}' must be a positive integer.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T4199432074"] = "The setting '{0}' must be a positive integer."
+
+-- Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T764865565"] = "Search the web with a configured SearXNG instance and return candidate URLs for the model. Use Read Web Page on relevant result URLs before answering factual or detailed web questions."
+
+-- The configured SearXNG URL must start with http:// or https://.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T944878454"] = "The configured SearXNG URL must start with http:// or https://."
+
+-- SearXNG URL
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T993547568"] = "SearXNG URL"
+
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::USERFILE::T185447014"] = "Pandoc Installation"
diff --git a/app/MindWork AI Studio/Chat/ChatThread.cs b/app/MindWork AI Studio/Chat/ChatThread.cs
index e8277cb5f..2ba1d7d53 100644
--- a/app/MindWork AI Studio/Chat/ChatThread.cs
+++ b/app/MindWork AI Studio/Chat/ChatThread.cs
@@ -1,8 +1,11 @@
using System.Globalization;
+using System.Text.Json.Serialization;
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
+using AIStudio.Tools;
+using AIStudio.Tools.ToolCallingSystem;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Chat;
@@ -79,6 +82,12 @@ public sealed record ChatThread
/// The content blocks of the chat thread.
///
public List Blocks { get; init; } = [];
+
+ [JsonIgnore]
+ public AIStudio.Tools.Components RuntimeComponent { get; set; } = AIStudio.Tools.Components.CHAT;
+
+ [JsonIgnore]
+ public HashSet RuntimeSelectedToolIds { get; set; } = [];
private bool allowProfile = true;
@@ -185,6 +194,17 @@ public string PrepareSystemPrompt(SettingsManager settingsManager)
}
LOGGER.LogInformation(logMessage);
+
+ var toolPolicy = this.BuildToolPolicyPrompt();
+ if (!string.IsNullOrWhiteSpace(toolPolicy))
+ {
+ systemPromptText = $"""
+ {systemPromptText}
+
+ {toolPolicy}
+ """;
+ }
+
if(!this.IncludeDateTime)
return systemPromptText;
@@ -205,6 +225,28 @@ public string PrepareSystemPrompt(SettingsManager settingsManager)
""";
}
+ private string BuildToolPolicyPrompt()
+ {
+ var normalizedToolIds = ToolSelectionRules.NormalizeSelection(this.RuntimeSelectedToolIds);
+ var hasWebSearch = normalizedToolIds.Contains(ToolSelectionRules.WEB_SEARCH_TOOL_ID);
+ var hasReadWebPage = normalizedToolIds.Contains(ToolSelectionRules.READ_WEB_PAGE_TOOL_ID);
+
+ if (hasWebSearch && hasReadWebPage)
+ return """
+ Tool usage policy for web search:
+ - Use the `web_search`-tool to discover relevant candidate URLs.
+ - Do not answer substantive web questions from search snippets alone when `read_web_page` is available.
+ - Search snippets alone are only sufficient for simple link-finding or very high-level orientation.
+ - After `web_search`, use the `read_web_page`-tool on at least one relevant result before answering questions that require facts, summaries, comparisons, current information, or other page-level details.
+ - Prefer answering from the extracted page content when it is available.
+ - Summarize tool results in natural language.
+ - Treat `read_web_page` results as working material for synthesis, not as final answer text.
+ - Add a sources-section to the end of your answer, where you link the sources that you used.
+ """;
+
+ return string.Empty;
+ }
+
///
/// Removes a content block from this chat thread.
///
@@ -287,4 +329,4 @@ public void Remove(IContent content, bool removeForRegenerate = false)
return new Tools.ERIClient.DataModel.ChatThread { ContentBlocks = contentBlocks };
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor
index 8d0689da1..4e0402bce 100644
--- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor
+++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor
@@ -11,9 +11,27 @@
-
- @this.Role.ToName() (@this.Time.LocalDateTime)
-
+
+
+ @this.Role.ToName() (@this.Time.LocalDateTime)
+
+ @if (this.HasToolTrace)
+ {
+
+
+
+
+
+
+
+
+ }
+
@if (this.Content.FileAttachments.Count > 0)
@@ -96,6 +114,67 @@
}
else
{
+ @if (this.HasToolTrace && this.showToolTrace)
+ {
+
+
+ @string.Format(T("Tool Calls ({0})"), textContent.ToolInvocations.Count)
+
+ @foreach (var invocation in textContent.ToolInvocations.OrderBy(x => x.Order))
+ {
+
+
+
+
+
+ @($"{invocation.Order}. {invocation.ToolName}")
+
+ @this.GetTraceStatusText(invocation)
+
+
+
+
+
+
+ @if (this.IsToolInvocationExpanded(invocation.Order))
+ {
+ @if (!string.IsNullOrWhiteSpace(invocation.StatusMessage))
+ {
+ @invocation.StatusMessage
+ }
+
+ @T("Result")
+
+ @this.GetToolInvocationResult(invocation)
+
+
+ @T("Arguments")
+ @if (invocation.Arguments.Count == 0)
+ {
+ @T("No arguments")
+ }
+ else
+ {
+
+ @foreach (var argument in invocation.Arguments)
+ {
+
+ @argument.Key: @argument.Value
+
+ }
+
+ }
+ }
+
+ }
+
+ }
+
var renderPlan = this.GetMarkdownRenderPlan(textContent.Text);
@foreach (var segment in renderPlan.Segments)
@@ -115,6 +194,13 @@
}
+
+ @if (this.Role is ChatRole.AI && !string.IsNullOrWhiteSpace(textContent.ToolRuntimeStatus.Message))
+ {
+
+ @textContent.ToolRuntimeStatus.Message
+
+ }
}
}
}
diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs
index e0b035ce1..84b41232d 100644
--- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs
+++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs
@@ -1,7 +1,9 @@
using AIStudio.Components;
using AIStudio.Dialogs;
using AIStudio.Tools.Services;
+using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
+using MudBlazor;
namespace AIStudio.Chat;
@@ -103,6 +105,8 @@ public partial class ContentBlockComponent : MSGComponentBase, IAsyncDisposable
private string lastMathRenderSignature = string.Empty;
private bool hasActiveMathContainer;
private bool isDisposed;
+ private bool showToolTrace;
+ private readonly HashSet expandedToolInvocations = [];
#region Overrides of ComponentBase
@@ -199,6 +203,27 @@ private int CreateRenderHash()
hash.Add(textValue.Length);
hash.Add(textValue.GetHashCode(StringComparison.Ordinal));
hash.Add(text.Sources.Count);
+ hash.Add(text.ToolInvocations.Count);
+ hash.Add(text.ToolRuntimeStatus.IsRunning);
+ hash.Add(text.ToolRuntimeStatus.Message);
+ hash.Add(this.showToolTrace);
+ hash.Add(this.expandedToolInvocations.Count);
+ foreach (var expandedInvocation in this.expandedToolInvocations.Order())
+ hash.Add(expandedInvocation);
+ foreach (var invocation in text.ToolInvocations)
+ {
+ hash.Add(invocation.Order);
+ hash.Add(invocation.ToolId);
+ hash.Add(invocation.Status);
+ hash.Add(invocation.StatusMessage);
+ hash.Add(invocation.Result);
+ hash.Add(invocation.Arguments.Count);
+ foreach (var argument in invocation.Arguments)
+ {
+ hash.Add(argument.Key);
+ hash.Add(argument.Value);
+ }
+ }
break;
case ContentImage image:
@@ -214,8 +239,55 @@ private int CreateRenderHash()
private string CardClasses => $"my-2 rounded-lg {this.Class}";
+ private bool HasToolTrace => this.Role is ChatRole.AI && this.GetToolInvocations().Count > 0;
+
private CodeBlockTheme CodeColorPalette => this.SettingsManager.IsDarkMode ? CodeBlockTheme.Dark : CodeBlockTheme.Default;
+ private static Color GetTraceColor(ToolInvocationTraceStatus status) => status switch
+ {
+ ToolInvocationTraceStatus.SUCCESS => Color.Success,
+ ToolInvocationTraceStatus.ERROR => Color.Error,
+ ToolInvocationTraceStatus.BLOCKED => Color.Warning,
+ _ => Color.Default,
+ };
+
+ private string GetTraceStatusText(ToolInvocationTrace trace) => trace.Status switch
+ {
+ ToolInvocationTraceStatus.SUCCESS => this.T("Executed"),
+ ToolInvocationTraceStatus.ERROR => this.T("Failed"),
+ ToolInvocationTraceStatus.BLOCKED => this.T("Blocked"),
+ _ => this.T("Unknown"),
+ };
+
+ private IReadOnlyList GetToolInvocations() => this.Content is ContentText textContent
+ ? textContent.ToolInvocations.OrderBy(x => x.Order).ToList()
+ : [];
+
+ private string GetToolTraceTooltip()
+ {
+ var invocations = this.GetToolInvocations();
+ return invocations.Count switch
+ {
+ 0 => this.T("No tool calls"),
+ 1 => string.Format(this.T("Show tool call for {0}"), invocations[0].ToolName),
+ _ => string.Format(this.T("Show {0} tool calls"), invocations.Count),
+ };
+ }
+
+ private void ToggleToolTrace() => this.showToolTrace = !this.showToolTrace;
+
+ private bool IsToolInvocationExpanded(int order) => this.expandedToolInvocations.Contains(order);
+
+ private void ToggleToolInvocation(int order)
+ {
+ if (!this.expandedToolInvocations.Add(order))
+ this.expandedToolInvocations.Remove(order);
+ }
+
+ private string GetToolInvocationResult(ToolInvocationTrace invocation) => string.IsNullOrWhiteSpace(invocation.Result)
+ ? this.T("No result")
+ : invocation.Result;
+
private MudMarkdownStyling MarkdownStyling => new()
{
CodeBlock = { Theme = this.CodeColorPalette },
diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs
index 3a9b8f9d4..81a34e35c 100644
--- a/app/MindWork AI Studio/Chat/ContentText.cs
+++ b/app/MindWork AI Studio/Chat/ContentText.cs
@@ -4,6 +4,7 @@
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.RAG.RAGProcesses;
+using AIStudio.Tools.ToolCallingSystem;
namespace AIStudio.Chat;
@@ -44,6 +45,11 @@ public sealed class ContentText : IContent
///
public List FileAttachments { get; set; } = [];
+ public List ToolInvocations { get; set; } = [];
+
+ [JsonIgnore]
+ public ToolRuntimeStatus ToolRuntimeStatus { get; set; } = new();
+
///
public async Task CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastUserPrompt, ChatThread? chatThread, CancellationToken token = default)
{
@@ -145,6 +151,19 @@ await Task.Run(async () =>
IsStreaming = this.IsStreaming,
Sources = [..this.Sources],
FileAttachments = [..this.FileAttachments],
+ ToolInvocations = [..this.ToolInvocations.Select(x => new ToolInvocationTrace
+ {
+ Order = x.Order,
+ ToolId = x.ToolId,
+ ToolName = x.ToolName,
+ ToolIcon = x.ToolIcon,
+ ToolCallId = x.ToolCallId,
+ Status = x.Status,
+ WasExecuted = x.WasExecuted,
+ StatusMessage = x.StatusMessage,
+ Arguments = new Dictionary(x.Arguments, StringComparer.Ordinal),
+ Result = x.Result,
+ })],
};
#endregion
@@ -214,4 +233,4 @@ public async Task PrepareTextContentForAI()
/// The text content.
///
public string Text { get; set; } = string.Empty;
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor
index 20bb5ec47..8f4fbf466 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor
@@ -123,6 +123,8 @@
+
+
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
index f734d620f..45fabf860 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
@@ -3,6 +3,7 @@
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
+using AIStudio.Tools.ToolCallingSystem;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@@ -64,6 +65,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private bool mustLoadChat;
private LoadChat loadChat;
private bool autoSaveEnabled;
+ private HashSet selectedToolIds = [];
private string currentWorkspaceName = string.Empty;
private Guid currentWorkspaceId = Guid.Empty;
private Guid currentChatThreadId = Guid.Empty;
@@ -79,7 +81,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
protected override async Task OnInitializedAsync()
{
// Apply the filters for the message bus:
- this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED ]);
+ this.ApplyFilters([], [ Event.HAS_CHAT_UNSAVED_CHANGES, Event.RESET_CHAT_STATE, Event.CHAT_STREAMING_DONE, Event.WORKSPACE_LOADED_CHAT_CHANGED, Event.CONFIGURATION_CHANGED ]);
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
@@ -91,6 +93,7 @@ protected override async Task OnInitializedAsync()
// Get the preselected chat template:
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
+ this.selectedToolIds = ToolSelectionRules.NormalizeSelection(this.SettingsManager.GetDefaultToolIds(Tools.Components.CHAT));
// Apply template's file attachments, if any:
foreach (var attachment in this.currentChatTemplate.FileAttachments)
@@ -607,6 +610,8 @@ private async Task SendMessage(bool reuseLastUserPrompt = false)
using (this.cancellationTokenSource = new())
{
this.StateHasChanged();
+ this.ChatThread!.RuntimeComponent = Tools.Components.CHAT;
+ this.ChatThread.RuntimeSelectedToolIds = ToolSelectionRules.NormalizeSelection(this.selectedToolIds);
// Use the selected provider to get the AI response.
// By awaiting this line, we wait for the entire
@@ -636,6 +641,12 @@ private async Task CancelStreaming()
if(!this.cancellationTokenSource.IsCancellationRequested)
await this.cancellationTokenSource.CancelAsync();
}
+
+ private Task SelectedToolIdsChanged(HashSet updatedToolIds)
+ {
+ this.selectedToolIds = ToolSelectionRules.NormalizeSelection(updatedToolIds);
+ return Task.CompletedTask;
+ }
private async Task SaveThread()
{
@@ -700,6 +711,7 @@ private async Task StartNewChat(bool useSameWorkspace = false, bool deletePrevio
this.isStreaming = false;
this.hasUnsavedChanges = false;
this.userInput = string.Empty;
+ this.selectedToolIds = this.SettingsManager.GetDefaultToolIds(Tools.Components.CHAT);
//
// Reset the LLM provider considering the user's settings:
@@ -995,6 +1007,10 @@ private Task EditLastBlock(IContent block)
case Event.WORKSPACE_LOADED_CHAT_CHANGED:
await this.LoadedChatChanged();
break;
+
+ case Event.CONFIGURATION_CHANGED:
+ await this.InvokeAsync(this.StateHasChanged);
+ break;
}
}
diff --git a/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor.cs b/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor.cs
index e924b4fda..8aebdc204 100644
--- a/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor.cs
+++ b/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor.cs
@@ -33,6 +33,15 @@ public partial class ConfigurationMultiSelect : ConfigurationBaseCore
///
[Parameter]
public Func IsItemLocked { get; set; } = _ => false;
+
+ [Parameter]
+ public string EmptySelectionText { get; set; } = "No items selected.";
+
+ [Parameter]
+ public string SingleSelectionText { get; set; } = "You have selected 1 item.";
+
+ [Parameter]
+ public string MultipleSelectionText { get; set; } = "You have selected {0} items.";
#region Overrides of ConfigurationBase
@@ -61,12 +70,12 @@ private async Task OptionChanged(IEnumerable? updatedValues)
private string GetMultiSelectionText(List? selectedValues)
{
if(selectedValues is null || selectedValues.Count == 0)
- return T("No preview features selected.");
+ return T(this.EmptySelectionText);
if(selectedValues.Count == 1)
- return T("You have selected 1 preview feature.");
+ return T(this.SingleSelectionText);
- return string.Format(T("You have selected {0} preview features."), selectedValues.Count);
+ return string.Format(T(this.MultipleSelectionText), selectedValues.Count);
}
private bool IsLockedValue(TData value) => this.IsItemLocked(value);
@@ -76,4 +85,4 @@ private string LockedTooltip() =>
"This feature is managed by your organization and has therefore been disabled.",
typeof(ConfigurationBase).Namespace,
nameof(ConfigurationBase));
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor
new file mode 100644
index 000000000..238b01338
--- /dev/null
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor
@@ -0,0 +1,56 @@
+@using AIStudio.Provider
+@using AIStudio.Tools.ToolCallingSystem
+@inherits SettingsPanelBase
+
+
+
+ @T("Configure global settings for each tool. Tool defaults for chat and assistants are configured in the corresponding feature settings.")
+
+
+
+
+ @T("Icon")
+ @T("Name")
+ @T("Description")
+ @T("Minimum provider confidence")
+ @T("State")
+ @T("Settings")
+
+
+
+
+
+
+ @context.Implementation.GetDisplayName()
+
+
+ @context.Implementation.GetDescription()
+
+
+
+ @foreach (var confidenceLevel in this.GetSelectableConfidenceLevels())
+ {
+
+ @this.GetConfidenceLevelName(confidenceLevel)
+
+ }
+
+
+
+ @if (context.ConfigurationState.IsConfigured)
+ {
+
+ }
+ else
+ {
+
+
+
+ }
+
+
+
+
+
+
+
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs
new file mode 100644
index 000000000..36b0e7a81
--- /dev/null
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTools.razor.cs
@@ -0,0 +1,89 @@
+using AIStudio.Provider;
+using AIStudio.Dialogs.Settings;
+using AIStudio.Settings;
+using AIStudio.Tools;
+using AIStudio.Tools.ToolCallingSystem;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Components.Settings;
+
+public partial class SettingsPanelTools : SettingsPanelBase
+{
+ [Inject]
+ private ToolRegistry ToolRegistry { get; init; } = null!;
+
+ private IReadOnlyList items = [];
+
+ protected override async Task OnInitializedAsync()
+ {
+ this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]);
+ this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
+ await base.OnInitializedAsync();
+ }
+
+ 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.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
+ this.StateHasChanged();
+ }
+
+ private string GetConfigurationTooltip(ToolCatalogItem item) => item.ConfigurationState.MissingRequiredFields.Count switch
+ {
+ _ when !string.IsNullOrWhiteSpace(item.ConfigurationState.Message) => item.ConfigurationState.Message,
+ 0 => this.T("This tool still needs to be configured."),
+ _ => string.Format(this.T("Missing required settings: {0}"), string.Join(", ", item.ConfigurationState.MissingRequiredFields.Select(fieldName => this.GetFieldDisplayName(item, fieldName))))
+ };
+
+ private string GetFieldDisplayName(ToolCatalogItem item, string fieldName)
+ {
+ var fieldDefinition = item.Definition.SettingsSchema.Properties.GetValueOrDefault(fieldName);
+ if (fieldDefinition is null)
+ return fieldName;
+
+ return item.Implementation.GetSettingsFieldLabel(fieldName, fieldDefinition);
+ }
+
+ private IEnumerable GetSelectableConfidenceLevels() =>
+ Enum.GetValues().OrderBy(x => x).Where(x => x is not ConfidenceLevel.UNKNOWN);
+
+ private string GetCurrentConfidenceLevelName(ToolCatalogItem item) => this.GetConfidenceLevelName(this.GetMinimumProviderConfidence(item));
+
+ private string GetConfidenceLevelName(ConfidenceLevel confidenceLevel) => confidenceLevel is ConfidenceLevel.NONE
+ ? this.T("No minimum confidence level chosen")
+ : confidenceLevel.GetName();
+
+ private string SetCurrentConfidenceLevelColorStyle(ToolCatalogItem item) =>
+ $"background-color: {this.GetMinimumProviderConfidence(item).GetColor(this.SettingsManager)};";
+
+ private bool IsToolConfidenceManaged() =>
+ ManagedConfiguration.TryGet(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, out var meta) && meta.IsLocked;
+
+ private ConfidenceLevel GetMinimumProviderConfidence(ToolCatalogItem item) => this.SettingsManager.GetMinimumProviderConfidenceForTool(item.Definition.Id);
+
+ private async Task ChangeMinimumProviderConfidence(ToolCatalogItem item, ConfidenceLevel confidenceLevel)
+ {
+ this.SettingsManager.SetMinimumProviderConfidenceForTool(item.Definition.Id, confidenceLevel);
+ await this.SettingsManager.StoreSettings();
+ this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
+ await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
+ }
+
+ protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
+ {
+ switch (triggeredEvent)
+ {
+ case Event.CONFIGURATION_CHANGED:
+ this.items = await this.ToolRegistry.GetCatalogAsync(this.ToolRegistry.GetAllDefinitions());
+ await this.InvokeAsync(this.StateHasChanged);
+ break;
+ }
+ }
+}
diff --git a/app/MindWork AI Studio/Components/ToolDefaultsConfiguration.razor b/app/MindWork AI Studio/Components/ToolDefaultsConfiguration.razor
new file mode 100644
index 000000000..be71c551d
--- /dev/null
+++ b/app/MindWork AI Studio/Components/ToolDefaultsConfiguration.razor
@@ -0,0 +1,12 @@
+@using AIStudio.Tools
+@using AIStudio.Tools.ToolCallingSystem
+@inherits MSGComponentBase
+
+@if (this.availableTools.Count > 0)
+{
+ @if (this.Component is not Components.CHAT && this.IncludeVisibilityToggle)
+ {
+
+ }
+
+}
diff --git a/app/MindWork AI Studio/Components/ToolDefaultsConfiguration.razor.cs b/app/MindWork AI Studio/Components/ToolDefaultsConfiguration.razor.cs
new file mode 100644
index 000000000..a63f8734d
--- /dev/null
+++ b/app/MindWork AI Studio/Components/ToolDefaultsConfiguration.razor.cs
@@ -0,0 +1,43 @@
+using AIStudio.Settings;
+using AIStudio.Tools;
+using AIStudio.Tools.ToolCallingSystem;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Components;
+
+public partial class ToolDefaultsConfiguration : MSGComponentBase
+{
+ [Parameter]
+ public AIStudio.Tools.Components Component { get; set; } = AIStudio.Tools.Components.CHAT;
+
+ [Parameter]
+ public bool IncludeVisibilityToggle { get; set; } = true;
+
+ [Inject]
+ private ToolRegistry ToolRegistry { get; init; } = null!;
+
+ private List> availableTools = [];
+
+ private string OptionTitle => this.Component is AIStudio.Tools.Components.CHAT ? this.T("Default tools for chat") : this.T("Default tools for this assistant");
+
+ private string OptionHelp => this.Component is AIStudio.Tools.Components.CHAT
+ ? this.T("Choose which tools should be preselected for new chats.")
+ : this.T("Choose which tools should be preselected for new runs of this assistant.");
+
+ private bool AreDefaultToolsDisabled =>
+ this.Component is not AIStudio.Tools.Components.CHAT &&
+ !this.SettingsManager.IsToolSelectionVisible(this.Component);
+
+ protected override async Task OnInitializedAsync()
+ {
+ this.availableTools = (await this.ToolRegistry.GetCatalogAsync(this.Component))
+ .Select(x => new ConfigurationSelectData(x.Implementation.GetDisplayName(), x.Definition.Id))
+ .ToList();
+ await base.OnInitializedAsync();
+ }
+
+ private HashSet GetSelectedValues() => this.SettingsManager.GetDefaultToolIds(this.Component);
+
+ private void UpdateSelection(HashSet 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..23613c26d
--- /dev/null
+++ b/app/MindWork AI Studio/Components/ToolSelection.razor
@@ -0,0 +1,78 @@
+@using AIStudio.Settings
+@using AIStudio.Tools.ToolCallingSystem
+@inherits MSGComponentBase
+
+