Codex-based code review

This commit is contained in:
Peer Schütt 2026-07-20 17:46:40 +02:00
parent c827ba2cd8
commit c13255fe77
14 changed files with 156 additions and 93 deletions

View File

@ -2,6 +2,7 @@ using System.Globalization;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using AIStudio.Components; using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Settings.DataModel; using AIStudio.Settings.DataModel;
using AIStudio.Tools; using AIStudio.Tools;
@ -79,6 +80,18 @@ public sealed record ChatThread
/// </summary> /// </summary>
public DataSourceSecurity DataSecurity { get; set; } = DataSourceSecurity.NOT_SPECIFIED; public DataSourceSecurity DataSecurity { get; set; } = DataSourceSecurity.NOT_SPECIFIED;
/// <summary>
/// The minimum confidence required for providers that continue this chat after a tool returned sensitive data.
/// </summary>
[JsonInclude]
public ConfidenceLevel RequiredProviderConfidence { get; private set; } = ConfidenceLevel.NONE;
public void RequireProviderConfidence(ConfidenceLevel minimumProviderConfidence)
{
if (minimumProviderConfidence > this.RequiredProviderConfidence)
this.RequiredProviderConfidence = minimumProviderConfidence;
}
/// <summary> /// <summary>
/// The name of the chat thread. Usually generated by an AI model or manually edited by the user. /// The name of the chat thread. Usually generated by an AI model or manually edited by the user.
/// </summary> /// </summary>

View File

@ -27,6 +27,17 @@ public static class ChatThreadExtensions
if (chatThread is null) if (chatThread is null)
return true; return true;
var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
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. // 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. // Means, we never used RAG or RAG was enabled, but no data sources were selected.
// That's fine as well: // That's fine as well:
@ -36,7 +47,6 @@ public static class ChatThreadExtensions
// //
// Is the provider trusted for data-source security checks? // Is the provider trusted for data-source security checks?
// //
var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
var isTrustedProvider = provider switch var isTrustedProvider = provider switch
{ {
IProvider p => p.IsTrustedForDataSourceSecurityChecks(settingsManager), IProvider p => p.IsTrustedForDataSourceSecurityChecks(settingsManager),
@ -57,4 +67,4 @@ public static class ChatThreadExtensions
false => chatThread.DataSecurity is not DataSourceSecurity.SELF_HOSTED, false => chatThread.DataSecurity is not DataSourceSecurity.SELF_HOSTED,
}; };
} }
} }

View File

@ -22,7 +22,7 @@
} }
<SettingsPanelApp AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/> <SettingsPanelApp AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
<SettingsPanelTools /> <SettingsPanelTools />
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))

View File

