mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 18:32:12 +00:00
Codex-based code review
This commit is contained in:
parent
c827ba2cd8
commit
c13255fe77
@ -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
|
||||
/// </summary>
|
||||
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>
|
||||
/// The name of the chat thread. Usually generated by an AI model or manually edited by the user.
|
||||
/// </summary>
|
||||
|
||||
@ -27,6 +27,17 @@ public static class ChatThreadExtensions
|
||||
if (chatThread is null)
|
||||
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.
|
||||
// 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<SettingsManager>();
|
||||
var isTrustedProvider = provider switch
|
||||
{
|
||||
IProvider p => p.IsTrustedForDataSourceSecurityChecks(settingsManager),
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<ContentStreamChunk> StreamResponsesWithLocalTools(
|
||||
Model chatModel,
|
||||
ChatThread chatThread,
|
||||
IList<object> baseInput,
|
||||
IDictionary<string, object> apiParameters,
|
||||
IList<object> 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<object>();
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -9,7 +9,7 @@ namespace AIStudio.Tools.ToolCallingSystem;
|
||||
|
||||
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 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;
|
||||
@ -83,7 +83,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
|
||||
implementation.FormatTraceResult(result.ToModelContent()),
|
||||
};
|
||||
|
||||
return (resultModelContent, toolInvocationTrace);
|
||||
return (resultModelContent, toolInvocationTrace, result.RequiredProviderConfidence);
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
@ -106,7 +106,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
|
||||
Result = exception.Message,
|
||||
};
|
||||
|
||||
return (exception.Message, toolInvocationTrace);
|
||||
return (exception.Message, toolInvocationTrace, ConfidenceLevel.NONE);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@ -126,7 +126,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
|
||||
Result = error,
|
||||
};
|
||||
|
||||
return (error, toolInvocationTrace);
|
||||
return (error, toolInvocationTrace, ConfidenceLevel.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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": {}
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user