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();
}
}
@@ -22,6 +22,7 @@
- Fixed saving the settings taking about 20 seconds each time when a chat template had an attachment on a network drive that was out of reach. Since AI Studio saves its settings while starting, this also slowed down every start.
- Fixed local data sources taking up more and more space on your disk with every change to your documents. AI Studio now removes what the vector database no longer needs.
- Fixed answers breaking off with an error message from self-hosted AI servers such as vLLM when the AI made up the name of a tool or sent broken details for one. AI Studio now tells the AI that its tool call was invalid, and the AI can finish its answer.
- Fixed OpenAI models using about 4,300 more tokens than necessary with every request. AI Studio offered them OpenAI's own web search in the background, even when you had not selected a web search. To let a model search the web, select the Web Search tool below the message field.
- Fixed AI Studio sometimes rebuilding the index of a data source from scratch when the configuration of your organization was applied while the data source was being updated.
- Fixed justified texts being hyphenated by the rules of English even when AI Studio shows another language, such as German. Screen readers now also know which language AI Studio uses.
- Updated the code contributions on the supporters page, which now thank everyone who has contributed code to AI Studio so far.
@@ -96,7 +96,7 @@ public sealed class ResponsesToolCallingAdapterTests
private static ResponsesToolCallingAdapter Adapter(params string[][] rounds)
{
var nextRound = 0;
return new(new Model("gpt-5", null), [], new Dictionary<string, object>(), [], [], (_, token) => Lines(rounds[nextRound++], token));
return new(new Model("gpt-5", null), [], new Dictionary<string, object>(), [], (_, token) => Lines(rounds[nextRound++], token));
}
private static async IAsyncEnumerable<ServerSentEvent> Lines(string[] data, [EnumeratorCancellation] CancellationToken token = default)