mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 19:32:10 +00:00
Set parallel_tool_calls to false in chat completions, because AI Studio calls all tools sequentially at the moment.
Also fixed a bug that resulted in an error when using tools with Mistral API
This commit is contained in:
parent
7a9ab85113
commit
ead156ac4d
@ -9241,12 +9241,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTE
|
||||
-- Tool calling for this provider is not implemented yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGAVAILABILITYEXTENSIONS::T3776963202"] = "Tool calling for this provider is not implemented yet."
|
||||
|
||||
-- (Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T1410249500"] = "(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."
|
||||
|
||||
-- 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."
|
||||
|
||||
-- The web page was not loaded because private or VPN web pages require a High-confidence provider or a provider trusted by your organization's configuration.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2563437007"] = "The web page was not loaded because private or VPN web pages require a High-confidence provider or a provider trusted by your organization's configuration."
|
||||
|
||||
-- Maximum Content Characters
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters"
|
||||
|
||||
@ -9265,15 +9265,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS:
|
||||
-- Load a web page and extract its readable content, links, and page details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3715690061"] = "Load a web page and extract its readable content, links, and page details."
|
||||
|
||||
-- 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."
|
||||
|
||||
-- (Optional) HTTP timeout for loading a web page in seconds.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T4126164830"] = "(Optional) HTTP timeout for loading a web page in seconds."
|
||||
|
||||
-- The setting '{0}' must be a positive integer.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T4199432074"] = "The setting '{0}' must be a positive integer."
|
||||
|
||||
-- (Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider or a provider trusted by your organization's configuration. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T64466871"] = "(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider or a provider trusted by your organization's configuration. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."
|
||||
|
||||
-- (Optional) Global truncation limit for extracted characters returned to the model.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T900659180"] = "(Optional) Global truncation limit for extracted characters returned to the model."
|
||||
|
||||
|
||||
@ -35,7 +35,14 @@ public static class ChatThreadExtensions
|
||||
|
||||
_ => ConfidenceLevel.UNKNOWN,
|
||||
};
|
||||
if (providerConfidence < chatThread.RequiredProviderConfidence)
|
||||
var isTrustedByConfiguration = provider switch
|
||||
{
|
||||
IProvider p => p.IsTrustedByConfiguration(settingsManager),
|
||||
AIStudio.Settings.Provider p => p.IsTrustedByConfiguration(settingsManager),
|
||||
|
||||
_ => false,
|
||||
};
|
||||
if (providerConfidence < chatThread.RequiredProviderConfidence && !isTrustedByConfiguration)
|
||||
return false;
|
||||
|
||||
// The chat thread is available, but the data security is not specified.
|
||||
|
||||
@ -368,10 +368,10 @@ CONFIG["SETTINGS"] = {}
|
||||
-- Configure the minimum provider confidence level required for individual tools.
|
||||
-- Tool IDs include: web_search, read_web_page
|
||||
-- Allowed values are: NONE, UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
|
||||
-- Defaults: web_search = MEDIUM, read_web_page = MEDIUM, but higher confidence is recommended
|
||||
-- Defaults: web_search = VERY_LOW, read_web_page = VERY_LOW
|
||||
-- CONFIG["SETTINGS"]["DataTools.MinimumProviderConfidenceByToolId"] = {
|
||||
-- ["web_search"] = "MEDIUM",
|
||||
-- ["read_web_page"] = "MEDIUM"
|
||||
-- ["web_search"] = "VERY_LOW",
|
||||
-- ["read_web_page"] = "VERY_LOW"
|
||||
-- }
|
||||
|
||||
-- Configure the Web Search tool. All values are strings.
|
||||
@ -489,7 +489,8 @@ CONFIG["SETTINGS"] = {}
|
||||
-- Configure provider instances trusted by your organization for data-source security checks.
|
||||
-- These IDs may refer to LLM providers, embedding providers, or transcription providers
|
||||
-- defined in this configuration. Trusted providers are treated like self-hosted providers
|
||||
-- only for data-source security checks and related local data warnings.
|
||||
-- only for data-source security checks and related local data warnings. Trusted LLM providers
|
||||
-- can also use read_web_page for explicitly allowed private or VPN hosts.
|
||||
-- CONFIG["SETTINGS"]["DataSourceSecuritySettings.TrustedProviderIds"] = {
|
||||
-- "00000000-0000-0000-0000-000000000000",
|
||||
-- "00000000-0000-0000-0000-000000000001",
|
||||
|
||||
@ -45,7 +45,6 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
||||
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -982,7 +982,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
yield break;
|
||||
|
||||
// Parse the API parameters:
|
||||
var apiParameters = this.ParseAdditionalApiParameters();
|
||||
var apiParameters = this.ParseAdditionalApiParameters("parallel_tool_calls");
|
||||
|
||||
var toolRegistry = Program.SERVICE_PROVIDER.GetService<ToolRegistry>();
|
||||
var toolExecutor = Program.SERVICE_PROVIDER.GetService<ToolExecutor>();
|
||||
@ -1054,10 +1054,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
{
|
||||
Messages = [..requestDtoBase.Messages, ..internalMessages],
|
||||
Stream = false,
|
||||
ParallelToolCalls = requestDtoBase.Tools is not null &&
|
||||
chatModel.Id.Contains("gpt-oss", StringComparison.InvariantCultureIgnoreCase)
|
||||
? false
|
||||
: requestDtoBase.ParallelToolCalls,
|
||||
ParallelToolCalls = requestDtoBase.Tools is null ? null : false,
|
||||
};
|
||||
var response = await this.ExecuteChatCompletionRequest(requestDto, requestPath, requestedSecret, headersAction, token);
|
||||
var responseMessage = response?.Choices.FirstOrDefault()?.Message;
|
||||
@ -1097,7 +1094,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
internalMessages.Add(new AssistantToolCallMessage
|
||||
{
|
||||
Content = responseMessage.Content,
|
||||
Content = responseMessage.RawContent,
|
||||
ReasoningContent = responseMessage.ReasoningContent,
|
||||
ToolCalls = toolCalls,
|
||||
});
|
||||
@ -1121,7 +1118,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
toolCall.Function.Name,
|
||||
toolCall.Function.Arguments,
|
||||
runnableTools,
|
||||
this.Provider.GetConfidence(settingsManager).Level,
|
||||
this,
|
||||
toolCallCount,
|
||||
token);
|
||||
toolResultCharacterCount += toolContent.Length;
|
||||
|
||||
@ -45,7 +45,6 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, ne
|
||||
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -46,7 +46,6 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, new Uri(
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -45,7 +45,6 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, new Uri("ht
|
||||
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -48,7 +48,6 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, new Uri("https
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -49,7 +49,6 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, new Uri("https://a
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -47,7 +47,6 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, n
|
||||
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -50,7 +50,6 @@ public sealed class ProviderHuggingFace : BaseProvider
|
||||
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -52,7 +52,6 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
@ -141,4 +140,4 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, new U
|
||||
token,
|
||||
apiKeyProvisional);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Provider.OpenAI;
|
||||
@ -6,7 +7,7 @@ public sealed record AssistantToolCallMessage : IMessageBase
|
||||
{
|
||||
public string Role { get; init; } = "assistant";
|
||||
|
||||
public string? Content { get; init; }
|
||||
public JsonElement? Content { get; init; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? ReasoningContent { get; init; }
|
||||
|
||||
@ -7,7 +7,7 @@ namespace AIStudio.Provider.OpenAI;
|
||||
/// <param name="Delta">The delta text of the choice.</param>
|
||||
public record ChatCompletionChoice(int Index, ChatCompletionDelta Delta)
|
||||
{
|
||||
public ChatCompletionChoice() : this(0, new (string.Empty))
|
||||
public ChatCompletionChoice() : this(0, new())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Provider.OpenAI;
|
||||
|
||||
internal static class ChatCompletionContent
|
||||
{
|
||||
public static string? GetText(JsonElement? content)
|
||||
{
|
||||
if (content is not { } value)
|
||||
return null;
|
||||
|
||||
if (value.ValueKind is JsonValueKind.String)
|
||||
return value.GetString();
|
||||
|
||||
if (value.ValueKind is not JsonValueKind.Array)
|
||||
return null;
|
||||
|
||||
var text = new StringBuilder();
|
||||
foreach (var chunk in value.EnumerateArray())
|
||||
{
|
||||
if (chunk.ValueKind is JsonValueKind.String)
|
||||
{
|
||||
text.Append(chunk.GetString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.ValueKind is not JsonValueKind.Object ||
|
||||
!chunk.TryGetProperty("type", out var type) ||
|
||||
type.ValueKind is not JsonValueKind.String ||
|
||||
!string.Equals(type.GetString(), "text", StringComparison.Ordinal) ||
|
||||
!chunk.TryGetProperty("text", out var textElement) ||
|
||||
textElement.ValueKind is not JsonValueKind.String)
|
||||
continue;
|
||||
|
||||
text.Append(textElement.GetString());
|
||||
}
|
||||
|
||||
return text.Length == 0 ? null : text.ToString();
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,16 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Provider.OpenAI;
|
||||
|
||||
/// <summary>
|
||||
/// The delta text of a choice.
|
||||
/// </summary>
|
||||
/// <param name="Content">The content of the delta text.</param>
|
||||
public record ChatCompletionDelta(string Content)
|
||||
public sealed record ChatCompletionDelta
|
||||
{
|
||||
public ChatCompletionDelta() : this(string.Empty)
|
||||
{
|
||||
}
|
||||
}
|
||||
[JsonPropertyName("content")]
|
||||
public JsonElement? RawContent { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Content => ChatCompletionContent.GetText(this.RawContent) ?? string.Empty;
|
||||
}
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Provider.OpenAI;
|
||||
|
||||
public sealed record ChatCompletionResponseMessage
|
||||
{
|
||||
public string Role { get; init; } = string.Empty;
|
||||
|
||||
public string? Content { get; init; }
|
||||
[JsonPropertyName("content")]
|
||||
public JsonElement? RawContent { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string? Content => ChatCompletionContent.GetText(this.RawContent);
|
||||
|
||||
public string? ReasoningContent { get; init; }
|
||||
|
||||
|
||||
@ -157,7 +157,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
|
||||
Messages = [systemPrompt, ..messages],
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters,
|
||||
};
|
||||
},
|
||||
@ -235,7 +234,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
|
||||
toolExecutor,
|
||||
currentAssistantContent,
|
||||
requestedSecret,
|
||||
providerConfidence,
|
||||
token))
|
||||
yield return content;
|
||||
|
||||
@ -318,7 +316,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
|
||||
ToolExecutor toolExecutor,
|
||||
ContentText? currentAssistantContent,
|
||||
RequestedSecret requestedSecret,
|
||||
ConfidenceLevel providerConfidence,
|
||||
[EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
var localProviderTools = runnableTools
|
||||
@ -424,7 +421,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
|
||||
functionCall.Name,
|
||||
functionCall.Arguments,
|
||||
runnableTools,
|
||||
providerConfidence,
|
||||
this,
|
||||
toolCallCount,
|
||||
token);
|
||||
toolResultCharacterCount += toolContent.Length;
|
||||
|
||||
@ -50,7 +50,6 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
@ -152,4 +151,4 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
request.Headers.Add("X-Title", PROJECT_NAME);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,6 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
|
||||
Messages = [systemPrompt, ..messages],
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -58,7 +58,6 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -46,7 +46,6 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, new Uri("https://
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
Tools = tools,
|
||||
ParallelToolCalls = tools is null ? null : true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
};
|
||||
},
|
||||
|
||||
@ -42,6 +42,8 @@ public static class DataSourceSecurityTrustExtensions
|
||||
|
||||
public static bool IsTrustedByConfiguration(this TranscriptionProvider provider, SettingsManager settingsManager) => IsTrustedProviderId(provider.Id, settingsManager);
|
||||
|
||||
public static bool IsTrustedByConfiguration(this IProvider provider, SettingsManager settingsManager) => IsTrustedProviderId(provider.ConfiguredProviderId, settingsManager);
|
||||
|
||||
private static bool IsTrustedProviderId(string providerId, SettingsManager settingsManager)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
@ -49,4 +51,4 @@ public static class DataSourceSecurityTrustExtensions
|
||||
|
||||
return settingsManager.ConfigurationData.DataSourceSecurity.TrustedProviderIds.Any(id => string.Equals(id, providerId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
{
|
||||
"timeoutSeconds" => TB("(Optional) HTTP timeout for loading a web page in seconds."),
|
||||
"maxContentCharacters" => TB("(Optional) Global truncation limit for extracted characters returned to the model."),
|
||||
ALLOWED_PRIVATE_HOSTS_SETTING => TB("(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."),
|
||||
ALLOWED_PRIVATE_HOSTS_SETTING => TB("(Optional) Host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a High-confidence provider or a provider trusted by your organization's configuration. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."),
|
||||
_ => TB(fieldDefinition.Description),
|
||||
};
|
||||
|
||||
@ -113,6 +113,7 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
{
|
||||
TimeoutSeconds = timeoutSeconds,
|
||||
ProviderConfidence = context.ProviderConfidence,
|
||||
ProviderIsTrustedByConfiguration = context.ProviderIsTrustedByConfiguration,
|
||||
UseOsSso = true,
|
||||
IsPrivateHostAllowed = host => IsAllowedPrivateHost(host, allowedPrivateHosts),
|
||||
OnPrivateHostProviderBlockAsync = this.ReportPrivateHostProviderBlockAsync,
|
||||
@ -239,13 +240,13 @@ public sealed class ReadWebPageTool(WebPageRetrievalService webPageRetrievalServ
|
||||
private async Task ReportPrivateHostProviderBlockAsync(Uri url, ConfidenceLevel providerConfidence)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Blocked read_web_page access to allowed private host '{Host}' because provider confidence '{ProviderConfidence}' is below HIGH.",
|
||||
"Blocked read_web_page access to allowed private host '{Host}' because provider confidence '{ProviderConfidence}' is below HIGH and the provider is not trusted by configuration.",
|
||||
url.Host,
|
||||
providerConfidence);
|
||||
|
||||
await MessageBus.INSTANCE.SendError(new DataErrorMessage(
|
||||
Icons.Material.Filled.Security,
|
||||
TB("The web page was not loaded because private or VPN web pages require a High-confidence provider.")));
|
||||
TB("The web page was not loaded because private or VPN web pages require a High-confidence provider or a provider trusted by your organization's configuration.")));
|
||||
}
|
||||
|
||||
private static bool IsAllowedPrivateHost(string host, IReadOnlyList<AllowedPrivateHostPattern> allowedPrivateHosts)
|
||||
|
||||
@ -18,6 +18,8 @@ public sealed class ToolExecutionContext
|
||||
public required IReadOnlyDictionary<string, string> SettingsValues { get; init; }
|
||||
|
||||
public ConfidenceLevel ProviderConfidence { get; init; } = ConfidenceLevel.UNKNOWN;
|
||||
|
||||
public bool ProviderIsTrustedByConfiguration { get; init; }
|
||||
}
|
||||
|
||||
public sealed class ToolExecutionResult
|
||||
|
||||
@ -2,6 +2,7 @@ using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@ -14,7 +15,7 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
|
||||
string toolName,
|
||||
string argumentsJson,
|
||||
IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools,
|
||||
ConfidenceLevel providerConfidence,
|
||||
IProvider provider,
|
||||
int order,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
@ -57,13 +58,15 @@ public sealed class ToolExecutor(ToolSettingsService toolSettingsService, ILogge
|
||||
{
|
||||
using var document = JsonDocument.Parse(string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson);
|
||||
var settingsValues = await toolSettingsService.GetSettingsAsync(definition);
|
||||
var settingsManager = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||
var result = await implementation.ExecuteAsync(document.RootElement, new ToolExecutionContext
|
||||
{
|
||||
Definition = definition,
|
||||
ToolCallId = toolCallId,
|
||||
SettingsManager = Program.SERVICE_PROVIDER.GetRequiredService<Settings.SettingsManager>(),
|
||||
SettingsManager = settingsManager,
|
||||
SettingsValues = settingsValues,
|
||||
ProviderConfidence = providerConfidence,
|
||||
ProviderConfidence = provider.Provider.GetConfidence(settingsManager).Level,
|
||||
ProviderIsTrustedByConfiguration = provider.IsTrustedByConfiguration(settingsManager),
|
||||
}, token);
|
||||
logger.LogInformation("Completed tool execution. ToolName={ToolName}, ToolCallId={ToolCallId}, DurationMs={DurationMs}, Status={Status}", toolName, toolCallId, stopwatch.ElapsedMilliseconds, ToolInvocationTraceStatus.SUCCESS);
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ public static class ToolSelectionRules
|
||||
|
||||
public static ConfidenceLevel GetDefaultMinimumProviderConfidence(string toolId) => toolId switch
|
||||
{
|
||||
WEB_SEARCH_TOOL_ID => ConfidenceLevel.MEDIUM,
|
||||
READ_WEB_PAGE_TOOL_ID => ConfidenceLevel.MEDIUM,
|
||||
WEB_SEARCH_TOOL_ID => ConfidenceLevel.VERY_LOW,
|
||||
READ_WEB_PAGE_TOOL_ID => ConfidenceLevel.VERY_LOW,
|
||||
_ => ConfidenceLevel.NONE,
|
||||
};
|
||||
|
||||
|
||||
@ -119,13 +119,13 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser)
|
||||
"Private or local-network web page URLs are not supported unless their host is explicitly allowed.",
|
||||
WebPageAccessBlockReason.PRIVATE_HOST_NOT_ALLOWED);
|
||||
|
||||
if (options.ProviderConfidence >= ConfidenceLevel.HIGH)
|
||||
if (options.ProviderConfidence >= ConfidenceLevel.HIGH || options.ProviderIsTrustedByConfiguration)
|
||||
return addresses;
|
||||
|
||||
if (options.OnPrivateHostProviderBlockAsync is not null)
|
||||
await options.OnPrivateHostProviderBlockAsync(url, options.ProviderConfidence);
|
||||
throw new WebPageAccessBlockedException(
|
||||
"This private or VPN web page requires a High-confidence provider.",
|
||||
"This private or VPN web page requires a High-confidence provider or a provider trusted by configuration.",
|
||||
WebPageAccessBlockReason.INSUFFICIENT_PROVIDER_CONFIDENCE);
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ public sealed class WebPageRetrievalService(HTMLParser htmlParser)
|
||||
IReadOnlyList<IPAddress> addresses,
|
||||
WebPageRetrievalOptions options) =>
|
||||
options.UseOsSso &&
|
||||
options.ProviderConfidence >= ConfidenceLevel.HIGH &&
|
||||
(options.ProviderConfidence >= ConfidenceLevel.HIGH || options.ProviderIsTrustedByConfiguration) &&
|
||||
originalUrl.Scheme.Equals(candidateUrl.Scheme, StringComparison.OrdinalIgnoreCase) &&
|
||||
originalUrl.Host.Equals(candidateUrl.Host, StringComparison.OrdinalIgnoreCase) &&
|
||||
originalUrl.Port == candidateUrl.Port &&
|
||||
@ -240,6 +240,8 @@ public sealed class WebPageRetrievalOptions
|
||||
|
||||
public ConfidenceLevel ProviderConfidence { get; init; } = ConfidenceLevel.NONE;
|
||||
|
||||
public bool ProviderIsTrustedByConfiguration { get; init; }
|
||||
|
||||
public bool UseOsSso { get; init; }
|
||||
|
||||
public Func<string, bool>? IsPrivateHostAllowed { get; init; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user