diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 8068531b..61590c55 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -1034,6 +1034,56 @@ CONFIG["CHAT_TEMPLATES"] = {} -- } -- } +-- An example chat template which preselects tools and data sources: +-- Both are optional and independent of each other. Leaving a field out is not the same as +-- leaving it empty: +-- +-- ToolIds omitted -> the chat starts with the tools set as its default +-- ToolIds = {} -> the chat starts with no tools at all +-- DataSourceOptions omitted -> the chat starts with the data source defaults +-- DataSourceOptions = { ... } -> the chat starts with exactly what this table says +-- +-- Both are a preselection, not a limit: users change either of them in the chat as usual. +-- CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = { +-- ["Id"] = "00000000-0000-0000-0000-000000000002", +-- ["Name"] = "Intranet Research", +-- ["SystemPrompt"] = "You are 's research assistant. Answer from our own documents and say where each answer comes from.", +-- ["AllowProfileUsage"] = true, +-- +-- -- Optional: the tools a chat with this template starts with, by tool ID. +-- -- A tool ID unknown to the installation is ignored, and so is a tool your +-- -- organization switched off. A tool has to meet the confidence requirements of the +-- -- provider in use, so it may stay unavailable even though this template names it. +-- -- Tool IDs include: web_search, read_web_page +-- ["ToolIds"] = { +-- "read_web_page", +-- }, +-- +-- -- Optional: the data source options a chat with this template starts with. +-- -- Every field inside is optional as well. DisableDataSources defaults to false here, +-- -- because writing this table at all says that the template wants data sources; the +-- -- other three default to false and an empty list. +-- ["DataSourceOptions"] = { +-- -- Set to true to start the chat with data sources switched off. +-- ["DisableDataSources"] = false, +-- +-- -- Let an agent choose the fitting data sources for each question. When true, +-- -- PreselectedDataSourceIds is not used. +-- ["AutomaticDataSourceSelection"] = false, +-- +-- -- Let an agent check whether the retrieved data fits the question. +-- ["AutomaticValidation"] = true, +-- +-- -- Must contain IDs from CONFIG["DATA_SOURCES"] or user-configured data sources. +-- -- IDs from another configuration of your organization work as well: they are +-- -- resolved against every known data source, not only against the ones defined +-- -- here. IDs that resolve to nothing are ignored. +-- ["PreselectedDataSourceIds"] = { +-- "00000000-0000-0000-0000-000000000000", +-- }, +-- }, +-- } + -- Introduction texts shown as expansion panels on the welcome page: CONFIG["INTRODUCTIONS"] = {} diff --git a/app/MindWork AI Studio/Settings/ChatTemplate.cs b/app/MindWork AI Studio/Settings/ChatTemplate.cs index c3d93ad9..85b6d698 100644 --- a/app/MindWork AI Studio/Settings/ChatTemplate.cs +++ b/app/MindWork AI Studio/Settings/ChatTemplate.cs @@ -1,6 +1,7 @@ using System.Text; using AIStudio.Chat; +using AIStudio.Settings.DataModel; using AIStudio.Tools.PluginSystem; using SharedTools; @@ -26,6 +27,29 @@ public record ChatTemplate( public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], [], false) { } + + /// + /// The tools this template preselects for a chat started with it. + /// + /// + /// Null means the template says nothing about tools, so the chat starts with the tools chosen + /// as its default in the app settings. An empty set is the opposite statement: this template + /// wants no tools at all, whatever that default says.

