diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs index d353d46855f..801305ad43a 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs @@ -187,8 +187,8 @@ internal static BinaryData ToOpenAIFunctionParameters(AIFunctionDeclaration aiFu StrictSchemaTransformCache.GetOrCreateTransformedSchema(aiFunction) : aiFunction.JsonSchema; - // Roundtrip the schema through the ToolJson model type to remove extra properties - // and force missing ones into existence, then return the serialized UTF8 bytes as BinaryData. + // Roundtrip the schema through the ToolJson model type to force missing properties + // into existence, then return the serialized UTF8 bytes as BinaryData. var tool = JsonSerializer.Deserialize(jsonSchema, OpenAIJsonContext.Default.ToolJson)!; var functionParameters = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool, OpenAIJsonContext.Default.ToolJson)); @@ -262,5 +262,8 @@ internal sealed class ToolJson [JsonPropertyName("additionalProperties")] public bool AdditionalProperties { get; set; } + + [JsonExtensionData] + public Dictionary? ExtensionData { get; set; } } } diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs index 1aa7e1e4d0f..7ff028ec736 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs @@ -104,6 +104,49 @@ public void AsOpenAIChatTool_ProducesValidInstance() ValidateSchemaParameters(tool.FunctionParameters); } + [Fact] + public void AsOpenAIChatTool_PreservesExtraTopLevelPropertiesLikeDefs() + { + // Create a JSON schema with $defs (used for reference types) + var jsonSchema = JsonDocument.Parse(""" + { + "type": "object", + "properties": { + "person": { "$ref": "#/$defs/Person" } + }, + "required": ["person"], + "$defs": { + "Person": { + "type": "object", + "properties": { + "name": { "type": "string" } + } + } + } + } + """).RootElement; + + var functionWithDefs = AIFunctionFactory.CreateDeclaration( + "test_function_with_defs", + "A test function with $defs", + jsonSchema); + + var tool = functionWithDefs.AsOpenAIChatTool(); + + Assert.NotNull(tool); + Assert.Equal("test_function_with_defs", tool.FunctionName); + Assert.Equal("A test function with $defs", tool.FunctionDescription); + + // Verify that $defs is preserved in the function parameters + using var parsedParams = JsonDocument.Parse(tool.FunctionParameters); + var root = parsedParams.RootElement; + + Assert.True(root.TryGetProperty("$defs", out var defs), "The $defs property should be preserved in the function parameters"); + Assert.True(defs.TryGetProperty("Person", out var person), "The Person definition should exist in $defs"); + Assert.True(person.TryGetProperty("properties", out var properties), "Person should have properties"); + Assert.True(properties.TryGetProperty("name", out _), "Person should have a name property"); + } + [Fact] public void AsOpenAIResponseTool_ProducesValidInstance() {