@ -1083,48 +1083,54 @@ public abstract class BaseProvider : IProvider, ISecretId
yield break; yield break;
} }
await ShowToolRuntimeStatusAsync(toolCalls try
.Select(x => runnableTools.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(x.Function.Name, StringComparison.Ordinal)).Implementation?.GetDisplayName() ?? x.Function.Name));
internalMessages.Add(new AssistantToolCallMessage
{ {
Content = responseMessage.Content, await ShowToolRuntimeStatusAsync(toolCalls
ToolCalls = 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) internalMessages.Add(new AssistantToolCallMessage
{
if (toolCallCount >= ToolSelectionRules.MAX_TOOL_CALLS)
{ {
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 internalMessages.Add(new ToolResultMessage
{ {
Content = finalResponseInstruction, Content = toolContent,
ToolCallId = toolCall.Id, 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,
});
} }
finally
if (currentAssistantContent is not null) {
await currentAssistantContent.StreamingEvent(); await ResetToolRuntimeStatusAsync();
}
} }
} }

View File

@ -227,8 +227,10 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
{ {
await foreach (var content in this.StreamResponsesWithLocalTools( await foreach (var content in this.StreamResponsesWithLocalTools(
chatModel, chatModel,
chatThread,
baseInput, baseInput,
apiParameters, apiParameters,
providerTools,
runnableTools, runnableTools,
toolExecutor, toolExecutor,
currentAssistantContent, currentAssistantContent,
@ -308,8 +310,10 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
private async IAsyncEnumerable<ContentStreamChunk> StreamResponsesWithLocalTools( private async IAsyncEnumerable<ContentStreamChunk> StreamResponsesWithLocalTools(
Model chatModel, Model chatModel,
ChatThread chatThread,
IList<object> baseInput, IList<object> baseInput,
IDictionary<string, object> apiParameters, IDictionary<string, object> apiParameters,
IList<object> providerTools,
IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools, IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools,
ToolExecutor toolExecutor, ToolExecutor toolExecutor,
ContentText? currentAssistantContent, ContentText? currentAssistantContent,
@ -317,9 +321,16 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
ConfidenceLevel providerConfidence, ConfidenceLevel providerConfidence,
[EnumeratorCancellation] CancellationToken token) [EnumeratorCancellation] CancellationToken token)
{ {
var providerTools = runnableTools var localProviderTools = runnableTools
.Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition)) .Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition))
.ToList(); .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 // Preserve every output item required to continue the response, including
// reasoning items emitted alongside function calls. // reasoning items emitted alongside function calls.
var internalItems = new List<object>(); var internalItems = new List<object>();
@ -344,7 +355,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
Input = requestInput, Input = requestInput,
Stream = false, Stream = false,
Store = false, Store = false,
Tools = finalResponseRequired ? [] : providerTools, Tools = finalResponseRequired ? [] : effectiveProviderTools,
AdditionalApiParameters = apiParameters, AdditionalApiParameters = apiParameters,
}; };
var response = await this.ExecuteResponsesRequest(requestDto, requestedSecret, token); var response = await this.ExecuteResponsesRequest(requestDto, requestedSecret, token);
@ -381,45 +392,51 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
yield break; yield break;
} }
await ShowToolRuntimeStatusAsync(currentAssistantContent, functionCalls try
.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)
{ {
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 internalItems.Add(new ResponsesFunctionCallOutputItem
{ {
CallId = functionCall.CallId, 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,
});
} }
finally
if (currentAssistantContent is not null) {
await currentAssistantContent.StreamingEvent(); await ResetToolRuntimeStatusAsync(currentAssistantContent);
}
} }
} }

View File

@ -769,7 +769,7 @@ public static partial class ManagedConfiguration
var successful = false; var successful = false;
var configuredValue = CloneStringDictionary(configMeta.Default); var configuredValue = CloneStringDictionary(configMeta.Default);
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: // 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) && if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table && configuredLuaList.Type is LuaValueType.Table &&

View File

@ -136,7 +136,8 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
return new ToolExecutionResult 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(); var warningArray = new JsonArray();
foreach (var warning in warnings) foreach (var warning in warnings)
warningArray.Add(warning); warningArray.Add(warning);
AddIfNotEmpty(metadata, "language", extractedPage.Language); AddIfNotEmpty(metadata, "language", extractedPage.Language);
AddIfNotEmpty(metadata, "published_time", extractedPage.PublishedTime); AddIfNotEmpty(metadata, "published_time", extractedPage.PublishedTime);
AddIfNotEmpty(metadata, "modified_time", extractedPage.ModifiedTime); AddIfNotEmpty(metadata, "modified_time", extractedPage.ModifiedTime);
@ -172,16 +173,16 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
metadata["original_content_characters"] = originalContentCharacters; metadata["original_content_characters"] = originalContentCharacters;
metadata["returned_content_characters"] = websiteContentAsMarkdown.Length; metadata["returned_content_characters"] = websiteContentAsMarkdown.Length;
} }
var content = new JsonObject var content = new JsonObject
{ {
["text_content"] = websiteContentAsMarkdown, ["text_content"] = websiteContentAsMarkdown,
}; };
AddIfNotEmpty(content, "title", extractedPage.Title); AddIfNotEmpty(content, "title", extractedPage.Title);
AddIfNotEmpty(content, "description", extractedPage.Description); AddIfNotEmpty(content, "description", extractedPage.Description);
AddStringArrayIfNotEmpty(content, "authors", extractedPage.Authors); AddStringArrayIfNotEmpty(content, "authors", extractedPage.Authors);
var result = new JsonObject var result = new JsonObject
{ {
@ -191,7 +192,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
["content"] = content, ["content"] = content,
["metadata"] = metadata, ["metadata"] = metadata,
}; };
return result; return result;
} }