+ /// A preselection, not a limit: the user changes the selection in the chat as usual, and a + /// tool still has to meet the confidence requirements of the provider in use. + ///
+ public HashSet? ToolIds { get; init; } + + /// + /// The data source options a chat started with this template begins with. + /// + /// + /// Null means the template says nothing, so the chat starts with the data source defaults from + /// the app settings. Anything else is the template's own answer, and it carries more than a + /// list of sources: whether data sources are used at all, whether an agent picks them, and + /// whether the retrieved data is validated. + /// + public DataSourceOptions? DataSourceOptions { get; init; } private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate)); @@ -41,6 +65,8 @@ public record ChatTemplate( ExampleConversation = [], FileAttachments = [], AllowProfileUsage = true, + ToolIds = null, + DataSourceOptions = null, EnterpriseConfigurationPluginId = Guid.Empty, IsEnterpriseConfiguration = false, }; @@ -121,6 +147,8 @@ public record ChatTemplate( ExampleConversation = ParseExampleConversation(idx, table), FileAttachments = fileAttachments, AllowProfileUsage = allowProfileUsage, + ToolIds = ParseToolIds(idx, table), + DataSourceOptions = ParseDataSourceOptions(idx, table), IsEnterpriseConfiguration = true, EnterpriseConfigurationPluginId = configPluginId, }; @@ -175,6 +203,89 @@ public record ChatTemplate( return exampleConversation; } + /// + /// A missing list and an empty one mean different things here, so an empty one must not fall + /// back to null: the template then states that it wants no tools. The assistant plugins reject + /// an empty list instead, because there it carries no meaning at all. + /// + private static HashSet? ParseToolIds(int idx, LuaTable table) + { + if (!table.TryGetValue("ToolIds", out var toolIdsValue) || !toolIdsValue.TryRead(out var toolIdsTable)) + return null; + + var toolIds = new HashSet(StringComparer.Ordinal); + var numToolIds = toolIdsTable.ArrayLength; + for (var toolNum = 1; toolNum <= numToolIds; toolNum++) + { + if (!toolIdsTable[toolNum].TryRead(out var toolId) || string.IsNullOrWhiteSpace(toolId)) + { + LOGGER.LogWarning("The ToolIds entry {ToolNum} in chat template {IdxChatTemplate} is not a valid tool ID and will be ignored.", toolNum, idx); + continue; + } + + toolIds.Add(toolId.Trim()); + } + + return toolIds; + } + + private static DataSourceOptions? ParseDataSourceOptions(int idx, LuaTable table) + { + if (!table.TryGetValue("DataSourceOptions", out var optionsValue) || !optionsValue.TryRead(out var optionsTable)) + return null; + + // + // Writing this table at all is already the statement that the template wants data sources, + // hence the switch starts enabled here. Everywhere else in the app, data sources start + // switched off. + // + var disableDataSources = false; + if (optionsTable.TryGetValue("DisableDataSources", out var disableValue) && disableValue.TryRead(out var disable)) + disableDataSources = disable; + + var automaticSelection = false; + if (optionsTable.TryGetValue("AutomaticDataSourceSelection", out var automaticSelectionValue) && automaticSelectionValue.TryRead(out var automaticSelectionFlag)) + automaticSelection = automaticSelectionFlag; + + var automaticValidation = false; + if (optionsTable.TryGetValue("AutomaticValidation", out var automaticValidationValue) && automaticValidationValue.TryRead(out var automaticValidationFlag)) + automaticValidation = automaticValidationFlag; + + return new DataSourceOptions + { + DisableDataSources = disableDataSources, + AutomaticDataSourceSelection = automaticSelection, + AutomaticValidation = automaticValidation, + PreselectedDataSourceIds = ParsePreselectedDataSourceIds(idx, optionsTable), + }; + } + + /// + /// The IDs stay strings instead of being parsed as GUIDs: a data source of another + /// configuration may carry an ID which is none, and rejecting it here would make it + /// unreferenceable for no gain. + /// + private static List ParsePreselectedDataSourceIds(int idx, LuaTable optionsTable) + { + var dataSourceIds = new List(); + if (!optionsTable.TryGetValue("PreselectedDataSourceIds", out var idsValue) || !idsValue.TryRead(out var idsTable)) + return dataSourceIds; + + var numIds = idsTable.ArrayLength; + for (var idNum = 1; idNum <= numIds; idNum++) + { + if (!idsTable[idNum].TryRead(out var dataSourceId) || string.IsNullOrWhiteSpace(dataSourceId)) + { + LOGGER.LogWarning("The PreselectedDataSourceIds entry {IdNum} in chat template {IdxChatTemplate} is not a valid data source ID and will be ignored.", idNum, idx); + continue; + } + + dataSourceIds.Add(dataSourceId.Trim()); + } + + return dataSourceIds; + } + private static List ParseFileAttachments(int idx, LuaTable table, string pluginPath) { var fileAttachments = new List();