From e9eb80e9a4747ed50258350792d642fd470743da Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Mon, 10 Mar 2025 16:20:43 +0100 Subject: [PATCH 1/2] Use strict by default for OpenAI schemas --- .../Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs | 3 ++- .../OpenAIModelMapper.ChatCompletion.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs index 3dec5920e22..01d7ae90837 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs @@ -267,7 +267,8 @@ strictObj is bool strictValue ? AssistantResponseFormat.CreateJsonSchemaFormat( jsonFormat.SchemaName ?? "json_schema", BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(jsonSchema, OpenAIJsonContext.Default.JsonElement)), - jsonFormat.SchemaDescription) : + jsonFormat.SchemaDescription, + strictSchemaEnabled: true) : AssistantResponseFormat.JsonObject; } } diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs index c16ad7fe543..7ecc1136d8f 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs @@ -402,7 +402,8 @@ public static ChatCompletionOptions ToOpenAIOptions(ChatOptions? options) jsonFormat.SchemaName ?? "json_schema", BinaryData.FromBytes( JsonSerializer.SerializeToUtf8Bytes(jsonSchema, OpenAIJsonContext.Default.JsonElement)), - jsonFormat.SchemaDescription) : + jsonFormat.SchemaDescription, + jsonSchemaIsStrict: true) : OpenAI.Chat.ChatResponseFormat.CreateJsonObjectFormat(); } } From 5148a4565bd7954be05ac8fc66e098f4213048a2 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 16 Mar 2025 10:58:12 -0400 Subject: [PATCH 2/2] Fix up remaining places that handle strict --- .../AzureAIInferenceChatClient.cs | 3 ++- .../OpenAIAssistantClient.cs | 9 +++++---- .../OpenAIModelMapper.ChatCompletion.cs | 9 +++++---- .../OpenAIResponseChatClient.cs | 3 ++- .../AzureAIInferenceChatClientTests.cs | 3 ++- .../OpenAIChatClientTests.cs | 2 ++ 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs index 4a373baf069..becb73c9606 100644 --- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs @@ -377,7 +377,8 @@ private ChatCompletionsOptions ToAzureAIOptions(IEnumerable chatCon ["required"] = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool.Required, JsonContext.Default.ListString)), ["additionalProperties"] = _falseString, }, - json.SchemaDescription); + json.SchemaDescription, + jsonSchemaIsStrict: true); } else { diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs index 01d7ae90837..1e6a29ad80e 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs @@ -217,10 +217,11 @@ private static (RunCreationOptions RunOptions, List? Tool switch (tool) { case AIFunction aiFunction: - bool? strict = - aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) && - strictObj is bool strictValue ? - strictValue : null; + // Default strict to true, but allow to be overridden by an additional Strict property. + bool strict = + !aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) || + strictObj is not bool strictValue || + strictValue; var functionParameters = BinaryData.FromBytes( JsonSerializer.SerializeToUtf8Bytes( diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs index 7ecc1136d8f..9edcac55c5e 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs @@ -444,10 +444,11 @@ private sealed class MetadataOnlyAIFunction(string name, string description, Jso /// Converts an Extensions function to an OpenAI chat tool. private static ChatTool ToOpenAIChatTool(AIFunction aiFunction) { - bool? strict = - aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) && - strictObj is bool strictValue ? - strictValue : null; + // Default strict to true, but allow to be overridden by an additional Strict property. + bool strict = + !aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) || + strictObj is not bool strictValue || + strictValue; // Map to an intermediate model so that redundant properties are skipped. var tool = JsonSerializer.Deserialize(aiFunction.JsonSchema, OpenAIJsonContext.Default.OpenAIChatToolJson)!; diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs index d54440902aa..1944f652c9c 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs @@ -412,7 +412,8 @@ private static ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptio ResponseTextFormat.CreateJsonSchemaFormat( jsonFormat.SchemaName ?? "json_schema", BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(jsonSchema, OpenAIJsonContext.Default.JsonElement)), - jsonFormat.SchemaDescription) : + jsonFormat.SchemaDescription, + jsonSchemaIsStrict: true) : ResponseTextFormat.CreateJsonObjectFormat(); } } diff --git a/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs index d0167c8778b..c3e2c74cb4c 100644 --- a/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs @@ -408,7 +408,8 @@ public async Task ResponseFormat_JsonSchema_NonStreaming() "required":["description"], "additionalProperties":false }, - "description":"An object with a description" + "description":"An object with a description", + "strict":true } } } diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs index 8a3e158041e..4097491399f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs @@ -691,6 +691,7 @@ public async Task FunctionCallContent_NonStreaming() "function": { "description": "Gets the age of the specified person.", "name": "GetPersonAge", + "strict":true, "parameters": { "type": "object", "required": [ @@ -811,6 +812,7 @@ public async Task FunctionCallContent_Streaming() "function": { "description": "Gets the age of the specified person.", "name": "GetPersonAge", + "strict":true, "parameters": { "type": "object", "required": [