diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index b9e3eb68..e819c227 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -87,18 +87,6 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur var toolRegistry = Program.SERVICE_PROVIDER.GetService(); 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 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 diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderTool.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderTool.cs deleted file mode 100644 index 61170af3..00000000 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderTool.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace AIStudio.Provider.OpenAI; - -/// -/// Represents a tool executed on the provider side. -/// -/// -/// 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. -/// -/// The type of the tool. -public record ProviderTool(string Type); diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderTools.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderTools.cs deleted file mode 100644 index 359c781b..00000000 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderTools.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace AIStudio.Provider.OpenAI; - -/// -/// Known provider-side tools for LLM providers. -/// -/// -/// 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. -/// -public static class ProviderTools -{ - public static readonly ProviderTool WEB_SEARCH = new("web_search"); -} diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs index 148edc79..155400ae 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesAPIRequest.cs @@ -9,7 +9,7 @@ namespace AIStudio.Provider.OpenAI; /// The chat messages and Responses API input items. /// Whether to stream the response. /// Whether to store the response on the server (usually OpenAI's infrastructure). -/// The provider-side tools and local function tools to use for the request. +/// The local function tools to use for the request. public record ResponsesAPIRequest( string Model, IList Input, diff --git a/app/MindWork AI Studio/Provider/OpenAI/ResponsesToolCallingAdapter.cs b/app/MindWork AI Studio/Provider/OpenAI/ResponsesToolCallingAdapter.cs index 5409f775..f8dbae93 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ResponsesToolCallingAdapter.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ResponsesToolCallingAdapter.cs @@ -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. /// -public sealed class ResponsesToolCallingAdapter(Model chatModel, IList baseInput, IDictionary apiParameters, IList providerTools, +public sealed class ResponsesToolCallingAdapter(Model chatModel, IList baseInput, IDictionary apiParameters, IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools, Func> streamRequestAsync) : IToolCallingProviderAdapter { @@ -25,13 +25,9 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList b public IReadOnlyList RecordedRequestTexts => this.recordedRequestTexts; /// - /// The tools offered to the model: the provider-native ones plus our local functions. + /// The tools offered to the model: our local functions. /// - /// - /// A provider-native tool whose type collides with one of our function names is dropped - /// because the model could not tell the two apart. - /// - private readonly IList effectiveProviderTools = BuildEffectiveProviderTools(providerTools, runnableTools); + private readonly IList offeredTools = runnableTools.Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition)).ToList(); /// public async IAsyncEnumerable ExecuteRoundAsync(string? finalResponseInstruction, bool includeTools, [EnumeratorCancellation] CancellationToken token = default) @@ -53,7 +49,7 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList 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 b if (!string.IsNullOrWhiteSpace(content)) this.recordedRequestTexts.Add(content); } - - private static IList BuildEffectiveProviderTools(IList 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(); - } } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md index 88ab5542..eb806441 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md @@ -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. diff --git a/app/Tests/Provider/ToolCalling/ResponsesToolCallingAdapterTests.cs b/app/Tests/Provider/ToolCalling/ResponsesToolCallingAdapterTests.cs index b2134a5b..cb61cdea 100644 --- a/app/Tests/Provider/ToolCalling/ResponsesToolCallingAdapterTests.cs +++ b/app/Tests/Provider/ToolCalling/ResponsesToolCallingAdapterTests.cs @@ -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(), [], [], (_, token) => Lines(rounds[nextRound++], token)); + return new(new Model("gpt-5", null), [], new Dictionary(), [], (_, token) => Lines(rounds[nextRound++], token)); } private static async IAsyncEnumerable Lines(string[] data, [EnumeratorCancellation] CancellationToken token = default) diff --git a/documentation/Tools.md b/documentation/Tools.md index 07ab9cfb..415a4c8a 100644 --- a/documentation/Tools.md +++ b/documentation/Tools.md @@ -45,7 +45,7 @@ The exception is a provider which rejects the parameter. Hugging Face answers it The OpenAI Responses API may continue to return multiple function calls in one response. AI Studio processes those calls sequentially as well; concurrent execution of separate local tool calls is not currently implemented. This does not restrict concurrency used internally by an individual tool. -Provider-native tools are separate from local function tools and do not have a `ToolDefinition` or an `IToolImplementation`. The local tool calling implementation does not influence the provider-native tool selection at all. +AI Studio offers no provider-native tools, i.e., tools which the provider runs on its own servers, such as OpenAI's hosted web search. That search used to go along with every Responses API request, whatever the user had selected. Merely offering it cost about 4,300 input tokens per request — in a tool calling loop, per round — and it bypassed the rules our own tools follow: the selection below the message field, and the outbound data restriction of a chat which read a mailbox, see `ToolOutboundData`. Nor did it show up in the tool trace. A provider-native tool belongs into the tool selection instead, as a choice the user makes, so that the selection, the minimum provider confidence, and the outbound data restriction apply to it. Its results never reach AI Studio, though, so the prompt injection filter cannot check them; whoever adds such a tool has to decide how to deal with that. If a tool throws `ToolExecutionBlockedException`, `ToolExecutor` returns the exception message as plain text to the model and records the trace as `BLOCKED`. Other exceptions are logged with details and returned to the model as plain text in the form `Tool execution failed: ...`, with the trace recorded as `ERROR`.