View File

@ -24,6 +24,8 @@ public sealed class ToolExecutionResult
public JsonNode? JsonContent { get; init; } public JsonNode? JsonContent { get; init; }
public ConfidenceLevel RequiredProviderConfidence { get; init; } = ConfidenceLevel.NONE;
public string ToModelContent() public string ToModelContent()
{ {
if (this.JsonContent is not null) if (this.JsonContent is not null)

View File

@ -9,7 +9,7 @@ namespace AIStudio.Tools.ToolCallingSystem;
public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogger<ToolExecutor> logger) public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogger<ToolExecutor> logger)
{ {
public async Task<(string Content, ToolInvocationTrace Trace)> ExecuteAsync( public async Task<(string Content, ToolInvocationTrace Trace, ConfidenceLevel RequiredProviderConfidence)> ExecuteAsync(
string toolCallId, string toolCallId,
string toolName, string toolName,
string argumentsJson, string argumentsJson,
@ -49,7 +49,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
StatusMessage = "Tool is not available in the current context.", StatusMessage = "Tool is not available in the current context.",
Arguments = formattedArguments, Arguments = formattedArguments,
Result = error, Result = error,
}); }, ConfidenceLevel.NONE);
} }
var definition = runnableTool.Definition; var definition = runnableTool.Definition;
@ -66,7 +66,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
ProviderConfidence = providerConfidence, ProviderConfidence = providerConfidence,
}, token); }, token);
logger.LogInformation("Completed tool execution. ToolName={ToolName}, ToolCallId={ToolCallId}, DurationMs={DurationMs}, Status={Status}", toolName, toolCallId, stopwatch.ElapsedMilliseconds, ToolInvocationTraceStatus.SUCCESS); logger.LogInformation("Completed tool execution. ToolName={ToolName}, ToolCallId={ToolCallId}, DurationMs={DurationMs}, Status={Status}", toolName, toolCallId, stopwatch.ElapsedMilliseconds, ToolInvocationTraceStatus.SUCCESS);
var resultModelContent = result.ToModelContent(); var resultModelContent = result.ToModelContent();
var toolInvocationTrace = new ToolInvocationTrace var toolInvocationTrace = new ToolInvocationTrace
{ {
@ -82,8 +82,8 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
Result = Result =
implementation.FormatTraceResult(result.ToModelContent()), implementation.FormatTraceResult(result.ToModelContent()),
}; };
return (resultModelContent, toolInvocationTrace); return (resultModelContent, toolInvocationTrace, result.RequiredProviderConfidence);
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
@ -104,9 +104,9 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
StatusMessage = exception.Message, StatusMessage = exception.Message,
Arguments = formattedArguments, Arguments = formattedArguments,
Result = exception.Message, Result = exception.Message,
}; };
return (exception.Message, toolInvocationTrace); return (exception.Message, toolInvocationTrace, ConfidenceLevel.NONE);
} }
catch (Exception exception) catch (Exception exception)
{ {
@ -125,8 +125,8 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
Arguments = formattedArguments, Arguments = formattedArguments,
Result = error, Result = error,
}; };
return (error, toolInvocationTrace); return (error, toolInvocationTrace, ConfidenceLevel.NONE);
} }
} }

