mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-14 13:33:48 +00:00
Sent strict tool schemas only to providers that enforce them
This commit is contained in:
6 files changed
+239
-13
No files matched your search
@@ -1245,6 +1245,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
/// <param name="requestPath">The request path, relative to the provider base URL.</param>
|
||||
/// <param name="headersAction">Optional additional headers to add.</param>
|
||||
/// <param name="mayAskForSequentialToolCalls">Whether a request which offers tools may ask for one call at a time. False for a provider which rejects the parallel_tool_calls parameter.</param>
|
||||
/// <param name="enforcesStrictToolSchemas">Whether the provider binds the model's tool calls to a strict schema. Only then are tools offered in strict mode; everywhere else a strict schema would only tell the model that every argument is required.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <typeparam name="TRequest">The request DTO type.</typeparam>
|
||||
/// <typeparam name="TDelta">The delta stream line type.</typeparam>
|
||||
@@ -1262,6 +1263,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
string requestPath = "chat/completions",
|
||||
Action<HttpRequestHeaders>? headersAction = null,
|
||||
bool mayAskForSequentialToolCalls = true,
|
||||
bool enforcesStrictToolSchemas = false,
|
||||
[EnumeratorCancellation] CancellationToken token = default)
|
||||
where TRequest : ChatCompletionAPIRequest
|
||||
where TDelta : IResponseStreamLine
|
||||
@@ -1300,7 +1302,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
if (runnableTools.Count > 0)
|
||||
{
|
||||
var adapter = new ChatCompletionToolCallingAdapter<TRequest>(requestFactory, systemPrompt, apiParameters,
|
||||
runnableTools.Select(x => ProviderToolAdapters.ToChatCompletionTool(x.Definition)).ToList(), mayAskForSequentialToolCalls, runnableTools,
|
||||
runnableTools.Select(x => ProviderToolAdapters.ToChatCompletionTool(x.Definition, enforcesStrictToolSchemas)).ToList(), mayAskForSequentialToolCalls, runnableTools,
|
||||
(requestDto, requestToken) => this.StreamChatCompletionRequest(requestDto, providerName, requestPath, requestedSecret, headersAction, requestToken),
|
||||
ChatCompletionSourceReader.Read<TDelta, TAnnotation>,
|
||||
this.logger);
|
||||
|
||||
@@ -168,6 +168,16 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, new Ur
|
||||
},
|
||||
systemPromptRole: systemPromptRole,
|
||||
requestPath: "chat/completions",
|
||||
|
||||
//
|
||||
// OpenAI binds a strict function's arguments to its schema -- read on
|
||||
// 2026-09-24 at https://developers.openai.com/api/docs/guides/function-calling.
|
||||
// Most other hosts of this API do not: Groq applies strict mode to
|
||||
// response formats only (https://console.groq.com/docs/structured-outputs,
|
||||
// same day) and merely validates a tool call afterward, so it rejects
|
||||
// every call that leaves out an argument the strict schema requires:
|
||||
//
|
||||
enforcesStrictToolSchemas: true,
|
||||
token: token))
|
||||
yield return content;
|
||||
|
||||
|
||||
@@ -10,33 +10,47 @@ namespace AIStudio.Provider;
|
||||
/// <remarks>
|
||||
/// The definitions state a tool once, in plain JSON Schema. What differs per API is not only the
|
||||
/// field names but how an optional argument is expressed, which is why the OpenAI shapes convert
|
||||
/// the schema while Anthropic takes it as written.
|
||||
/// the schema while Anthropic takes it as written.<br/><br/>
|
||||
/// Strict mode is a promise of the host, not of the definition: only a host which binds the
|
||||
/// model's output to the schema keeps it. Everywhere else the model merely reads a schema that
|
||||
/// calls every argument required, and then either invents a value for an argument it meant to
|
||||
/// leave out, or the host rejects the call for lacking one. That is why the Chat Completions
|
||||
/// shape, which reaches every OpenAI-compatible host, only goes strict where the host says so.
|
||||
/// </remarks>
|
||||
public static class ProviderToolAdapters
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the nested function tool shape used by Chat Completions compatible APIs.
|
||||
/// </summary>
|
||||
public static object ToChatCompletionTool(ToolDefinition definition) => new
|
||||
/// <param name="definition">The tool to describe.</param>
|
||||
/// <param name="hostEnforcesStrict">Whether the host binds the model's tool calls to a strict schema. Only then is the tool sent in strict mode.</param>
|
||||
public static object ToChatCompletionTool(ToolDefinition definition, bool hostEnforcesStrict)
|
||||
{
|
||||
type = "function",
|
||||
function = new
|
||||
var isStrict = definition.Function.Strict && hostEnforcesStrict;
|
||||
return new
|
||||
{
|
||||
name = definition.Function.Name,
|
||||
description = definition.Function.DescriptionForLLM,
|
||||
parameters = ToOpenAIParameters(definition),
|
||||
strict = definition.Function.Strict,
|
||||
}
|
||||
};
|
||||
type = "function",
|
||||
function = new
|
||||
{
|
||||
name = definition.Function.Name,
|
||||
description = definition.Function.DescriptionForLLM,
|
||||
parameters = ToOpenAIParameters(definition, isStrict),
|
||||
strict = isStrict,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the flat function tool shape used by the OpenAI Responses API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only OpenAI speaks this API, and it enforces strict mode, so the definition alone decides.
|
||||
/// </remarks>
|
||||
public static ResponsesFunctionTool ToResponsesTool(ToolDefinition definition) => new()
|
||||
{
|
||||
Name = definition.Function.Name,
|
||||
Description = definition.Function.DescriptionForLLM,
|
||||
Parameters = ToOpenAIParameters(definition),
|
||||
Parameters = ToOpenAIParameters(definition, definition.Function.Strict),
|
||||
Strict = definition.Function.Strict,
|
||||
};
|
||||
|
||||
@@ -59,7 +73,7 @@ public static class ProviderToolAdapters
|
||||
/// <summary>
|
||||
/// The parameter schema for the OpenAI APIs, converted only when strict mode asks for it.
|
||||
/// </summary>
|
||||
private static System.Text.Json.JsonElement ToOpenAIParameters(ToolDefinition definition) => definition.Function.Strict
|
||||
private static System.Text.Json.JsonElement ToOpenAIParameters(ToolDefinition definition, bool isStrict) => isStrict
|
||||
? OpenAIStrictToolSchema.FromToolParameters(definition.Function.Parameters)
|
||||
: definition.Function.Parameters;
|
||||
}
|
||||
Reference in new issue
Block a user