Fixed OpenAI requests always offering OpenAI's own web search (#1041)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-10-10 15:38:50 +02:00
1 parent 9215011b08
commit ca6ad6b1f0
8 files changed
+9 -69

No files matched your search

@@ -87,18 +87,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
var toolRegistry = Program.SERVICE_PROVIDER.GetService<ToolRegistry>();
var providerConfidence = this.Provider.GetConfidence(settingsManager).Level;
//
// The provider-native web search is held to the same confidence the local web search tool
// asks for: to the user it is the same act, whoever performs the search.
//
var minimumWebSearchConfidence = toolRegistry?.GetMinimumProviderConfidence(ToolSelectionRules.WEB_SEARCH_TOOL_ID) ?? ConfidenceLevel.NONE;
var isWebSearchAllowed = toolRegistry?.IsToolActive(ToolSelectionRules.WEB_SEARCH_TOOL_ID) is true &&
ToolSelectionRules.IsProviderConfidenceAllowed(providerConfidence, minimumWebSearchConfidence);
IList<object> providerTools = modelProfile.Has(Capability.WEB_SEARCH) && isWebSearchAllowed
? [ ProviderTools.WEB_SEARCH ]
: [];
// Parse the API parameters:
var additionalApiParameters = this.ParseAdditionalApiParameters("input", "store", "tools");
@@ -220,7 +208,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
chatModel,
baseInput,
additionalApiParameters,
providerTools,
runnableTools,
(requestDto, requestToken) => this.StreamResponsesRequest(requestDto, requestedSecret, requestToken));
@@ -243,9 +230,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
yield break;
}
if (runnableTools.Count > 0)
providerTools = [];
//
// Create the request: either for the Responses API or the Chat Completion API
//
@@ -277,10 +261,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
// We do not want to store any data on OpenAI's servers:
Store = false,
// Tools we want to use:
Tools = providerTools,
// Additional API parameters:
AdditionalApiParameters = additionalApiParameters
@@ -1,12 +0,0 @@
namespace AIStudio.Provider.OpenAI;
/// <summary>
/// Represents a tool executed on the provider side.
/// </summary>
/// <remarks>
/// Right now, only our OpenAI provider is using tools. Thus, this class is located in the
/// OpenAI namespace. In the future, when other providers also support tools, this class can
/// be moved into the provider namespace.
/// </remarks>
/// <param name="Type">The type of the tool.</param>
public record ProviderTool(string Type);
@@ -1,14 +0,0 @@
namespace AIStudio.Provider.OpenAI;
/// <summary>
/// Known provider-side tools for LLM providers.
/// </summary>
/// <remarks>
/// Right now, only our OpenAI provider is using tools. Thus, this class is located in the
/// OpenAI namespace. In the future, when other providers also support tools, this class can
/// be moved into the provider namespace.
/// </remarks>
public static class ProviderTools
{
public static readonly ProviderTool WEB_SEARCH = new("web_search");
}
@@ -9,7 +9,7 @@ namespace AIStudio.Provider.OpenAI;
/// <param name="Input">The chat messages and Responses API input items.</param>
/// <param name="Stream">Whether to stream the response.</param>
/// <param name="Store">Whether to store the response on the server (usually OpenAI's infrastructure).</param>
/// <param name="Tools">The provider-side tools and local function tools to use for the request.</param>
/// <param name="Tools">The local function tools to use for the request.</param>
public record ResponsesAPIRequest(
string Model,
IList<object> Input,
@@ -13,7 +13,7 @@ namespace AIStudio.Provider.OpenAI;
/// correlated by call ID. Unlike Chat Completions, the whole output of a round has to be sent
/// back for the next one, reasoning items included, or the API refuses to continue.
/// </remarks>
public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> baseInput, IDictionary<string, object> apiParameters, IList<object> providerTools,
public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> baseInput, IDictionary<string, object> apiParameters,
IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools,
Func<ResponsesAPIRequest, CancellationToken, IAsyncEnumerable<ServerSentEvent>> streamRequestAsync) : IToolCallingProviderAdapter
{
@@ -25,13 +25,9 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
public IReadOnlyList<string> RecordedRequestTexts => this.recordedRequestTexts;
/// <summary>
/// The tools offered to the model: the provider-native ones plus our local functions.
/// The tools offered to the model: our local functions.
/// </summary>
/// <remarks>
/// A provider-native tool whose type collides with one of our function names is dropped
/// because the model could not tell the two apart.
/// </remarks>
private readonly IList<object> effectiveProviderTools = BuildEffectiveProviderTools(providerTools, runnableTools);
private readonly IList<object> offeredTools = runnableTools.Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition)).ToList();
/// <inheritdoc />
public async IAsyncEnumerable<ToolCallingStreamEvent> ExecuteRoundAsync(string? finalResponseInstruction, bool includeTools, [EnumeratorCancellation] CancellationToken token = default)
@@ -53,7 +49,7 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
Input = requestInput,
Stream = true,
Store = false,
Tools = includeTools ? this.effectiveProviderTools : [],
Tools = includeTools ? this.offeredTools : [],
AdditionalApiParameters = apiParameters,
};
@@ -127,16 +123,4 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
if (!string.IsNullOrWhiteSpace(content))
this.recordedRequestTexts.Add(content);
}
private static IList<object> BuildEffectiveProviderTools(IList<object> providerTools, IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools)
{
var localFunctionNames = runnableTools
.Select(x => x.Definition.Function.Name)
.ToHashSet(StringComparer.Ordinal);
return providerTools
.Where(x => x is not ProviderTool providerTool || !localFunctionNames.Contains(providerTool.Type))
.Concat(runnableTools.Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition)))
.ToList();
}
}