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 3dec5920e22..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( @@ -267,7 +268,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..9edcac55c5e 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(); } } @@ -443,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": [