Skip to content

Commit e4e3187

Browse files
danielwinklerdanielwinklermedelstephentoub
authored
[AI extensions] Add jsonSchemaIsStrict option to OpenAI options mapping (#6064)
* Use strict by default for OpenAI schemas * Fix up remaining places that handle strict --------- Co-authored-by: Daniel Winkler <[email protected]> Co-authored-by: Stephen Toub <[email protected]>
1 parent 3d61789 commit e4e3187

File tree

6 files changed

+22
-13
lines changed

6 files changed

+22
-13
lines changed

src/Libraries/Microsoft.Extensions.AI.AzureAIInference/AzureAIInferenceChatClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,8 @@ private ChatCompletionsOptions ToAzureAIOptions(IEnumerable<ChatMessage> chatCon
377377
["required"] = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool.Required, JsonContext.Default.ListString)),
378378
["additionalProperties"] = _falseString,
379379
},
380-
json.SchemaDescription);
380+
json.SchemaDescription,
381+
jsonSchemaIsStrict: true);
381382
}
382383
else
383384
{

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantClient.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,11 @@ private static (RunCreationOptions RunOptions, List<FunctionResultContent>? Tool
217217
switch (tool)
218218
{
219219
case AIFunction aiFunction:
220-
bool? strict =
221-
aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) &&
222-
strictObj is bool strictValue ?
223-
strictValue : null;
220+
// Default strict to true, but allow to be overridden by an additional Strict property.
221+
bool strict =
222+
!aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) ||
223+
strictObj is not bool strictValue ||
224+
strictValue;
224225

225226
var functionParameters = BinaryData.FromBytes(
226227
JsonSerializer.SerializeToUtf8Bytes(
@@ -267,7 +268,8 @@ strictObj is bool strictValue ?
267268
AssistantResponseFormat.CreateJsonSchemaFormat(
268269
jsonFormat.SchemaName ?? "json_schema",
269270
BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(jsonSchema, OpenAIJsonContext.Default.JsonElement)),
270-
jsonFormat.SchemaDescription) :
271+
jsonFormat.SchemaDescription,
272+
strictSchemaEnabled: true) :
271273
AssistantResponseFormat.JsonObject;
272274
}
273275
}

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIModelMapper.ChatCompletion.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,8 @@ public static ChatCompletionOptions ToOpenAIOptions(ChatOptions? options)
402402
jsonFormat.SchemaName ?? "json_schema",
403403
BinaryData.FromBytes(
404404
JsonSerializer.SerializeToUtf8Bytes(jsonSchema, OpenAIJsonContext.Default.JsonElement)),
405-
jsonFormat.SchemaDescription) :
405+
jsonFormat.SchemaDescription,
406+
jsonSchemaIsStrict: true) :
406407
OpenAI.Chat.ChatResponseFormat.CreateJsonObjectFormat();
407408
}
408409
}
@@ -443,10 +444,11 @@ private sealed class MetadataOnlyAIFunction(string name, string description, Jso
443444
/// <summary>Converts an Extensions function to an OpenAI chat tool.</summary>
444445
private static ChatTool ToOpenAIChatTool(AIFunction aiFunction)
445446
{
446-
bool? strict =
447-
aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) &&
448-
strictObj is bool strictValue ?
449-
strictValue : null;
447+
// Default strict to true, but allow to be overridden by an additional Strict property.
448+
bool strict =
449+
!aiFunction.AdditionalProperties.TryGetValue("Strict", out object? strictObj) ||
450+
strictObj is not bool strictValue ||
451+
strictValue;
450452

451453
// Map to an intermediate model so that redundant properties are skipped.
452454
var tool = JsonSerializer.Deserialize(aiFunction.JsonSchema, OpenAIJsonContext.Default.OpenAIChatToolJson)!;

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,8 @@ private static ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptio
412412
ResponseTextFormat.CreateJsonSchemaFormat(
413413
jsonFormat.SchemaName ?? "json_schema",
414414
BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(jsonSchema, OpenAIJsonContext.Default.JsonElement)),
415-
jsonFormat.SchemaDescription) :
415+
jsonFormat.SchemaDescription,
416+
jsonSchemaIsStrict: true) :
416417
ResponseTextFormat.CreateJsonObjectFormat();
417418
}
418419
}

test/Libraries/Microsoft.Extensions.AI.AzureAIInference.Tests/AzureAIInferenceChatClientTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,8 @@ public async Task ResponseFormat_JsonSchema_NonStreaming()
408408
"required":["description"],
409409
"additionalProperties":false
410410
},
411-
"description":"An object with a description"
411+
"description":"An object with a description",
412+
"strict":true
412413
}
413414
}
414415
}

test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ public async Task FunctionCallContent_NonStreaming()
691691
"function": {
692692
"description": "Gets the age of the specified person.",
693693
"name": "GetPersonAge",
694+
"strict":true,
694695
"parameters": {
695696
"type": "object",
696697
"required": [
@@ -811,6 +812,7 @@ public async Task FunctionCallContent_Streaming()
811812
"function": {
812813
"description": "Gets the age of the specified person.",
813814
"name": "GetPersonAge",
815+
"strict":true,
814816
"parameters": {
815817
"type": "object",
816818
"required": [

0 commit comments

Comments
 (0)