View File

@ -37,7 +37,7 @@ public static class ToolSelectionRules
var toolPolicyPrompt = $""" var toolPolicyPrompt = $"""
# Tool usage instructions: # 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: 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)} {string.Join(Environment.NewLine+Environment.NewLine, policySections)}
"""; """;

View File

@ -14,6 +14,7 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser)
CancellationToken token = default) CancellationToken token = default)
{ {
var triedOsSso = false; var triedOsSso = false;
var requiredProviderConfidence = ConfidenceLevel.NONE;
HTMLParserWebPage page; HTMLParserWebPage page;
try try
{ {
@ -21,7 +22,14 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser)
url, url,
token, token,
options.TimeoutSeconds, 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, MAX_RESPONSE_BYTES,
options.UseOsSso ? ExternalWebAuthenticationMode.OS_DEFAULT_CREDENTIALS : ExternalWebAuthenticationMode.NONE, options.UseOsSso ? ExternalWebAuthenticationMode.OS_DEFAULT_CREDENTIALS : ExternalWebAuthenticationMode.NONE,
shouldUseDefaultCredentials: (candidateUrl, addresses) => shouldUseDefaultCredentials: (candidateUrl, addresses) =>
@ -58,6 +66,7 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser)
Page = page, Page = page,
ExtractedPage = WebPageContentExtractor.Extract(htmlParser, page.Document, page.FinalUrl), ExtractedPage = WebPageContentExtractor.Extract(htmlParser, page.Document, page.FinalUrl),
RetrievedAtUtc = DateTimeOffset.UtcNow, RetrievedAtUtc = DateTimeOffset.UtcNow,
RequiredProviderConfidence = requiredProviderConfidence,
}; };
} }
@ -245,4 +254,6 @@ public sealed class RetrievedWebPage
public required ExtractedWebPage ExtractedPage { get; init; } public required ExtractedWebPage ExtractedPage { get; init; }
public required DateTimeOffset RetrievedAtUtc { get; init; } public required DateTimeOffset RetrievedAtUtc { get; init; }
public ConfidenceLevel RequiredProviderConfidence { get; init; } = ConfidenceLevel.NONE;
} }

View File

@ -41,9 +41,9 @@
}, },
"Microsoft.NET.ILLink.Tasks": { "Microsoft.NET.ILLink.Tasks": {
"type": "Direct", "type": "Direct",
"requested": "[9.0.12, )", "requested": "[9.0.18, )",
"resolved": "9.0.12", "resolved": "9.0.18",
"contentHash": "StA3kyImQHqDo8A8ZHaSxgASbEuT5UIqgeCvK5SzUPj//xE1QSys421J9pEs4cYuIVwq7CJvWSKxtyH7aPr1LA==" "contentHash": "ztGVXB28bi8SeplFmAx+4MkqP1ieA4UNzj/M3qyyz5tLa37Ln8x8LuaXdxzzoOdaucjQBKXSdCMFSbpQaNGIEg=="
}, },
"MudBlazor": { "MudBlazor": {
"type": "Direct", "type": "Direct",
@ -212,6 +212,6 @@
"type": "Project" "type": "Project"
} }
}, },
"net9.0/win-x64": {} "net9.0/osx-arm64": {}
} }
} }

View File

@ -1,3 +1,4 @@
# v26.7.4, build 249 (2026-07-xx xx:xx UTC) # 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. - Added organization-wide management for tool availability and all Web Search and Read Web Page settings.

View File

@ -78,7 +78,7 @@ Example:
"demoLabel" "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": { "function": {
"name": "get_current_weather", "name": "get_current_weather",
"descriptionForLLM": "Get the current weather in a given location.", "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. 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 ## 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. 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 ## Security
Treat model-provided tool arguments as untrusted input. Treat model-provided tool arguments as untrusted input.