From c13255fe777fd8233e1877e2ecba34cc1c243d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= <20603780+peerschuett@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:46:40 +0200 Subject: [PATCH] Codex-based code review --- app/MindWork AI Studio/Chat/ChatThread.cs | 13 +++ .../Chat/ChatThreadExtensions.cs | 14 +++- app/MindWork AI Studio/Pages/Settings.razor | 2 +- .../Provider/BaseProvider.cs | 70 ++++++++-------- .../Provider/OpenAI/ProviderOpenAI.cs | 81 +++++++++++-------- .../Settings/ManagedConfiguration.Parsing.cs | 2 +- .../ReadWebPageTool.cs | 13 +-- .../ToolCallingSystem/ToolExecutionModels.cs | 2 + .../Tools/ToolCallingSystem/ToolExecutor.cs | 20 ++--- .../ToolCallingSystem/ToolSelectionRules.cs | 2 +- .../Tools/Web/WebPageRetrievalService.cs | 13 ++- app/MindWork AI Studio/packages.lock.json | 10 +-- .../wwwroot/changelog/v26.7.4.md | 1 + documentation/Tools.md | 6 +- 14 files changed, 156 insertions(+), 93 deletions(-) diff --git a/app/MindWork AI Studio/Chat/ChatThread.cs b/app/MindWork AI Studio/Chat/ChatThread.cs index 7c0dd754..560e19c8 100644 --- a/app/MindWork AI Studio/Chat/ChatThread.cs +++ b/app/MindWork AI Studio/Chat/ChatThread.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Text.Json.Serialization; using AIStudio.Components; +using AIStudio.Provider; using AIStudio.Settings; using AIStudio.Settings.DataModel; using AIStudio.Tools; @@ -79,6 +80,18 @@ public sealed record ChatThread /// public DataSourceSecurity DataSecurity { get; set; } = DataSourceSecurity.NOT_SPECIFIED; + /// + /// The minimum confidence required for providers that continue this chat after a tool returned sensitive data. + /// + [JsonInclude] + public ConfidenceLevel RequiredProviderConfidence { get; private set; } = ConfidenceLevel.NONE; + + public void RequireProviderConfidence(ConfidenceLevel minimumProviderConfidence) + { + if (minimumProviderConfidence > this.RequiredProviderConfidence) + this.RequiredProviderConfidence = minimumProviderConfidence; + } + /// /// The name of the chat thread. Usually generated by an AI model or manually edited by the user. /// diff --git a/app/MindWork AI Studio/Chat/ChatThreadExtensions.cs b/app/MindWork AI Studio/Chat/ChatThreadExtensions.cs index 2eb5395b..256a2689 100644 --- a/app/MindWork AI Studio/Chat/ChatThreadExtensions.cs +++ b/app/MindWork AI Studio/Chat/ChatThreadExtensions.cs @@ -27,6 +27,17 @@ public static class ChatThreadExtensions if (chatThread is null) return true; + var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService(); + var providerConfidence = provider switch + { + IProvider p => p.Provider.GetConfidence(settingsManager).Level, + AIStudio.Settings.Provider p => p.UsedLLMProvider.GetConfidence(settingsManager).Level, + + _ => ConfidenceLevel.UNKNOWN, + }; + if (providerConfidence < chatThread.RequiredProviderConfidence) + return false; + // The chat thread is available, but the data security is not specified. // Means, we never used RAG or RAG was enabled, but no data sources were selected. // That's fine as well: @@ -36,7 +47,6 @@ public static class ChatThreadExtensions // // Is the provider trusted for data-source security checks? // - var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService(); var isTrustedProvider = provider switch { IProvider p => p.IsTrustedForDataSourceSecurityChecks(settingsManager), @@ -57,4 +67,4 @@ public static class ChatThreadExtensions false => chatThread.DataSecurity is not DataSourceSecurity.SELF_HOSTED, }; } -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor index fad7234f..7718ba9b 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor +++ b/app/MindWork AI Studio/Pages/Settings.razor @@ -22,7 +22,7 @@ } - + @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 7ed742e4..56bb80b2 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -1083,48 +1083,54 @@ public abstract class BaseProvider : IProvider, ISecretId yield break; } - await ShowToolRuntimeStatusAsync(toolCalls - .Select(x => runnableTools.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(x.Function.Name, StringComparison.Ordinal)).Implementation?.GetDisplayName() ?? x.Function.Name)); - - internalMessages.Add(new AssistantToolCallMessage + try { - Content = responseMessage.Content, - ToolCalls = toolCalls, - }); + await ShowToolRuntimeStatusAsync(toolCalls + .Select(x => runnableTools.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(x.Function.Name, StringComparison.Ordinal)).Implementation?.GetDisplayName() ?? x.Function.Name)); - foreach (var toolCall in toolCalls) - { - if (toolCallCount >= ToolSelectionRules.MAX_TOOL_CALLS) + internalMessages.Add(new AssistantToolCallMessage { - var finalResponseInstruction = ToolSelectionRules.GetMaxToolCallsFinalResponseInstruction(); + Content = responseMessage.Content, + ToolCalls = toolCalls, + }); + + foreach (var toolCall in toolCalls) + { + if (toolCallCount >= ToolSelectionRules.MAX_TOOL_CALLS) + { + var finalResponseInstruction = ToolSelectionRules.GetMaxToolCallsFinalResponseInstruction(); + internalMessages.Add(new ToolResultMessage + { + Content = finalResponseInstruction, + ToolCallId = toolCall.Id, + }); + continue; + } + + toolCallCount++; + var (toolContent, trace, requiredProviderConfidence) = await toolExecutor.ExecuteAsync( + toolCall.Id, + toolCall.Function.Name, + toolCall.Function.Arguments, + runnableTools, + this.Provider.GetConfidence(settingsManager).Level, + toolCallCount, + token); + + chatThread.RequireProviderConfidence(requiredProviderConfidence); + currentAssistantContent?.ToolInvocations.Add(trace); internalMessages.Add(new ToolResultMessage { - Content = finalResponseInstruction, + Content = toolContent, ToolCallId = toolCall.Id, }); - continue; } - toolCallCount++; - var (toolContent, trace) = await toolExecutor.ExecuteAsync( - toolCall.Id, - toolCall.Function.Name, - toolCall.Function.Arguments, - runnableTools, - this.Provider.GetConfidence(settingsManager).Level, - toolCallCount, - token); - - currentAssistantContent?.ToolInvocations.Add(trace); - internalMessages.Add(new ToolResultMessage - { - Content = toolContent, - ToolCallId = toolCall.Id, - }); } - - if (currentAssistantContent is not null) - await currentAssistantContent.StreamingEvent(); + finally + { + await ResetToolRuntimeStatusAsync(); + } } } diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index 792e65bb..8dd3b24f 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -227,8 +227,10 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur { await foreach (var content in this.StreamResponsesWithLocalTools( chatModel, + chatThread, baseInput, apiParameters, + providerTools, runnableTools, toolExecutor, currentAssistantContent, @@ -308,8 +310,10 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur private async IAsyncEnumerable StreamResponsesWithLocalTools( Model chatModel, + ChatThread chatThread, IList baseInput, IDictionary apiParameters, + IList providerTools, IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools, ToolExecutor toolExecutor, ContentText? currentAssistantContent, @@ -317,9 +321,16 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur ConfidenceLevel providerConfidence, [EnumeratorCancellation] CancellationToken token) { - var providerTools = runnableTools + var localProviderTools = runnableTools .Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition)) .ToList(); + var localFunctionNames = runnableTools + .Select(x => x.Definition.Function.Name) + .ToHashSet(StringComparer.Ordinal); + var effectiveProviderTools = providerTools + .Where(x => x is not ProviderTool providerTool || !localFunctionNames.Contains(providerTool.Type)) + .Concat(localProviderTools) + .ToList(); // Preserve every output item required to continue the response, including // reasoning items emitted alongside function calls. var internalItems = new List(); @@ -344,7 +355,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur Input = requestInput, Stream = false, Store = false, - Tools = finalResponseRequired ? [] : providerTools, + Tools = finalResponseRequired ? [] : effectiveProviderTools, AdditionalApiParameters = apiParameters, }; var response = await this.ExecuteResponsesRequest(requestDto, requestedSecret, token); @@ -381,45 +392,51 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur yield break; } - await ShowToolRuntimeStatusAsync(currentAssistantContent, functionCalls - .Select(x => runnableTools.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(x.Name, StringComparison.Ordinal)).Implementation?.GetDisplayName() ?? x.Name)); - - foreach (var outputItem in response.Output) - internalItems.Add(outputItem); - - foreach (var functionCall in functionCalls) + try { - if (toolCallCount >= ToolSelectionRules.MAX_TOOL_CALLS) + await ShowToolRuntimeStatusAsync(currentAssistantContent, functionCalls + .Select(x => runnableTools.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(x.Name, StringComparison.Ordinal)).Implementation?.GetDisplayName() ?? x.Name)); + + foreach (var outputItem in response.Output) + internalItems.Add(outputItem); + + foreach (var functionCall in functionCalls) { - var finalResponseInstruction = ToolSelectionRules.GetMaxToolCallsFinalResponseInstruction(); + if (toolCallCount >= ToolSelectionRules.MAX_TOOL_CALLS) + { + var finalResponseInstruction = ToolSelectionRules.GetMaxToolCallsFinalResponseInstruction(); + internalItems.Add(new ResponsesFunctionCallOutputItem + { + CallId = functionCall.CallId, + Output = finalResponseInstruction, + }); + continue; + } + + toolCallCount++; + var (toolContent, trace, requiredProviderConfidence) = await toolExecutor.ExecuteAsync( + functionCall.CallId, + functionCall.Name, + functionCall.Arguments, + runnableTools, + providerConfidence, + toolCallCount, + token); + + chatThread.RequireProviderConfidence(requiredProviderConfidence); + currentAssistantContent?.ToolInvocations.Add(trace); internalItems.Add(new ResponsesFunctionCallOutputItem { CallId = functionCall.CallId, - Output = finalResponseInstruction, + Output = toolContent, }); - continue; } - toolCallCount++; - var (toolContent, trace) = await toolExecutor.ExecuteAsync( - functionCall.CallId, - functionCall.Name, - functionCall.Arguments, - runnableTools, - providerConfidence, - toolCallCount, - token); - - currentAssistantContent?.ToolInvocations.Add(trace); - internalItems.Add(new ResponsesFunctionCallOutputItem - { - CallId = functionCall.CallId, - Output = toolContent, - }); } - - if (currentAssistantContent is not null) - await currentAssistantContent.StreamingEvent(); + finally + { + await ResetToolRuntimeStatusAsync(currentAssistantContent); + } } } diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs index 70e79cf9..8aaaf560 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs @@ -769,7 +769,7 @@ public static partial class ManagedConfiguration var successful = false; var configuredValue = CloneStringDictionary(configMeta.Default); - + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && configuredLuaList.Type is LuaValueType.Table && diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs index 3ca38de2..e483f941 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/ReadWebPageTool.cs @@ -136,7 +136,8 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ return new ToolExecutionResult { - JsonContent = BuildModelContent(page, extractedPage, retrievedPage.RetrievedAtUtc, markdown, originalContentCharacters, contentTruncated, warnings) + JsonContent = BuildModelContent(page, extractedPage, retrievedPage.RetrievedAtUtc, markdown, originalContentCharacters, contentTruncated, warnings), + RequiredProviderConfidence = retrievedPage.RequiredProviderConfidence, }; } @@ -161,7 +162,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ var warningArray = new JsonArray(); foreach (var warning in warnings) warningArray.Add(warning); - + AddIfNotEmpty(metadata, "language", extractedPage.Language); AddIfNotEmpty(metadata, "published_time", extractedPage.PublishedTime); AddIfNotEmpty(metadata, "modified_time", extractedPage.ModifiedTime); @@ -172,16 +173,16 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ metadata["original_content_characters"] = originalContentCharacters; metadata["returned_content_characters"] = websiteContentAsMarkdown.Length; } - + var content = new JsonObject { ["text_content"] = websiteContentAsMarkdown, }; - + AddIfNotEmpty(content, "title", extractedPage.Title); AddIfNotEmpty(content, "description", extractedPage.Description); AddStringArrayIfNotEmpty(content, "authors", extractedPage.Authors); - + var result = new JsonObject { @@ -191,7 +192,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ ["content"] = content, ["metadata"] = metadata, }; - + return result; } diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs index 376fcba9..88faa389 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutionModels.cs @@ -24,6 +24,8 @@ public sealed class ToolExecutionResult public JsonNode? JsonContent { get; init; } + public ConfidenceLevel RequiredProviderConfidence { get; init; } = ConfidenceLevel.NONE; + public string ToModelContent() { if (this.JsonContent is not null) diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs index f31bb1d3..f85c9be9 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolExecutor.cs @@ -9,7 +9,7 @@ namespace AIStudio.Tools.ToolCallingSystem; public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogger logger) { - public async Task<(string Content, ToolInvocationTrace Trace)> ExecuteAsync( + public async Task<(string Content, ToolInvocationTrace Trace, ConfidenceLevel RequiredProviderConfidence)> ExecuteAsync( string toolCallId, string toolName, string argumentsJson, @@ -49,7 +49,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge StatusMessage = "Tool is not available in the current context.", Arguments = formattedArguments, Result = error, - }); + }, ConfidenceLevel.NONE); } var definition = runnableTool.Definition; @@ -66,7 +66,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge ProviderConfidence = providerConfidence, }, token); logger.LogInformation("Completed tool execution. ToolName={ToolName}, ToolCallId={ToolCallId}, DurationMs={DurationMs}, Status={Status}", toolName, toolCallId, stopwatch.ElapsedMilliseconds, ToolInvocationTraceStatus.SUCCESS); - + var resultModelContent = result.ToModelContent(); var toolInvocationTrace = new ToolInvocationTrace { @@ -82,8 +82,8 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge Result = implementation.FormatTraceResult(result.ToModelContent()), }; - - return (resultModelContent, toolInvocationTrace); + + return (resultModelContent, toolInvocationTrace, result.RequiredProviderConfidence); } catch (OperationCanceledException) when (token.IsCancellationRequested) { @@ -104,9 +104,9 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge StatusMessage = exception.Message, Arguments = formattedArguments, Result = exception.Message, - }; - - return (exception.Message, toolInvocationTrace); + }; + + return (exception.Message, toolInvocationTrace, ConfidenceLevel.NONE); } catch (Exception exception) { @@ -125,8 +125,8 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge Arguments = formattedArguments, Result = error, }; - - return (error, toolInvocationTrace); + + return (error, toolInvocationTrace, ConfidenceLevel.NONE); } } diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs index 63107340..e01a8c61 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSelectionRules.cs @@ -37,7 +37,7 @@ public static class ToolSelectionRules var toolPolicyPrompt = $""" # Tool usage instructions: You have multiple tools available. Each tool has a different purpose and usage policy. Choose wisely and if you are not sure, always ask the user for clarification. You must follow the usage policy of each tool to ensure accurate and reliable results. Here are the usage policies for each tool: - + {string.Join(Environment.NewLine+Environment.NewLine, policySections)} """; diff --git a/app/MindWork AI Studio/Tools/Web/WebPageRetrievalService.cs b/app/MindWork AI Studio/Tools/Web/WebPageRetrievalService.cs index 976eb47f..7582914b 100644 --- a/app/MindWork AI Studio/Tools/Web/WebPageRetrievalService.cs +++ b/app/MindWork AI Studio/Tools/Web/WebPageRetrievalService.cs @@ -14,6 +14,7 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser) CancellationToken token = default) { var triedOsSso = false; + var requiredProviderConfidence = ConfidenceLevel.NONE; HTMLParserWebPage page; try { @@ -21,7 +22,14 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser) url, token, options.TimeoutSeconds, - async (candidateUrl, validationToken) => await ResolveValidatedUrlAddressesAsync(candidateUrl, options, validationToken), + async (candidateUrl, validationToken) => + { + var addresses = await ResolveValidatedUrlAddressesAsync(candidateUrl, options, validationToken); + if (addresses.Any(IsNonPublicAddress)) + requiredProviderConfidence = ConfidenceLevel.HIGH; + + return addresses; + }, MAX_RESPONSE_BYTES, options.UseOsSso ? ExternalWebAuthenticationMode.OS_DEFAULT_CREDENTIALS : ExternalWebAuthenticationMode.NONE, shouldUseDefaultCredentials: (candidateUrl, addresses) => @@ -58,6 +66,7 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser) Page = page, ExtractedPage = WebPageContentExtractor.Extract(htmlParser, page.Document, page.FinalUrl), RetrievedAtUtc = DateTimeOffset.UtcNow, + RequiredProviderConfidence = requiredProviderConfidence, }; } @@ -245,4 +254,6 @@ public sealed class RetrievedWebPage public required ExtractedWebPage ExtractedPage { get; init; } public required DateTimeOffset RetrievedAtUtc { get; init; } + + public ConfidenceLevel RequiredProviderConfidence { get; init; } = ConfidenceLevel.NONE; } diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index bb9b4ed0..e2b0f829 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -41,9 +41,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[9.0.12, )", - "resolved": "9.0.12", - "contentHash": "StA3kyImQHqDo8A8ZHaSxgASbEuT5UIqgeCvK5SzUPj//xE1QSys421J9pEs4cYuIVwq7CJvWSKxtyH7aPr1LA==" + "requested": "[9.0.18, )", + "resolved": "9.0.18", + "contentHash": "ztGVXB28bi8SeplFmAx+4MkqP1ieA4UNzj/M3qyyz5tLa37Ln8x8LuaXdxzzoOdaucjQBKXSdCMFSbpQaNGIEg==" }, "MudBlazor": { "type": "Direct", @@ -212,6 +212,6 @@ "type": "Project" } }, - "net9.0/win-x64": {} + "net9.0/osx-arm64": {} } -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.7.4.md b/app/MindWork AI Studio/wwwroot/changelog/v26.7.4.md index 7fc91688..31ea5668 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.7.4.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.7.4.md @@ -1,3 +1,4 @@ # v26.7.4, build 249 (2026-07-xx xx:xx UTC) +- Added model-driven Web Search and Read Web Page tools for supported AI models. - Added organization-wide management for tool availability and all Web Search and Read Web Page settings. diff --git a/documentation/Tools.md b/documentation/Tools.md index 285e29cc..d3975ad9 100644 --- a/documentation/Tools.md +++ b/documentation/Tools.md @@ -78,7 +78,7 @@ Example: "demoLabel" ] }, - "policyInstructions": "Use this tool only when the user asks for current weather conditions.", + "systemPromptInstructions": "Use this tool only when the user asks for current weather conditions.", "function": { "name": "get_current_weather", "descriptionForLLM": "Get the current weather in a given location.", @@ -116,7 +116,7 @@ Example: Use stable lower-case IDs with underscores. Keep `id`, `implementationKey`, and `function.name` identical unless there is a clear compatibility reason not to. -Keep `function.descriptionForLLM` focused on what the tool does. This value is mapped to the provider's function `description` field and is only shown to the LLM. Put sequencing rules, answer-format guidance, or other behavior instructions in `policyInstructions`. When runnable tools are selected, their non-empty policy text is combined centrally and appended to the effective system prompt. +Keep `function.descriptionForLLM` focused on what the tool does. This value is mapped to the provider's function `description` field and is only shown to the LLM. Put sequencing rules, answer-format guidance, or other behavior instructions in `systemPromptInstructions`. When runnable tools are selected, their non-empty policy text is combined centrally and appended to the effective system prompt. ## Implementation @@ -189,6 +189,8 @@ Use `ValidateConfigurationAsync` when a setting needs more than "required field Use `SensitiveTraceArgumentNames` for model-provided arguments that must not be shown in tool traces. Do not return secrets in `TextContent`, `JsonContent`, exception messages, logs, or trace formatting. +When a tool returns data that future messages must only send to providers at or above a specific confidence level, set `ToolExecutionResult.RequiredProviderConfidence`. AI Studio persists the highest requirement reached by the chat and applies it to later provider checks. + ## Security Treat model-provided tool arguments as untrusted input.